mirror of
https://github.com/sasjs/core.git
synced 2025-12-21 18:21:20 +00:00
Compare commits
376 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee35f47f4f | ||
|
|
7f867e2a5c | ||
|
|
c6af6ce578 | ||
|
|
a1aac785c0 | ||
|
|
dbe8b0b1c3 | ||
|
|
2ee9a4cee4 | ||
|
|
3a7afdffb7 | ||
|
|
c78211aa1c | ||
|
|
76c49e96f2 | ||
|
|
984ea44f5d | ||
|
|
88f1222abd | ||
|
|
d88f028ee3 | ||
|
|
07d7c9df4b | ||
|
|
6765a1d025 | ||
|
|
952f28a872 | ||
|
|
8246b5a42c | ||
|
|
72123aeeb7 | ||
|
|
236d1ae25f | ||
|
|
b75369b28d | ||
|
|
63871db170 | ||
|
|
6456c2f6e2 | ||
|
|
36faa194a8 | ||
|
|
093dc87aad | ||
|
|
ca045e3ebf | ||
|
|
be5e2f371d | ||
|
|
6d15465bac | ||
|
|
2031a5b0c0 | ||
|
|
7b3844a391 | ||
|
|
202de36042 | ||
|
|
62837b512b | ||
|
|
5d5a99fd77 | ||
|
|
1b5effd584 | ||
|
|
1613ab2c9e | ||
|
|
a2df4e35be | ||
|
|
aabbcfdf6b | ||
|
|
7b7759e1ce | ||
|
|
e5a3053600 | ||
|
|
9856d0ef58 | ||
|
|
77b37e5503 | ||
|
|
793319fe38 | ||
|
|
594a895ddd | ||
|
|
0d59266b8d | ||
|
|
4863aafaa8 | ||
|
|
6015320145 | ||
|
|
8c09c0bce0 | ||
|
|
437943b779 | ||
|
|
6a090e45b6 | ||
|
|
a7dc314204 | ||
|
|
37076eae89 | ||
|
|
9a9f8dc847 | ||
|
|
719b657267 | ||
|
|
671a615501 | ||
|
|
884b45bf12 | ||
|
|
ff6ae1b066 | ||
|
|
d581fec55e | ||
|
|
a5613a79bb | ||
|
|
c6703e16e8 | ||
|
|
6587dce95b | ||
|
|
b60e6448b9 | ||
|
|
46d9b58b32 | ||
|
|
349cbabc94 | ||
|
|
9de056a3fc | ||
|
|
ad497b322f | ||
|
|
7a6408ee44 | ||
|
|
336743f2b4 | ||
|
|
6e32eb3bd6 | ||
|
|
b377b83442 | ||
|
|
899b94bb6e | ||
|
|
d97efdff61 | ||
|
|
1097afbcb8 | ||
|
|
165b2d3568 | ||
|
|
44a80c8985 | ||
|
|
6e32d9b743 | ||
|
|
6b167e7a4c | ||
|
|
011672b1ed | ||
|
|
a7eb926810 | ||
|
|
cad7f13a0e | ||
|
|
65fcea817a | ||
|
|
22fade13e7 | ||
|
|
7146310072 | ||
|
|
b7de1c25ec | ||
|
|
f4c7f47ffe | ||
|
|
cdf339d077 | ||
|
|
31702df19b | ||
|
|
cf0d1c0473 | ||
|
|
1f369f9848 | ||
|
|
2372ff5f4f | ||
|
|
6d0e34ba1d | ||
|
|
7a69698178 | ||
|
|
532bf84e06 | ||
|
|
e1afbc02c4 | ||
|
|
756f00d88d | ||
|
|
b72e404d52 | ||
|
|
e31cdeef42 | ||
|
|
8a4e32cc27 | ||
|
|
f285505b79 | ||
|
|
67f5c50300 | ||
|
|
ce39e4f779 | ||
|
|
9c80f5664c | ||
|
|
83466c001b | ||
|
|
ad315be503 | ||
|
|
c41ae2dcc8 | ||
| d9f8e92fac | |||
|
|
d42ede15db | ||
|
|
08ea9f7c00 | ||
|
|
c327e1fc0d | ||
|
|
02fddcf9a1 | ||
|
|
4752bfbb05 | ||
|
|
767ddd7add | ||
|
|
54a24ced83 | ||
|
|
57ae2981f1 | ||
|
|
a3043ac685 | ||
|
|
2bdb90b0be | ||
|
|
2cd846d504 | ||
|
|
f593c7bec9 | ||
|
|
c8805db0b5 | ||
|
|
1eb6d8cec9 | ||
|
|
ed19ee03af | ||
|
|
a1c931b5e6 | ||
|
|
cb553a31ab | ||
|
|
557df272ff | ||
|
|
0cb3c96c15 | ||
|
|
1cb39d4d61 | ||
|
|
934b7d4f8a | ||
|
|
24c50cde56 | ||
|
|
055e8d2f13 | ||
|
|
abfe7fe339 | ||
|
|
16ed91f6a9 | ||
|
|
67ba2a5286 | ||
|
|
3d7f9b71e1 | ||
|
|
1d972fad11 | ||
|
|
e23bc461c4 | ||
|
|
28ed458b83 | ||
|
|
827210e010 | ||
|
|
de2f32da36 | ||
|
|
6fa0fc5dc6 | ||
|
|
73e3d9d419 | ||
|
|
5f2229e3d5 | ||
|
|
d19c4a517c | ||
|
|
c47480f60c | ||
|
|
295211bb72 | ||
|
|
818bc3cc2b | ||
|
|
bb6111e2b3 | ||
|
|
512f05c0b2 | ||
|
|
500fb8124f | ||
|
|
88ddba2a4b | ||
|
|
86f6d06b85 | ||
|
|
1cefc0e7ee | ||
|
|
412182a022 | ||
|
|
43b8ee1c7e | ||
|
|
83eea02240 | ||
|
|
a14e31804a | ||
|
|
3fa639ebf7 | ||
|
|
ed11d44fe8 | ||
|
|
de4ea8888f | ||
|
|
ea0a936871 | ||
|
|
042987c91e | ||
|
|
6669e74baa | ||
|
|
906f9a139d | ||
|
|
b31f960635 | ||
|
|
1ed3cb31b5 | ||
|
|
ca7c332f20 | ||
|
|
d587b44b34 | ||
|
|
e43aac972a | ||
|
|
7dbe31b5d3 | ||
|
|
1672c96340 | ||
|
|
453aee2c1f | ||
|
|
00abbdcd65 | ||
|
|
88685dc585 | ||
|
|
cf8147d6ca | ||
|
|
f28f6b1530 | ||
|
|
cb4ea71e81 | ||
|
|
fe94d3781a | ||
|
|
7c17b39dad | ||
|
|
73dab4c651 | ||
|
|
5d72843167 | ||
|
|
f43df47cff | ||
|
|
aaca26770b | ||
|
|
4a124d5bd8 | ||
|
|
03cd52a01a | ||
|
|
da79181b00 | ||
|
|
a405104052 | ||
|
|
56fdaa65d2 | ||
|
|
9d60c49c9f | ||
|
|
380170d5ba | ||
|
|
4b450f2091 | ||
|
|
1b013fbf1c | ||
|
|
bf7459bd2d | ||
|
|
1096db0846 | ||
|
|
fc9b765246 | ||
|
|
4a8f7bb014 | ||
|
|
e0469be0d8 | ||
|
|
e9e576b5ec | ||
|
|
1a32d114f1 | ||
|
|
94e83f6b8d | ||
|
|
35a6dede6f | ||
|
|
039ec397dd | ||
|
|
dce4630eb8 | ||
|
|
1e142f042b | ||
|
|
7caca2f139 | ||
|
|
61556b2de8 | ||
|
|
9e12409389 | ||
|
|
c8050f5a79 | ||
|
|
cb4f71c7cd | ||
|
|
a39f4e4eee | ||
|
|
b525b4171d | ||
|
|
f2d80b3b63 | ||
|
|
96dda87f37 | ||
|
|
3435509eec | ||
|
|
42f2767129 | ||
|
|
099a5f7840 | ||
|
|
c83ea705a2 | ||
|
|
9ea6c875f2 | ||
|
|
0728f72c4f | ||
|
|
a90a6f00cf | ||
|
|
f71e53af8d | ||
|
|
cc1b971e19 | ||
|
|
8484c752ed | ||
|
|
8bd31e6c97 | ||
|
|
f9b0f87f44 | ||
|
|
d7e9f10291 | ||
|
|
3edc3587b3 | ||
|
|
63bf00e28f | ||
|
|
6b2574947a | ||
|
|
eb9027ecb6 | ||
|
|
2ad931a566 | ||
|
|
cebe119304 | ||
|
|
bd3082d7e3 | ||
|
|
11da53f1cb | ||
|
|
c4cb0b2395 | ||
|
|
58614e9a3d | ||
|
|
c94c334c4b | ||
|
|
3bf44405f8 | ||
|
|
db68a256cb | ||
|
|
611fac6338 | ||
|
|
ddd120bb75 | ||
|
|
0d75e0bad8 | ||
|
|
ba8c4ac844 | ||
|
|
6938a42896 | ||
|
|
efff77c94e | ||
|
|
2b10cf6192 | ||
|
|
134b91f266 | ||
|
|
969f551e10 | ||
|
|
26623ba085 | ||
|
|
8eb495890d | ||
|
|
c1a30977f1 | ||
|
|
9a6be61651 | ||
|
|
388839039e | ||
|
|
e760a89a6a | ||
|
|
d2e30267e8 | ||
|
|
190dbddfe3 | ||
|
|
05e769794e | ||
|
|
558ebaf6f2 | ||
|
|
970b56fe5a | ||
|
|
c2597bd07b | ||
|
|
c4baca477b | ||
|
|
7726b0e0b0 | ||
|
|
0a536245f3 | ||
|
|
edfa9ecc07 | ||
|
|
f4982c85ca | ||
|
|
3ce771d587 | ||
|
|
72d6b446c3 | ||
|
|
40d694eec8 | ||
|
|
6af1423666 | ||
|
|
23a01347f1 | ||
|
|
7c86d6163a | ||
|
|
d7233208f1 | ||
|
|
7f587ba720 | ||
|
|
21ecc1b675 | ||
|
|
6b13dc2b87 | ||
|
|
bb89184212 | ||
|
|
56338caaca | ||
|
|
d7e2ff8ac9 | ||
|
|
582ec0a1f9 | ||
|
|
53785f5644 | ||
|
|
a8acadb8f1 | ||
|
|
23dbda302e | ||
|
|
7e7ab4275d | ||
|
|
a455a3d98d | ||
|
|
588d987c25 | ||
|
|
8ffd06343a | ||
|
|
76207c443c | ||
|
|
7e9e0fac07 | ||
|
|
1fdbc7cce9 | ||
|
|
312369b200 | ||
|
|
c030174bfb | ||
|
|
faf466e79a | ||
|
|
856ffc1b72 | ||
|
|
c0924af06b | ||
|
|
33cec61a13 | ||
|
|
854ff696d8 | ||
|
|
cc3435d13d | ||
|
|
5ceaac195d | ||
|
|
5d5df977a6 | ||
|
|
245e85ef36 | ||
|
|
b96df6f14f | ||
|
|
1932c1e138 | ||
|
|
f7ee012be3 | ||
|
|
b49e11bc79 | ||
|
|
f709a11dfb | ||
|
|
17ed2240d3 | ||
|
|
a8b5107b1a | ||
|
|
735bab5d26 | ||
|
|
86f7876f50 | ||
|
|
46c96bc7ec | ||
|
|
cba3f5972b | ||
|
|
ed48c49964 | ||
|
|
203ff3f80d | ||
|
|
cfe90a8d0d | ||
|
|
0749ea0819 | ||
|
|
e09a39e748 | ||
|
|
20dcefaefd | ||
|
|
4c8347516a | ||
|
|
e497d226a0 | ||
|
|
ccf8f1acc0 | ||
|
|
fe9a2ed979 | ||
|
|
078815e83e | ||
|
|
bb80c7af5a | ||
|
|
842662aae1 | ||
|
|
876fac2332 | ||
|
|
427deca350 | ||
|
|
07bde4b25c | ||
|
|
80b06af581 | ||
|
|
3c026811e9 | ||
|
|
cf547ce7e4 | ||
|
|
6952c79899 | ||
|
|
09e3f63da7 | ||
|
|
d6956f4122 | ||
|
|
6fca73e7da | ||
|
|
880df4138c | ||
|
|
badf5b5761 | ||
|
|
b174aa25b3 | ||
|
|
bc6eac6977 | ||
|
|
2d4d595e5d | ||
|
|
7111fe14fb | ||
|
|
8499e38c55 | ||
|
|
682d80b1b8 | ||
|
|
4fe6f233f2 | ||
|
|
6ba3588eff | ||
|
|
53aa403630 | ||
|
|
cba9255732 | ||
|
|
a7b78c73c4 | ||
|
|
85e0b6a4a9 | ||
|
|
3c7e762eeb | ||
|
|
9a1f7d0985 | ||
|
|
dfd60200fb | ||
|
|
713f7544cd | ||
|
|
de4e96ab01 | ||
|
|
3e7b15c7db | ||
|
|
eb2ccfbbca | ||
|
|
70e508e583 | ||
|
|
8b0acf2eae | ||
|
|
d254870439 | ||
|
|
303225cb85 | ||
|
|
90de167643 | ||
|
|
8ee997de8e | ||
|
|
e27f6ac716 | ||
|
|
ec4de95fcf | ||
|
|
df0fa95519 | ||
|
|
2fe7fba79b | ||
|
|
e40234ee29 | ||
|
|
a287cc27a7 | ||
|
|
921186eb74 | ||
|
|
6fd215ceff | ||
|
|
0297509aa0 | ||
|
|
c5a8bc745d | ||
|
|
36aa466561 | ||
|
|
009485e5b9 | ||
|
|
eb01c8772d | ||
|
|
e3fb69928c | ||
|
|
65afa14466 | ||
|
|
0176b19616 | ||
|
|
9f3dfd9a59 | ||
|
|
513ea354ab | ||
|
|
7b686e11c9 | ||
|
|
3997000266 |
@@ -117,6 +117,24 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "yabwon",
|
||||||
|
"name": "Bart Jablonski",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/9314894?v=4",
|
||||||
|
"profile": "https://github.com/yabwon",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "eltociear",
|
||||||
|
"name": "Ikko Ashimine",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/22633385?v=4",
|
||||||
|
"profile": "https://bandism.net/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
|||||||
8
.devcontainer/Dockerfile
Normal file
8
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/javascript-node/.devcontainer/base.Dockerfile
|
||||||
|
|
||||||
|
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||||
|
ARG VARIANT="18-bullseye"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y doxygen
|
||||||
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||||
|
// https://github.com/microsoft/vscode-dev-containers/tree/v0.233.0/containers/typescript-node
|
||||||
|
{
|
||||||
|
"name": "Node.js & TypeScript",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
// Update 'VARIANT' to pick a Node version: 18, 16, 14.
|
||||||
|
// Append -bullseye or -buster to pin to an OS version.
|
||||||
|
// Use -bullseye variants on local on arm64/Apple Silicon.
|
||||||
|
"args": {
|
||||||
|
"VARIANT": "16-bullseye"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {},
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": [
|
||||||
|
"SASjs.sasjs-for-vscode"
|
||||||
|
],
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
// "forwardPorts": [],
|
||||||
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
|
"postCreateCommand": "npm i && npm i -g @sasjs/cli",
|
||||||
|
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||||
|
"remoteUser": "node"
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
sasjs lint
|
|
||||||
|
# Ensure lint is passing
|
||||||
|
LINT=`sasjs lint`
|
||||||
|
if [[ "$LINT" != "✔ All matched files use @sasjs/lint code style!" ]]; then
|
||||||
|
echo "$LINT"
|
||||||
|
echo "To commit in spite of these warnings, use the -n parameter."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Avoid commits to the master branch
|
# Avoid commits to the master branch
|
||||||
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||||
|
|||||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [sasjs]
|
||||||
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
17
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
## Issue
|
||||||
|
|
||||||
|
Link any related issue(s) in this section.
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
What this PR intends to achieve.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
What code changes have been made to achieve the intent.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
- [ ] Code is formatted correctly (`sasjs lint`).
|
||||||
|
- [ ] Any new functionality has been unit tested.
|
||||||
|
- [ ] All unit tests are passing (`sasjs test`).
|
||||||
9
.github/dependabot.yml
vendored
Normal file
9
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: '/'
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
allow:
|
||||||
|
- dependency-type: "production"
|
||||||
30
.github/vpn/config.ovpn
vendored
30
.github/vpn/config.ovpn
vendored
@@ -1,30 +0,0 @@
|
|||||||
cipher AES-256-CBC
|
|
||||||
setenv FORWARD_COMPATIBLE 1
|
|
||||||
client
|
|
||||||
server-poll-timeout 4
|
|
||||||
nobind
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 443 tcp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
remote vpn.analytium.co.uk 1194 udp
|
|
||||||
dev tun
|
|
||||||
dev-type tun
|
|
||||||
ns-cert-type server
|
|
||||||
setenv opt tls-version-min 1.0 or-highest
|
|
||||||
reneg-sec 604800
|
|
||||||
sndbuf 0
|
|
||||||
rcvbuf 0
|
|
||||||
# NOTE: LZO commands are pushed by the Access Server at connect time.
|
|
||||||
# NOTE: The below line doesn't disable LZO.
|
|
||||||
comp-lzo no
|
|
||||||
verb 3
|
|
||||||
setenv PUSH_PEER_INFO
|
|
||||||
|
|
||||||
ca ca.crt
|
|
||||||
cert user.crt
|
|
||||||
key user.key
|
|
||||||
tls-auth tls.key 1
|
|
||||||
27
.github/workflows/run-tests.yml
vendored
27
.github/workflows/run-tests.yml
vendored
@@ -21,31 +21,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Write VPN Files
|
|
||||||
run: |
|
|
||||||
echo "$CA_CRT" > .github/vpn/ca.crt
|
|
||||||
echo "$USER_CRT" > .github/vpn/user.crt
|
|
||||||
echo "$USER_KEY" > .github/vpn/user.key
|
|
||||||
echo "$TLS_KEY" > .github/vpn/tls.key
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
CA_CRT: ${{ secrets.CA_CRT}}
|
|
||||||
USER_CRT: ${{ secrets.USER_CRT }}
|
|
||||||
USER_KEY: ${{ secrets.USER_KEY }}
|
|
||||||
TLS_KEY: ${{ secrets.TLS_KEY }}
|
|
||||||
|
|
||||||
- name: Install Open VPN
|
|
||||||
run: |
|
|
||||||
sudo apt install apt-transport-https
|
|
||||||
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
|
|
||||||
sudo apt-key add openvpn-repo-pkg-key.pub
|
|
||||||
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install openvpn3
|
|
||||||
|
|
||||||
- name: Start Open VPN 3
|
|
||||||
run: openvpn3 session-start --config .github/vpn/config.ovpn
|
|
||||||
|
|
||||||
- name: Install Doxygen
|
- name: Install Doxygen
|
||||||
run: sudo apt-get install doxygen
|
run: sudo apt-get install doxygen
|
||||||
|
|
||||||
@@ -78,7 +53,5 @@ jobs:
|
|||||||
SECRET: ${{secrets.SECRET}}
|
SECRET: ${{secrets.SECRET}}
|
||||||
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
||||||
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
||||||
SERVER_URL: ${{secrets.SERVER_URL}}
|
|
||||||
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
|
|
||||||
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
||||||
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
tasks:
|
tasks:
|
||||||
- init: |
|
- init: npm install -g npm
|
||||||
nvm install --lts
|
- command: npm i
|
||||||
npm i -g @sasjs/cli
|
- command: npm i -g @sasjs/cli
|
||||||
|
|
||||||
image:
|
image:
|
||||||
file: .gitpod.dockerfile
|
file: .gitpod.dockerfile
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ sasjs/
|
|||||||
.github/
|
.github/
|
||||||
.git-hooks/
|
.git-hooks/
|
||||||
.vscode/
|
.vscode/
|
||||||
main.dox
|
|
||||||
make_singlefile.sh
|
make_singlefile.sh
|
||||||
*.md
|
*.md
|
||||||
.all-contributorsrc
|
.all-contributorsrc
|
||||||
|
|||||||
141
README.md
141
README.md
@@ -18,7 +18,7 @@
|
|||||||
[dependency-url]:https://github.com/sasjs/core/blob/main/package.json
|
[dependency-url]:https://github.com/sasjs/core/blob/main/package.json
|
||||||
|
|
||||||
|
|
||||||
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
|
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/.github/CONTRIBUTING.md) are welcome.
|
||||||
|
|
||||||
You can download and compile them all in just two lines of SAS code:
|
You can download and compile them all in just two lines of SAS code:
|
||||||
|
|
||||||
@@ -31,57 +31,34 @@ Documentation: https://core.sasjs.io
|
|||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
### BASE library (SAS9/Viya)
|
### BASE folder (All Platforms)
|
||||||
|
|
||||||
- OS independent
|
- OS independent
|
||||||
- Not metadata aware
|
- Works on all SAS Platforms
|
||||||
- No X command
|
- No X command
|
||||||
- Prefixes: _mf_, _mp_
|
- Prefixes: `mf_`, `mp_`
|
||||||
|
|
||||||
|
### DDL folder (All Platforms)
|
||||||
|
|
||||||
|
- OS independent
|
||||||
|
- Works on all SAS Platforms
|
||||||
|
- No X command
|
||||||
|
- Prefixes: `mddl_(lib)_` -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
|
||||||
|
|
||||||
|
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
|
||||||
|
|
||||||
|
### FCMP folder (All Platforms)
|
||||||
|
|
||||||
#### FCMP library (SAS9/Viya)
|
|
||||||
- Function and macro names are identical, except for special cases
|
- Function and macro names are identical, except for special cases
|
||||||
- Prefixes: _mcf_
|
- Prefixes: `mcf_`
|
||||||
|
|
||||||
The fcmp macros are used to generate fcmp functions, and can be used with or
|
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
|
||||||
without the `proc fcmp` wrapper.
|
|
||||||
|
|
||||||
### META library (SAS9 only)
|
### LUA folder
|
||||||
|
|
||||||
Macros used in SAS EBI, which connect to the metadata server.
|
|
||||||
|
|
||||||
- OS independent
|
|
||||||
- Metadata aware
|
|
||||||
- No X command
|
|
||||||
- Prefixes: _mm_
|
|
||||||
|
|
||||||
### SERVER library (@sasjs/server only)
|
|
||||||
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
|
|
||||||
|
|
||||||
- OS independent
|
|
||||||
- @sasjs/server aware
|
|
||||||
- No X command
|
|
||||||
- Prefixes: _ms_
|
|
||||||
|
|
||||||
### VIYA library (Viya only)
|
|
||||||
|
|
||||||
Macros used for interfacing with SAS Viya.
|
|
||||||
|
|
||||||
- OS independent
|
|
||||||
- No X command
|
|
||||||
- Prefixes: _mv_, _mvf_
|
|
||||||
|
|
||||||
### METAX library (SAS9 only)
|
|
||||||
|
|
||||||
- OS specific
|
|
||||||
- Metadata aware
|
|
||||||
- X command enabled
|
|
||||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
|
||||||
|
|
||||||
### LUA library
|
|
||||||
|
|
||||||
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
|
Wait - this is a macro library - what is LUA doing here? Well, it is a little known fact that you CAN run LUA within a SAS Macro. It has to be written to a text file with a `.lua` extension, from where you can `%include` it. So, without using the `proc lua` wrapper.
|
||||||
|
|
||||||
To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert your LUA into a data step with put statements, and create the macro wrapper with a `ml_` prefix. You can then use your module in any program by running:
|
To contribute, simply write your freeform LUA in the LUA folder. Then run the `build.py`, which will convert all files with a ".lua" extension into a macro wrapper with an `ml_` prefix (embedding the necessary data step put statements). You can then use your module in any program by running:
|
||||||
|
|
||||||
```sas
|
```sas
|
||||||
/* compile the lua module */
|
/* compile the lua module */
|
||||||
@@ -95,16 +72,63 @@ endsubmit;
|
|||||||
run;
|
run;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Prefixes: `ml_`
|
||||||
|
|
||||||
|
### META folder (SAS9 only)
|
||||||
|
|
||||||
|
Macros used in SAS EBI, which connect to the metadata server.
|
||||||
|
|
||||||
|
- OS independent
|
||||||
|
- Metadata aware
|
||||||
|
- No X command
|
||||||
|
- Prefixes: `mm_`
|
||||||
|
|
||||||
|
### METAX folder (SAS9 only)
|
||||||
|
|
||||||
|
- OS specific
|
||||||
|
- Metadata aware
|
||||||
- X command enabled
|
- X command enabled
|
||||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
- Prefixes: `mmx_`
|
||||||
|
|
||||||
|
### SERVER folder (@sasjs/server only)
|
||||||
|
These macros are used for building applications using [@sasjs/server](https://server.sasjs.io) - an open source REST API for Desktop SAS.
|
||||||
|
|
||||||
|
- OS independent
|
||||||
|
- @sasjs/server aware
|
||||||
|
- No X command
|
||||||
|
- Prefixes: `ms_`
|
||||||
|
|
||||||
|
### VIYA folder (Viya only)
|
||||||
|
|
||||||
|
Macros used for interfacing with SAS Viya.
|
||||||
|
|
||||||
|
- OS independent
|
||||||
|
- No X command
|
||||||
|
- Prefixes: `mv_`, `mvf_`
|
||||||
|
|
||||||
|
### XPLATFORM folder (Viya, Meta, and Server)
|
||||||
|
|
||||||
|
Sometimes it is helpful to use a macro that can be used interchangeably regardless of the server type on which is is running (SASVIYA, SAS9, SASJS).
|
||||||
|
|
||||||
|
- OS independent
|
||||||
|
- No X command
|
||||||
|
- Prefixes: `mx_`
|
||||||
|
|
||||||
## Installation
|
## 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");
|
%let repoloc=/your/path/core;
|
||||||
options insert=(sasautos="/your/path/macrocore/meta");
|
options insert=(sasautos="&repoloc/base");
|
||||||
|
options insert=(sasautos="&repoloc/ddl");
|
||||||
|
options insert=(sasautos="&repoloc/fcmp");
|
||||||
|
options insert=(sasautos="&repoloc/lua");
|
||||||
|
options insert=(sasautos="&repoloc/meta");
|
||||||
|
options insert=(sasautos="&repoloc/metax");
|
||||||
|
options insert=(sasautos="&repoloc/server");
|
||||||
|
options insert=(sasautos="&repoloc/viya");
|
||||||
|
options insert=(sasautos="&repoloc/xplatform");
|
||||||
```
|
```
|
||||||
|
|
||||||
The above can be done directly in your sas program, via an autoexec, or an initialisation program.
|
The above can be done directly in your sas program, via an autoexec, or an initialisation program.
|
||||||
@@ -126,14 +150,15 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
|||||||
- one macro per file
|
- one macro per file
|
||||||
- prefixes:
|
- prefixes:
|
||||||
- _mcf_ for macro compiled functions (proc fcmp)
|
- _mcf_ for macro compiled functions (proc fcmp)
|
||||||
|
- _mddl_ for macros containing DDL (Data Definition Language)
|
||||||
- _mf_ for macro functions (can be used in open code).
|
- _mf_ for macro functions (can be used in open code).
|
||||||
- _ml_ for macros that are used to compile LUA modules
|
- _ml_ for macros that are used to compile LUA modules
|
||||||
- _mm_ for metadata macros (interface with the metadata server).
|
- _mm_ for metadata macros (interface with the metadata server).
|
||||||
- _mmx_ for macros that use metadata and are XCMD enabled
|
- _mmx_ for macros that use metadata and are XCMD enabled (working on both windows and unix)
|
||||||
- _mp_ for macro procedures (which generate sas code)
|
- _mp_ for macro procedures (which generate sas code)
|
||||||
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
|
- _ms_ for macro procedures that will only work with [@sasjs/server](https://github.com/sasjs/server)
|
||||||
- _mv_ for macro procedures that will only work in Viya
|
- _mv_ for macro procedures that will only work in Viya
|
||||||
- _mx_ for macros that are XCMD enabled
|
- _mx_ for macros that work on Viya, SAS 9 EBI and SASjs Server
|
||||||
- follow verb-noun convention
|
- follow verb-noun convention
|
||||||
- unix style line endings (lf)
|
- unix style line endings (lf)
|
||||||
- individual lines should be no more than 80 characters long
|
- individual lines should be no more than 80 characters long
|
||||||
@@ -193,11 +218,14 @@ When contributing to this library, it is therefore important to ensure that all
|
|||||||
|
|
||||||
## Breaking Changes
|
## Breaking Changes
|
||||||
|
|
||||||
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major (breaking) release becomes necessary:
|
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major/breaking release (v5) becomes necessary:
|
||||||
|
|
||||||
|
* mf_getuniquelibref.sas to have the deprecated maxtried parameter removed (no longer needed)
|
||||||
* mp_testservice.sas to be renamed as mp_execute.sas (as it doesn't actually test anything)
|
* mp_testservice.sas to be renamed as mp_execute.sas (as it doesn't actually test anything)
|
||||||
* `insert_cmplib` option of mcf_xxx macros will be deprecated (the option is now checked automatically with value inserted only if needed)
|
* `insert_cmplib` option of mcf_xxx macros will be deprecated (the option is now checked automatically with value inserted only if needed)
|
||||||
* mcf_xxx macros to have `wrap=` option defaulted to YES for convenience
|
* mcf_xxx macros to have `wrap=` option defaulted to YES for convenience. Set this option explicitly to avoid issues.
|
||||||
|
* mp_getddl.sas to be renamed to mp_ds2ddl.sas (consistent with other ds2xxx macros). A wrapper macro is already in place, and you are able to use this immediately. The default for SHOWLOG will also be YES instead of NO.
|
||||||
|
* mp_coretable.sas will be replaced by the standalone macros in the `ddl` folder (which are already available)
|
||||||
|
|
||||||
## Star Gazing
|
## Star Gazing
|
||||||
|
|
||||||
@@ -205,11 +233,22 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Other SAS Repositories
|
||||||
|
|
||||||
|
The following repositories are also worth checking out:
|
||||||
|
|
||||||
|
* [xieliaing/SAS](https://github.com/xieliaing/SAS)
|
||||||
|
* [SASJedi/sas-macros](https://github.com/SASJedi/sas-macros)
|
||||||
|
* [chris-swenson/sasmacros](https://github.com/chris-swenson/sasmacros)
|
||||||
|
* [greg-wotton/sas-programs](https://github.com/greg-wootton/sas-programs)
|
||||||
|
* [KatjaGlassConsulting/SMILE-SmartSASMacros](https://github.com/KatjaGlassConsulting/SMILE-SmartSASMacros)
|
||||||
|
* [rogerjdeangelis](https://github.com/rogerjdeangelis)
|
||||||
|
* [scottbass/sas](https://github.com/scottbass/SAS)
|
||||||
|
* [yabwon/sas_packages](https://github.com/yabwon/SAS_PACKAGES)
|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[](#contributors-)
|
[](#contributors-)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
@@ -230,6 +269,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<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>
|
<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>
|
||||||
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
||||||
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
|
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
31
base/mf_deletefile.sas
Normal file
31
base/mf_deletefile.sas
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Deletes a physical file, if it exists
|
||||||
|
@details Usage:
|
||||||
|
|
||||||
|
%mf_writefile(&sasjswork/myfile.txt,l1=some content)
|
||||||
|
|
||||||
|
%mf_deletefile(&sasjswork/myfile.txt)
|
||||||
|
|
||||||
|
%mf_deletefile(&sasjswork/myfile.txt)
|
||||||
|
|
||||||
|
|
||||||
|
@param filepath Full path to the target file
|
||||||
|
|
||||||
|
@returns The return code from the fdelete() invocation
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_deletefile.test.sas
|
||||||
|
@li mf_writefile.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mf_deletefile(file
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
%local rc fref;
|
||||||
|
%let rc= %sysfunc(filename(fref,&file));
|
||||||
|
%if %sysfunc(fdelete(&fref)) ne 0 %then %put %sysfunc(sysmsg());
|
||||||
|
%let rc= %sysfunc(filename(fref));
|
||||||
|
%mend mf_deletefile;
|
||||||
@@ -9,19 +9,17 @@
|
|||||||
|
|
||||||
%put %mf_existfeature(PROCLUA);
|
%put %mf_existfeature(PROCLUA);
|
||||||
|
|
||||||
@param feature the feature to detect. Leave blank to list all in log.
|
@param [in] feature The feature to detect.
|
||||||
|
|
||||||
@return output returns 1 or 0 (or -1 if not found)
|
@return output returns 1 or 0 (or -1 if not found)
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getplatform.sas
|
@li mf_getplatform.sas
|
||||||
|
|
||||||
|
|
||||||
@version 8
|
@version 8
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
/** @cond */
|
/** @cond */
|
||||||
|
|
||||||
%macro mf_existfeature(feature
|
%macro mf_existfeature(feature
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%let feature=%upcase(&feature);
|
%let feature=%upcase(&feature);
|
||||||
@@ -29,7 +27,11 @@
|
|||||||
%let platform=%mf_getplatform();
|
%let platform=%mf_getplatform();
|
||||||
|
|
||||||
%if &feature= %then %do;
|
%if &feature= %then %do;
|
||||||
%put Supported features: PROCLUA;
|
%put No feature was requested for detection;
|
||||||
|
%end;
|
||||||
|
%else %if &feature=COLCONSTRAINTS %then %do;
|
||||||
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
|
||||||
|
%else 1;
|
||||||
%end;
|
%end;
|
||||||
%else %if &feature=PROCLUA %then %do;
|
%else %if &feature=PROCLUA %then %do;
|
||||||
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
||||||
@@ -38,10 +40,20 @@
|
|||||||
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
||||||
%else 1;
|
%else 1;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &feature=DBMS_MEMTYPE %then %do;
|
||||||
|
/* does dbms_memtype exist in dictionary.tables? */
|
||||||
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 0;
|
||||||
|
%else 1;
|
||||||
|
%end;
|
||||||
|
%else %if &feature=EXPORTXLS %then %do;
|
||||||
|
/* is it possible to PROC EXPORT an excel file? */
|
||||||
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then 1;
|
||||||
|
%else %if %sysfunc(sysprod(SAS/ACCESS Interface to PC Files)) = 1 %then 1;
|
||||||
|
%else 0;
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
-1
|
-1
|
||||||
%put &sysmacroname: &feature not found;
|
%put &sysmacroname: &feature not found;
|
||||||
%end;
|
%end;
|
||||||
%mend mf_existfeature;
|
%mend mf_existfeature;
|
||||||
|
|
||||||
/** @endcond */
|
/** @endcond */
|
||||||
@@ -6,9 +6,6 @@
|
|||||||
|
|
||||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
|
||||||
@li mf_abort.sas
|
|
||||||
|
|
||||||
@param libds 2 part dataset or view reference
|
@param libds 2 part dataset or view reference
|
||||||
@param varlist space separated variable names
|
@param varlist space separated variable names
|
||||||
|
|
||||||
|
|||||||
42
base/mf_fmtdttm.sas
Normal file
42
base/mf_fmtdttm.sas
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Returns E8601DT26.6 if compatible else DATETIME19.3
|
||||||
|
@details From our experience in [Data Controller for SAS]
|
||||||
|
(https://datacontroller.io) deployments, the E8601DT26.6 datetime format has
|
||||||
|
the widest support when it comes to pass-through SQL queries.
|
||||||
|
|
||||||
|
However, it is not supported in WPS or early versions of SAS 9 (M3 and below)
|
||||||
|
when used as a datetime literal, eg:
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
demo="%sysfunc(datetime(),E8601DT26.6)"dt;
|
||||||
|
demo=;
|
||||||
|
run;
|
||||||
|
|
||||||
|
This macro will therefore return DATEITME19.3 as an alternative format
|
||||||
|
based on the runtime environment so that it can be used in such cases, eg:
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
demo="%sysfunc(datetime(),%mf_fmtdttm())"dt;
|
||||||
|
demo=;
|
||||||
|
run;
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_fmtdttm.test.sas
|
||||||
|
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mf_fmtdttm(
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%if "&sysver"="9.2" or "&sysver"="9.3"
|
||||||
|
or ("&sysver"="9.4" and "%substr(&SYSVLONG,9,1)" le "3")
|
||||||
|
or "%substr(&sysver,1,1)"="4"
|
||||||
|
or "%substr(&sysver,1,1)"="5"
|
||||||
|
%then %do;DATETIME19.3%end;
|
||||||
|
%else %do;E8601DT26.6%end;
|
||||||
|
|
||||||
|
%mend mf_fmtdttm;
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
@li /data
|
@li /data
|
||||||
@li /jobs
|
@li /jobs
|
||||||
@li /services
|
@li /services
|
||||||
|
@li /tests
|
||||||
@li /tests/jobs
|
@li /tests/jobs
|
||||||
@li /tests/services
|
@li /tests/services
|
||||||
@li /tests/macros
|
@li /tests/macros
|
||||||
@@ -46,9 +47,13 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* First check we are not in the tests/macros folder (which has no subfolders)
|
* First check we are not in the tests/macros folder (which has no subfolders)
|
||||||
|
* or specifically in the testsetup or testteardown services
|
||||||
*/
|
*/
|
||||||
%if %index(&pgm,/tests/macros/) %then %do;
|
%if %index(&pgm,/tests/macros/)
|
||||||
%let root=%substr(&pgm,1,%index(&pgm,/tests/macros)-1);
|
or %index(&pgm,/tests/testsetup)
|
||||||
|
or %index(&pgm,/tests/testteardown)
|
||||||
|
%then %do;
|
||||||
|
%let root=%substr(&pgm,1,%index(&pgm,/tests)-1);
|
||||||
&root
|
&root
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
|
|
||||||
%put %mf_getplatform();
|
%put %mf_getplatform();
|
||||||
|
|
||||||
returns:
|
returns one of:
|
||||||
SASMETA (or SASVIYA)
|
|
||||||
|
@li SASMETA
|
||||||
|
@li SASVIYA
|
||||||
|
@li SASJS
|
||||||
|
@li BASESAS
|
||||||
|
|
||||||
@param switch the param for which to return a platform specific variable
|
@param switch the param for which to return a platform specific variable
|
||||||
|
|
||||||
@@ -22,6 +26,12 @@
|
|||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local a b c;
|
%local a b c;
|
||||||
%if &switch.NONE=NONE %then %do;
|
%if &switch.NONE=NONE %then %do;
|
||||||
|
%if %symexist(sasjsprocessmode) %then %do;
|
||||||
|
%if &sasjsprocessmode=Stored Program %then %do;
|
||||||
|
SASJS
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
%if %symexist(sysprocessmode) %then %do;
|
%if %symexist(sysprocessmode) %then %do;
|
||||||
%if "&sysprocessmode"="SAS Object Server"
|
%if "&sysprocessmode"="SAS Object Server"
|
||||||
or "&sysprocessmode"= "SAS Compute Server" %then %do;
|
or "&sysprocessmode"= "SAS Compute Server" %then %do;
|
||||||
|
|||||||
@@ -28,15 +28,17 @@
|
|||||||
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
|
be 8 characters, so a 7 letter prefix would mean `maxtries` should be 10.
|
||||||
if using zero (0) as the prefix, a native assignment is used.
|
if using zero (0) as the prefix, a native assignment is used.
|
||||||
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
|
@param [in] maxtries= (1000) the last part of the libref. Must be an integer.
|
||||||
|
@param [in] lrecl= (32767) Provide a default lrecl with which to initialise
|
||||||
|
the generated fileref.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_getuniquefileref(prefix=_,maxtries=1000);
|
%macro mf_getuniquefileref(prefix=_,maxtries=1000,lrecl=32767);
|
||||||
%local rc fname;
|
%local rc fname;
|
||||||
%if &prefix=0 %then %do;
|
%if &prefix=0 %then %do;
|
||||||
%let rc=%sysfunc(filename(fname,,temp));
|
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
|
||||||
%if &rc %then %put %sysfunc(sysmsg());
|
%if &rc %then %put %sysfunc(sysmsg());
|
||||||
&fname
|
&fname
|
||||||
%end;
|
%end;
|
||||||
@@ -47,7 +49,7 @@
|
|||||||
%do x=0 %to &maxtries;
|
%do x=0 %to &maxtries;
|
||||||
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
|
%let fname=&prefix%substr(%sysfunc(ranuni(0)),3,&len);
|
||||||
%if %sysfunc(fileref(&fname)) > 0 %then %do;
|
%if %sysfunc(fileref(&fname)) > 0 %then %do;
|
||||||
%let rc=%sysfunc(filename(fname,,temp));
|
%let rc=%sysfunc(filename(fname,,temp,lrecl=&lrecl));
|
||||||
%if &rc %then %put %sysfunc(sysmsg());
|
%if &rc %then %put %sysfunc(sysmsg());
|
||||||
&fname
|
&fname
|
||||||
%return;
|
%return;
|
||||||
|
|||||||
@@ -14,27 +14,44 @@
|
|||||||
|
|
||||||
> mclib3
|
> mclib3
|
||||||
|
|
||||||
@param prefix= first part of libref. Remember that librefs can only be 8 characters,
|
A blank value is returned if no usable libname is determined.
|
||||||
so a 7 letter prefix would mean that maxtries should be 10.
|
|
||||||
@param maxtries= the last part of the libref. Provide an integer value.
|
@param [in] prefix= (mclib) first part of the returned libref. As librefs can
|
||||||
|
be as long as 8 characters, a maximum length of 7 characters is premitted
|
||||||
|
for this prefix.
|
||||||
|
@param [in] maxtries= Deprecated parameter. Remains here to ensure a
|
||||||
|
non-breaking change. Will be removed in v5.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
|
||||||
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
%macro mf_getuniquelibref(prefix=mclib,maxtries=1000);
|
||||||
%local x libref;
|
%local x;
|
||||||
%let x=0;
|
|
||||||
%do x=0 %to &maxtries;
|
%if ( %length(&prefix) gt 7 ) %then %do;
|
||||||
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
%put %str(ERR)OR: The prefix parameter cannot exceed 7 characters.;
|
||||||
%let libref=&prefix&x;
|
0
|
||||||
%let rc=%sysfunc(libname(&libref,%sysfunc(pathname(work))));
|
|
||||||
%if &rc %then %put %sysfunc(sysmsg());
|
|
||||||
&prefix&x
|
|
||||||
%*put &sysmacroname: Libref &libref assigned as WORK and returned;
|
|
||||||
%return;
|
%return;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if (%sysfunc(NVALID(&prefix,v7))=0) %then %do;
|
||||||
|
%put %str(ERR)OR: Invalid prefix (&prefix);
|
||||||
|
0
|
||||||
|
%return;
|
||||||
%end;
|
%end;
|
||||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
|
||||||
|
/* Set maxtries equal to '10 to the power of [# unused characters] - 1' */
|
||||||
|
%let maxtries=%eval(10**(8-%length(&prefix))-1);
|
||||||
|
|
||||||
|
%do x = 0 %to &maxtries;
|
||||||
|
%if %sysfunc(libref(&prefix&x)) ne 0 %then %do;
|
||||||
|
&prefix&x
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%let x = %eval(&x + 1);
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%put %str(ERR)OR: No usable libref in range &prefix.0-&maxtries;
|
||||||
|
%put %str(ERR)OR- Try reducing the prefix or deleting some libraries!;
|
||||||
|
0
|
||||||
%mend mf_getuniquelibref;
|
%mend mf_getuniquelibref;
|
||||||
@@ -23,17 +23,19 @@
|
|||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_getuser(type=META
|
%macro mf_getuser(
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local user metavar;
|
%local user;
|
||||||
%if &type=OS %then %let metavar=_secureusername;
|
|
||||||
%else %let metavar=_metaperson;
|
|
||||||
|
|
||||||
%if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER;
|
%if %symexist(_sasjs_username) %then %let user=&_sasjs_username;
|
||||||
%else %if %symexist(&metavar) %then %do;
|
%else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do;
|
||||||
%if %length(&&&metavar)=0 %then %let user=&sysuserid;
|
%let user=&SYS_COMPUTE_SESSION_OWNER;
|
||||||
|
%end;
|
||||||
|
%else %if %symexist(_metaperson) %then %do;
|
||||||
|
%if %length(&_metaperson)=0 %then %let user=&sysuserid;
|
||||||
/* sometimes SAS will add @domain extension - remove for consistency */
|
/* sometimes SAS will add @domain extension - remove for consistency */
|
||||||
%else %let user=%scan(&&&metavar,1,@);
|
/* but be sure to quote in case of usernames with commas */
|
||||||
|
%else %let user=%unquote(%scan(%quote(&_metaperson),1,@));
|
||||||
%end;
|
%end;
|
||||||
%else %let user=&sysuserid;
|
%else %let user=&sysuserid;
|
||||||
|
|
||||||
|
|||||||
@@ -2,31 +2,48 @@
|
|||||||
@file
|
@file
|
||||||
@brief Returns number of variables in a dataset
|
@brief Returns number of variables in a dataset
|
||||||
@details Useful to identify those renagade datasets that have no columns!
|
@details Useful to identify those renagade datasets that have no columns!
|
||||||
|
Can also be used to count for numeric, or character columns
|
||||||
|
|
||||||
%put Number of Variables=%mf_getvarcount(sashelp.class);
|
%put Number of Variables=%mf_getvarcount(sashelp.class);
|
||||||
|
%put Character Variables=%mf_getvarcount(sashelp.class,typefilter=C);
|
||||||
|
%put Numeric Variables = %mf_getvarcount(sashelp.class,typefilter=N);
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
> Number of Variables=4
|
> Number of Variables=4
|
||||||
|
|
||||||
@param libds Two part dataset (or view) reference.
|
|
||||||
|
@param [in] libds Two part dataset (or view) reference.
|
||||||
|
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
|
||||||
|
@li A Count All columns
|
||||||
|
@li C Count Character columns only
|
||||||
|
@li N Count Numeric columns only
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mf_getvarcount(libds
|
%macro mf_getvarcount(libds,typefilter=A
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local dsid nvars rc ;
|
%local dsid nvars rc outcnt x;
|
||||||
%let dsid=%sysfunc(open(&libds));
|
%let dsid=%sysfunc(open(&libds));
|
||||||
%let nvars=.;
|
%let nvars=.;
|
||||||
|
%let outcnt=0;
|
||||||
|
%let typefilter=%upcase(&typefilter);
|
||||||
%if &dsid %then %do;
|
%if &dsid %then %do;
|
||||||
%let nvars=%sysfunc(attrn(&dsid,NVARS));
|
%let nvars=%sysfunc(attrn(&dsid,NVARS));
|
||||||
|
%if &typefilter=A %then %let outcnt=&nvars;
|
||||||
|
%else %if &nvars>0 %then %do x=1 %to &nvars;
|
||||||
|
/* increment based on variable type */
|
||||||
|
%if %sysfunc(vartype(&dsid,&x))=&typefilter %then %do;
|
||||||
|
%let outcnt=%eval(&outcnt+1);
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
%let rc=%sysfunc(close(&dsid));
|
%let rc=%sysfunc(close(&dsid));
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%put unable to open &libds (rc=&dsid);
|
%put unable to open &libds (rc=&dsid);
|
||||||
%let rc=%sysfunc(close(&dsid));
|
%let rc=%sysfunc(close(&dsid));
|
||||||
%end;
|
%end;
|
||||||
&nvars
|
&outcnt
|
||||||
%mend mf_getvarcount;
|
%mend mf_getvarcount;
|
||||||
29
base/mf_increment.sas
Normal file
29
base/mf_increment.sas
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Increments a macro variable
|
||||||
|
@details Useful outside of do-loops - will increment a macro variable every
|
||||||
|
time it is called.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
%let cnt=1;
|
||||||
|
%put We have run %mf_increment(cnt) lines;
|
||||||
|
%put Now we have run %mf_increment(cnt) lines;
|
||||||
|
%put There are %mf_increment(cnt) lines in total;
|
||||||
|
|
||||||
|
@param [in] MACRO_NAME the name of the macro variable to increment
|
||||||
|
@param [in] ITER= The amount to add or subtract to the macro
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mf_increment.test.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mf_increment(macro_name,incr=1);
|
||||||
|
|
||||||
|
/* iterate the value */
|
||||||
|
%let ¯o_name=%eval(&&¯o_name+&incr);
|
||||||
|
/* return the value */
|
||||||
|
&&¯o_name
|
||||||
|
|
||||||
|
%mend mf_increment;
|
||||||
@@ -20,8 +20,11 @@
|
|||||||
|
|
||||||
%macro mf_isint(arg
|
%macro mf_isint(arg
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
/* remove minus sign if exists */
|
|
||||||
|
|
||||||
|
/* blank val is not an integer */
|
||||||
|
%if "&arg"="" %then %do;0%return;%end;
|
||||||
|
|
||||||
|
/* remove minus sign if exists */
|
||||||
%local val;
|
%local val;
|
||||||
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
|
%if "%substr(%str(&arg),1,1)"="-" %then %let val=%substr(%str(&arg),2);
|
||||||
%else %let val=&arg;
|
%else %let val=&arg;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Checks if a set of macro variables exist / contain values.
|
@brief Checks if a set of macro variables exist AND contain values.
|
||||||
@details Writes ERROR to log if abortType is SOFT, else will call %mf_abort.
|
@details Writes ERROR to log if abortType is SOFT, else will call %mf_abort.
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@@ -14,10 +14,11 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_abort.sas
|
@li mf_abort.sas
|
||||||
|
|
||||||
@param verifyvars space separated list of macro variable names
|
@param [in] verifyvars Space separated list of macro variable names
|
||||||
@param makeupcase= set to YES to convert all variable VALUES to
|
@param [in] makeupcase= (NO) Set to YES to convert all variable VALUES to
|
||||||
uppercase.
|
uppercase.
|
||||||
@param mAbort= Abort Type. Default is SOFT (writes err to log).
|
@param [in] mAbort= (SOFT) Abort Type. When SOFT, simply writes an err
|
||||||
|
message to the log.
|
||||||
Set to any other value to call mf_abort (which can be configured to abort in
|
Set to any other value to call mf_abort (which can be configured to abort in
|
||||||
various fashions according to context).
|
various fashions according to context).
|
||||||
|
|
||||||
@@ -58,8 +59,14 @@
|
|||||||
|
|
||||||
%goto exit_success;
|
%goto exit_success;
|
||||||
%exit_err:
|
%exit_err:
|
||||||
%if &mAbort=SOFT %then %put %str(ERR)OR: &abortmsg;
|
%put &abortmsg;
|
||||||
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
|
%mf_abort(iftrue=(&mabort ne SOFT),
|
||||||
|
mac=mf_verifymacvars,
|
||||||
|
msg=%str(&abortmsg)
|
||||||
|
)
|
||||||
|
0
|
||||||
|
%return;
|
||||||
%exit_success:
|
%exit_success:
|
||||||
|
1
|
||||||
|
|
||||||
%mend mf_verifymacvars;
|
%mend mf_verifymacvars;
|
||||||
|
|||||||
@@ -8,23 +8,29 @@
|
|||||||
|
|
||||||
The method used varies according to the context. Important points:
|
The method used varies according to the context. Important points:
|
||||||
|
|
||||||
@li should not use endsas or abort cancel in 9.4m3 environments as this can
|
@li should not use endsas or abort cancel in 9.4m3 WIN environments as this
|
||||||
cause hung multibridge sessions and result in a frozen STP server
|
can cause hung multibridge sessions and result in a frozen STP server
|
||||||
|
@li The use of endsas in 9.4m6+ windows environments for POST requests to the
|
||||||
|
STP server can result in an empty response body
|
||||||
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
||||||
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
||||||
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. We take a unique "soft abort" approach - we open a macro
|
and set SYSCC=0.
|
||||||
|
Where possible, we take a unique "soft abort" approach - we open a macro
|
||||||
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
but don't close it! This works everywhere EXCEPT inside a \%include inside
|
||||||
a macro. For that, we recommend you use mp_include.sas to perform the
|
a macro. For that, we recommend you use mp_include.sas to perform the
|
||||||
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
|
||||||
OUTSIDE of the top-parent macro).
|
OUTSIDE of the top-parent macro).
|
||||||
|
The soft abort has become ineffective in 9.4m6 WINDOWS environments. We are
|
||||||
|
currently investigating approaches to deal with this.
|
||||||
|
|
||||||
|
|
||||||
@param mac= to contain the name of the calling macro
|
@param mac= (mp_abort.sas) To contain the name of the calling macro. Do not
|
||||||
|
use &sysmacroname as this will always resolve to MP_ABORT.
|
||||||
@param msg= message to be returned
|
@param msg= message to be returned
|
||||||
@param iftrue= supply a condition under which the macro should be executed.
|
@param iftrue= (1=1) Supply a condition for which the macro should be executed
|
||||||
@param errds= (work.mp_abort_errds) There is no clean way to end a process
|
@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
|
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
|
test if a macro is called within a %include. To handle this particular
|
||||||
@@ -45,11 +51,12 @@
|
|||||||
@li REGULAR (default)
|
@li REGULAR (default)
|
||||||
@li INCLUDE
|
@li INCLUDE
|
||||||
|
|
||||||
|
@version 9.4
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_include.sas
|
@li mp_include.sas
|
||||||
|
|
||||||
@version 9.4
|
|
||||||
@author Allan Bowe
|
|
||||||
@cond
|
@cond
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@@ -58,7 +65,8 @@
|
|||||||
, mode=REGULAR
|
, mode=REGULAR
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%global sysprocessmode sysprocessname;
|
%global sysprocessmode sysprocessname sasjs_stpsrv_header_loc sasjsprocessmode;
|
||||||
|
%local fref fid i;
|
||||||
|
|
||||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
@@ -68,7 +76,7 @@
|
|||||||
|
|
||||||
%if %symexist(_SYSINCLUDEFILEDEVICE)
|
%if %symexist(_SYSINCLUDEFILEDEVICE)
|
||||||
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
|
/* abort cancel FILE does not restart outside the INCLUDE on Viya 3.5 */
|
||||||
and "&SYSPROCESSNAME " ne "Compute Server "
|
and %superq(SYSPROCESSNAME) ne %str(Compute Server)
|
||||||
%then %do;
|
%then %do;
|
||||||
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
|
||||||
data &errds;
|
data &errds;
|
||||||
@@ -84,12 +92,17 @@
|
|||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
/* Stored Process Server web app context */
|
/* Web App Context */
|
||||||
%if %symexist(_metaperson)
|
%if %symexist(_PROGRAM)
|
||||||
or "&SYSPROCESSNAME "="Compute Server "
|
or %superq(SYSPROCESSNAME) = %str(Compute Server)
|
||||||
or &mode=INCLUDE
|
or &mode=INCLUDE
|
||||||
%then %do;
|
%then %do;
|
||||||
options obs=max replace nosyntaxcheck mprint;
|
options obs=max replace mprint;
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5"
|
||||||
|
%then %do;
|
||||||
|
options nosyntaxcheck;
|
||||||
|
%end;
|
||||||
|
|
||||||
%if &mode=INCLUDE %then %do;
|
%if &mode=INCLUDE %then %do;
|
||||||
%if %sysfunc(exist(&errds))=1 %then %do;
|
%if %sysfunc(exist(&errds))=1 %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -147,8 +160,8 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||||
/* setup webout */
|
/* setup webout for Viya */
|
||||||
OPTIONS NOBOMFILE;
|
options nobomfile;
|
||||||
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
||||||
filename _webout temp lrecl=999999 mod;
|
filename _webout temp lrecl=999999 mod;
|
||||||
%end;
|
%end;
|
||||||
@@ -157,16 +170,31 @@
|
|||||||
name="_webout.json" lrecl=999999 mod;
|
name="_webout.json" lrecl=999999 mod;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
%else %if %sysfunc(filename(fref,&sasjs_stpsrv_header_loc))=0 %then %do;
|
||||||
|
options nobomfile;
|
||||||
|
/* set up http header for SASjs Server */
|
||||||
|
%let fid=%sysfunc(fopen(&fref,A));
|
||||||
|
%if &fid=0 %then %do;
|
||||||
|
%put %str(ERR)OR: %sysfunc(sysmsg());
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%let rc=%sysfunc(fput(&fid,%str(Content-Type: application/json)));
|
||||||
|
%let rc=%sysfunc(fwrite(&fid));
|
||||||
|
%let rc=%sysfunc(fclose(&fid));
|
||||||
|
%let rc=%sysfunc(filename(&fref));
|
||||||
|
%end;
|
||||||
|
|
||||||
/* send response in SASjs JSON format */
|
/* 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 ;
|
length msg syswarningtext syserrortext $32767 mode $10 ;
|
||||||
sasdatetime=datetime();
|
sasdatetime=datetime();
|
||||||
msg=symget('msg');
|
msg=symget('msg');
|
||||||
%if &logline>0 %then %do;
|
%if &logline>0 %then %do;
|
||||||
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
msg=cats(msg,'\n\nLog Extract:\n',symget('logmsg'));
|
||||||
%end;
|
%end;
|
||||||
|
/* escape the escapes */
|
||||||
|
msg=tranwrd(msg,'\','\\');
|
||||||
/* escape the quotes */
|
/* escape the quotes */
|
||||||
msg=tranwrd(msg,'"','\"');
|
msg=tranwrd(msg,'"','\"');
|
||||||
/* ditch the CRLFs as chrome complains */
|
/* ditch the CRLFs as chrome complains */
|
||||||
@@ -175,7 +203,8 @@
|
|||||||
msg=cats('"',msg,'"');
|
msg=cats('"',msg,'"');
|
||||||
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
if symexist('_debug') then debug=quote(trim(symget('_debug')));
|
||||||
else debug='""';
|
else debug='""';
|
||||||
put '>>weboutBEGIN<<';
|
if symget('sasjsprocessmode')='Stored Program' then mode='SASJS';
|
||||||
|
if mode ne 'SASJS' then put '>>weboutBEGIN<<';
|
||||||
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
put '{"SYSDATE" : "' "&SYSDATE" '"';
|
||||||
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
put ',"SYSTIME" : "' "&SYSTIME" '"';
|
||||||
put ',"sasjsAbort" : [{';
|
put ',"sasjsAbort" : [{';
|
||||||
@@ -196,7 +225,7 @@
|
|||||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||||
put ",""SYSCC"" : ""&syscc"" ";
|
put ",""SYSCC"" : ""&syscc"" ";
|
||||||
syserrortext=quote(trim(symget('syserrortext')));
|
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
|
||||||
put ",""SYSERRORTEXT"" : " syserrortext;
|
put ",""SYSERRORTEXT"" : " syserrortext;
|
||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
@@ -204,11 +233,11 @@
|
|||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
syswarningtext=quote(trim(symget('syswarningtext')));
|
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
|
||||||
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
put ",""SYSWARNINGTEXT"" : " syswarningtext;
|
||||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
||||||
put "}" @;
|
put "}" ;
|
||||||
put '>>weboutEND<<';
|
if mode ne 'SASJS' then put '>>weboutEND<<';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%put _all_;
|
%put _all_;
|
||||||
@@ -219,6 +248,14 @@
|
|||||||
rc=stpsrvset('program error', 0);
|
rc=stpsrvset('program error', 0);
|
||||||
call symputx("syscc",0,"g");
|
call symputx("syscc",0,"g");
|
||||||
run;
|
run;
|
||||||
|
%if &sysscp=WIN
|
||||||
|
and 1=0 /* deprecating this logic until we figure out a consistent abort */
|
||||||
|
and "%substr(%str(&sysvlong ),1,8)"="9.04.01M"
|
||||||
|
and "%substr(%str(&sysvlong ),9,1)">"5" %then %do;
|
||||||
|
/* skip approach (below) does not work in windows m6+ envs */
|
||||||
|
endsas;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
/**
|
/**
|
||||||
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||||
* Abort variants are ungraceful (non zero return code)
|
* Abort variants are ungraceful (non zero return code)
|
||||||
@@ -236,6 +273,7 @@
|
|||||||
run;
|
run;
|
||||||
%inc skip;
|
%inc skip;
|
||||||
%end;
|
%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 */
|
||||||
data _null_;
|
data _null_;
|
||||||
|
|||||||
@@ -74,7 +74,8 @@
|
|||||||
outds=work.test_results
|
outds=work.test_results
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local ds test_result test_comments del add mod ilist;
|
%local ds test_result test_comments del add mod ilist;
|
||||||
%let ilist=%upcase(&sasjs_prefix._FUNCTIONS &ignorelist);
|
%let ilist=%upcase(&sasjs_prefix._FUNCTIONS SYS_PROCHTTP_STATUS_CODE
|
||||||
|
SYS_PROCHTTP_STATUS_CODE SYS_PROCHTTP_STATUS_PHRASE &ignorelist);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this sets up the global vars, it will also enter STRICT mode. If this
|
* this sets up the global vars, it will also enter STRICT mode. If this
|
||||||
@@ -89,7 +90,7 @@
|
|||||||
create table &scopeds as
|
create table &scopeds as
|
||||||
select name,offset,value
|
select name,offset,value
|
||||||
from dictionary.macros
|
from dictionary.macros
|
||||||
where scope="&scope" and name not in (%mf_getquotedstr(&ilist))
|
where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
|
||||||
order by name,offset;
|
order by name,offset;
|
||||||
%end;
|
%end;
|
||||||
%else %if &action=COMPARE %then %do;
|
%else %if &action=COMPARE %then %do;
|
||||||
@@ -98,12 +99,14 @@
|
|||||||
create table _data_ as
|
create table _data_ as
|
||||||
select name,offset,value
|
select name,offset,value
|
||||||
from dictionary.macros
|
from dictionary.macros
|
||||||
where scope="&scope" and name not in (%mf_getquotedstr(&ilist))
|
where scope="&scope" and upcase(name) not in (%mf_getquotedstr(&ilist))
|
||||||
order by name,offset;
|
order by name,offset;
|
||||||
|
|
||||||
%let ds=&syslast;
|
%let ds=&syslast;
|
||||||
|
|
||||||
proc compare base=&scopeds compare=&ds;
|
proc compare
|
||||||
|
base=&scopeds(where=(upcase(name) not in (%mf_getquotedstr(&ilist))))
|
||||||
|
compare=&ds noprint;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &sysinfo=0 %then %do;
|
%if &sysinfo=0 %then %do;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
@details Reads in a file byte by byte and writes it back out. Is an
|
@details Reads in a file byte by byte and writes it back out. Is an
|
||||||
os-independent method to copy files. In case of naming collision, the
|
os-independent method to copy files. In case of naming collision, the
|
||||||
default filerefs can be modified.
|
default filerefs can be modified.
|
||||||
Based on:
|
Note that if you have a new enough version of SAS, and you don't need features
|
||||||
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
such as APPEND, you may be better of using the fcopy() function instead.
|
||||||
|
|
||||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
@param [in] mode (CREATE) Valid values:
|
@param [in] mode (CREATE) Valid values:
|
||||||
@li CREATE - Create the file (even if it already exists)
|
@li CREATE - Create the file (even if it already exists)
|
||||||
@li APPEND - Append to the file (don't overwrite)
|
@li APPEND - Append to the file (don't overwrite)
|
||||||
|
@param iftrue= (1=1) Supply a condition for which the macro should be executed
|
||||||
|
|
||||||
@returns nothing
|
@returns nothing
|
||||||
|
|
||||||
@@ -44,15 +45,14 @@
|
|||||||
,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
|
,mode=CREATE
|
||||||
|
,iftrue=%str(1=1)
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local mod outmode;
|
%local mod;
|
||||||
%if &mode=APPEND %then %do;
|
|
||||||
%let mod=mod;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
%let outmode='a';
|
|
||||||
%end;
|
%if &mode=APPEND %then %let mod=mod;
|
||||||
%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 ;
|
||||||
@@ -63,18 +63,13 @@
|
|||||||
|
|
||||||
/* copy the file byte-for-byte */
|
/* copy the file byte-for-byte */
|
||||||
data _null_;
|
data _null_;
|
||||||
length filein 8 fileid 8;
|
infile &inref lrecl=1 recfm=n;
|
||||||
filein = fopen("&inref",'I',1,'B');
|
file &outref &mod recfm=n;
|
||||||
fileid = fopen("&outref",&outmode,1,'B');
|
input sourcechar $char1. @@;
|
||||||
rec = '20'x;
|
format sourcechar hex2.;
|
||||||
do while(fread(filein)=0);
|
put sourcechar char1. @@;
|
||||||
rc = fget(filein,rec,1);
|
|
||||||
rc = fput(fileid, rec);
|
|
||||||
rc =fwrite(fileid);
|
|
||||||
end;
|
|
||||||
rc = fclose(filein);
|
|
||||||
rc = fclose(fileid);
|
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &inref = ____in %then %do;
|
%if &inref = ____in %then %do;
|
||||||
filename &inref clear;
|
filename &inref clear;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
194
base/mp_chop.sas
Normal file
194
base/mp_chop.sas
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Splits a file of ANY SIZE by reference to a search string.
|
||||||
|
@details Provide a fileref and a search string to chop off part of a file.
|
||||||
|
|
||||||
|
Works by reading in the file byte by byte, then marking the beginning and end
|
||||||
|
of each matched string, before finally doing the chop.
|
||||||
|
|
||||||
|
Choose whether to keep the FIRST or the LAST section of the file. Optionally,
|
||||||
|
use an OFFSET to fix the precise chop point.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let src="%sysfunc(pathname(work))/file.txt";
|
||||||
|
%let str=Chop here!;
|
||||||
|
%let out1="%sysfunc(pathname(work))/file1.txt";
|
||||||
|
%let out2="%sysfunc(pathname(work))/file2.txt";
|
||||||
|
%let out3="%sysfunc(pathname(work))/file3.txt";
|
||||||
|
%let out4="%sysfunc(pathname(work))/file4.txt";
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
file &src;
|
||||||
|
put "startsection&str.endsection";
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_chop(&src, matchvar=str, keep=FIRST, outfile=&out1)
|
||||||
|
%mp_chop(&src, matchvar=str, keep=LAST, outfile=&out2)
|
||||||
|
%mp_chop(&src, matchvar=str, keep=FIRST, matchpoint=END, outfile=&out3)
|
||||||
|
%mp_chop(&src, matchvar=str, keep=LAST, matchpoint=END, outfile=&out4)
|
||||||
|
|
||||||
|
filename results (&out1 &out2 &out3 &out4);
|
||||||
|
data _null_;
|
||||||
|
infile results;
|
||||||
|
input;
|
||||||
|
list;
|
||||||
|
run;
|
||||||
|
|
||||||
|
Results:
|
||||||
|
@li `startsection`
|
||||||
|
@li `Chop here!endsection`
|
||||||
|
@li `startsectionChop here!`
|
||||||
|
@li `endsection`
|
||||||
|
|
||||||
|
For more examples, see mp_chop.test.sas
|
||||||
|
|
||||||
|
@param [in] infile The QUOTED path to the file on which to perform the chop
|
||||||
|
@param [in] matchvar= Macro variable NAME containing the string to split by
|
||||||
|
@param [in] matchpoint= (START) Valid values:
|
||||||
|
@li START - chop at the beginning of the string in `matchvar`.
|
||||||
|
@li END - chop at the end of the string in `matchvar`.
|
||||||
|
@param [in] offset= (0) An adjustment to the precise chop location, by
|
||||||
|
by reference to the `matchpoint`. Should be a positive or negative integer.
|
||||||
|
@param [in] keep= (FIRST) Valid values:
|
||||||
|
@li FIRST - keep the section of the file before the chop
|
||||||
|
@li LAST - keep the section of the file after the chop
|
||||||
|
@param [in] mdebug= (0) Set to 1 to provide macro debugging
|
||||||
|
@param outfile= (0) Optional QUOTED path to the adjusted output file (avoids
|
||||||
|
overwriting the first file).
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquefileref.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_abort.sas
|
||||||
|
@li mp_gsubfile.sas
|
||||||
|
@li mp_replace.sas
|
||||||
|
@li mp_chop.test.sas
|
||||||
|
|
||||||
|
@version 9.4
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_chop(infile,
|
||||||
|
matchvar=,
|
||||||
|
matchpoint=START,
|
||||||
|
keep=FIRST,
|
||||||
|
offset=0,
|
||||||
|
mdebug=0,
|
||||||
|
outfile=0
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local fref0 dttm ds1 outref;
|
||||||
|
%let fref0=%mf_getuniquefileref();
|
||||||
|
%let ds1=%mf_getuniquename(prefix=allchars);
|
||||||
|
%let ds2=%mf_getuniquename(prefix=startmark);
|
||||||
|
|
||||||
|
%if &outfile=0 %then %let outfile=&infile;
|
||||||
|
|
||||||
|
%mp_abort(iftrue= (%length(%superq(&matchvar))=0)
|
||||||
|
,mac=mp_chop.sas
|
||||||
|
,msg=%str(&matchvar is an empty variable)
|
||||||
|
)
|
||||||
|
|
||||||
|
/* START */
|
||||||
|
%let dttm=%sysfunc(datetime());
|
||||||
|
|
||||||
|
filename &fref0 &infile lrecl=1 recfm=n;
|
||||||
|
|
||||||
|
/* create dataset with one char per row */
|
||||||
|
data &ds1;
|
||||||
|
infile &fref0;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* get start & stop position of first matchvar string (one row, two vars) */
|
||||||
|
data &ds2;
|
||||||
|
/* set find string to length in bytes to cover trailing spaces */
|
||||||
|
length string $ %length(%superq(&matchvar));
|
||||||
|
string =symget("&matchvar");
|
||||||
|
drop string;
|
||||||
|
|
||||||
|
firstchar=char(string,1);
|
||||||
|
findlen=lengthm(string); /* <- for trailing bytes */
|
||||||
|
|
||||||
|
do _N_=1 to nobs;
|
||||||
|
set &ds1 nobs=nobs point=_N_;
|
||||||
|
if sourcechar=firstchar then do;
|
||||||
|
pos=1;
|
||||||
|
s=0;
|
||||||
|
do point=_N_ to min(_N_ + findlen -1,nobs);
|
||||||
|
set &ds1 point=point;
|
||||||
|
if sourcechar=char(string, pos) then s + 1;
|
||||||
|
else goto _leave_;
|
||||||
|
pos+1;
|
||||||
|
end;
|
||||||
|
_leave_:
|
||||||
|
if s=findlen then do;
|
||||||
|
START =_N_;
|
||||||
|
_N_ =_N_+ s - 1;
|
||||||
|
STOP =_N_;
|
||||||
|
output;
|
||||||
|
/* matched! */
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
stop;
|
||||||
|
keep START STOP;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%local split;
|
||||||
|
%let split=0;
|
||||||
|
data _null_;
|
||||||
|
set &ds2;
|
||||||
|
if "&matchpoint"='START' then do;
|
||||||
|
if "&keep"='FIRST' then mp=start;
|
||||||
|
else if "&keep"='LAST' then mp=start-1;
|
||||||
|
end;
|
||||||
|
else if "&matchpoint"='END' then do;
|
||||||
|
if "&keep"='FIRST' then mp=stop+1;
|
||||||
|
else if "&keep"='LAST' then mp=stop;
|
||||||
|
end;
|
||||||
|
split=mp+&offset;
|
||||||
|
call symputx('split',split,'l');
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
put (_all_)(=);
|
||||||
|
%put &=offset;
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
%if &split=0 %then %do;
|
||||||
|
%put &sysmacroname: No match found in &infile for string %superq(&matchvar);
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
file &outfile recfm=n;
|
||||||
|
set &ds1;
|
||||||
|
%if &keep=FIRST %then %do;
|
||||||
|
if _n_ ge &split then stop;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
if _n_ gt &split;
|
||||||
|
%end;
|
||||||
|
put sourcechar char1.;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%if &mdebug=0 %then %do;
|
||||||
|
filename &fref0 clear;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
data _null_;
|
||||||
|
infile &outfile lrecl=32767;
|
||||||
|
input;
|
||||||
|
list;
|
||||||
|
if _n_>50 then stop;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
/* END */
|
||||||
|
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
|
||||||
|
|
||||||
|
%mend mp_chop;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_cleancsv.sas
|
@file
|
||||||
@brief Fixes embedded cr / lf / crlf in CSV
|
@brief Fixes embedded cr / lf / crlf in CSV
|
||||||
@details CSVs will sometimes contain lf or crlf within quotes (eg when
|
@details CSVs will sometimes contain lf or crlf within quotes (eg when
|
||||||
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
||||||
@@ -7,14 +7,16 @@
|
|||||||
This macro converts any csv to follow the convention of a windows excel file,
|
This macro converts any csv to follow the convention of a windows excel file,
|
||||||
applying CRLF line endings and converting embedded cr and crlf to lf.
|
applying CRLF line endings and converting embedded cr and crlf to lf.
|
||||||
|
|
||||||
usage:
|
Usage:
|
||||||
|
|
||||||
fileref mycsv "/path/your/csv";
|
fileref mycsv "/path/your/csv";
|
||||||
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
||||||
|
|
||||||
@param in= provide path or fileref to input csv
|
@param in= (NOTPROVIDED) Provide path or fileref to input csv. If a period is
|
||||||
@param out= output path or fileref to output csv
|
found, it is assumed to be a file.
|
||||||
@param qchar= quote char - hex code 22 is the double quote.
|
@param out= (NOTPROVIDED) Output path or fileref to output csv. If a period
|
||||||
|
is found, it is assumed to be a file.
|
||||||
|
@param qchar= ('22'x) Quote char - hex code 22 is the double quote.
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -56,9 +58,14 @@
|
|||||||
else do;
|
else do;
|
||||||
/* outside a quote, change cr and lf to crlf */
|
/* outside a quote, change cr and lf to crlf */
|
||||||
if inchar='0D'x then do;
|
if inchar='0D'x then do;
|
||||||
|
crblank:
|
||||||
put '0D0A'x;
|
put '0D0A'x;
|
||||||
input inchar $char1.;
|
input inchar $char1.;
|
||||||
if inchar ne '0A'x then do;
|
if inchar='0D'x then do;
|
||||||
|
/* multiple CR indicates CR formatted file with blank lines */
|
||||||
|
goto crblank;
|
||||||
|
end;
|
||||||
|
else if inchar ne '0A'x then do;
|
||||||
put inchar $char1.;
|
put inchar $char1.;
|
||||||
if inchar=qchar then isq = mod(isq+1,2);
|
if inchar=qchar then isq = mod(isq+1,2);
|
||||||
end;
|
end;
|
||||||
|
|||||||
85
base/mp_cntlout.sas
Normal file
85
base/mp_cntlout.sas
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
@file mp_cntlout.sas
|
||||||
|
@brief Creates a cntlout dataset in a consistent format
|
||||||
|
@details The dataset produced by proc format in the cntlout option will vary
|
||||||
|
according to its contents.
|
||||||
|
|
||||||
|
When dealing with formats from an ETL perspective (eg in [Data Controller for
|
||||||
|
SAS](https://datacontroller.io)), it is important that the output dataset
|
||||||
|
has a consistent model (and compariable values).
|
||||||
|
|
||||||
|
This macro makes use of mddl_sas_cntlout.sas to provide the consistent model,
|
||||||
|
and will left-align the start and end values when dealing with numeric ranges
|
||||||
|
to enable consistency when checking for differences.
|
||||||
|
|
||||||
|
usage:
|
||||||
|
|
||||||
|
%mp_cntlout(libcat=yourlib.cat,cntlout=work.formatexport)
|
||||||
|
|
||||||
|
@param [in] libcat The library.catalog reference
|
||||||
|
@param [in] fmtlist= (0) provide a space separated list of specific formats to
|
||||||
|
extract
|
||||||
|
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||||
|
@param [out] cntlout= (work.fmtextract) Libds reference for the output dataset
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mddl_sas_cntlout.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mf_getvarformat.sas
|
||||||
|
@li mp_getformats.sas
|
||||||
|
@li mp_loadformat.sas
|
||||||
|
@li mp_ds2fmtds.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
@cond
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_cntlout(
|
||||||
|
iftrue=(1=1)
|
||||||
|
,libcat=
|
||||||
|
,cntlout=work.fmtextract
|
||||||
|
,fmtlist=0
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
%local ddlds cntlds i;
|
||||||
|
|
||||||
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
|
%let ddlds=%mf_getuniquename();
|
||||||
|
%let cntlds=%mf_getuniquename();
|
||||||
|
|
||||||
|
%mddl_sas_cntlout(libds=&ddlds)
|
||||||
|
|
||||||
|
%if %index(&libcat,-)>0 and %scan(&libcat,2,-)=FC %then %do;
|
||||||
|
%let libcat=%scan(&libcat,1,-);
|
||||||
|
%end;
|
||||||
|
|
||||||
|
proc format lib=&libcat cntlout=&cntlds;
|
||||||
|
%if "&fmtlist" ne "0" %then %do;
|
||||||
|
select
|
||||||
|
%do i=1 %to %sysfunc(countw(&fmtlist));
|
||||||
|
%scan(&fmtlist,&i,%str( ))
|
||||||
|
%end;
|
||||||
|
;
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
data &cntlout;
|
||||||
|
if 0 then set &ddlds;
|
||||||
|
set &cntlds;
|
||||||
|
if type="N" then do;
|
||||||
|
start=cats(start);
|
||||||
|
end=cats(end);
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
proc sort;
|
||||||
|
by fmtname start;
|
||||||
|
run;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
drop table &ddlds,&cntlds;
|
||||||
|
|
||||||
|
%mend mp_cntlout;
|
||||||
|
/** @endcond */
|
||||||
@@ -16,8 +16,11 @@
|
|||||||
|
|
||||||
%mp_copyfolder(&rootdir,©dir)
|
%mp_copyfolder(&rootdir,©dir)
|
||||||
|
|
||||||
@param source Unquoted path to the folder to copy from.
|
@param [in] source Unquoted path to the folder to copy from.
|
||||||
@param target Unquoted path to the folder to copy to.
|
@param [out] target Unquoted path to the folder to copy to.
|
||||||
|
@param [in] copymax=(MAX) Set to a positive integer to indicate the level of
|
||||||
|
subdirectory copy recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
|
recursion, set to MAX.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@@ -31,7 +34,7 @@
|
|||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_copyfolder(source,target);
|
%macro mp_copyfolder(source,target,copymax=MAX);
|
||||||
|
|
||||||
%mp_abort(iftrue=(%mf_isdir(&source)=0)
|
%mp_abort(iftrue=(%mf_isdir(&source)=0)
|
||||||
,mac=&sysmacroname
|
,mac=&sysmacroname
|
||||||
@@ -50,7 +53,7 @@
|
|||||||
%let tempds=%mf_getuniquename();
|
%let tempds=%mf_getuniquename();
|
||||||
|
|
||||||
/* recursive directory listing */
|
/* recursive directory listing */
|
||||||
%mp_dirlist(path=&source,outds=work.&tempds, maxdepth=MAX)
|
%mp_dirlist(path=&source,outds=work.&tempds,maxdepth=©max)
|
||||||
|
|
||||||
/* create folders and copy content */
|
/* create folders and copy content */
|
||||||
data _null_;
|
data _null_;
|
||||||
|
|||||||
@@ -5,24 +5,33 @@
|
|||||||
make use of permanent tables. To avoid duplication in definitions, this
|
make use of permanent tables. To avoid duplication in definitions, this
|
||||||
macro provides a central location for managing the corresponding DDL.
|
macro provides a central location for managing the corresponding DDL.
|
||||||
|
|
||||||
|
Note - this macro is likely to be deprecated in future in favour of a
|
||||||
|
dedicated "datamodel" folder (prefix mddl)
|
||||||
|
|
||||||
|
Any corresponding data would go in a seperate repo, to avoid this one
|
||||||
|
ballooning in size!
|
||||||
|
|
||||||
Example usage:
|
Example usage:
|
||||||
|
|
||||||
%mp_coretable(LOCKTABLE,libds=work.locktable)
|
%mp_coretable(LOCKTABLE,libds=work.locktable)
|
||||||
|
|
||||||
@param [in] table_ref The type of table to create. Example values:
|
@param [in] table_ref The type of table to create. Example values:
|
||||||
@li DIFFTABLE - Used to store changes to tables. Used by mp_storediffs.sas
|
@li DIFFTABLE
|
||||||
and mp_stackdiffs.sas
|
@li FILTER_DETAIL
|
||||||
@li FILTER_DETAIL - For storing detailed filter values. Used by
|
@li FILTER_SUMMARY
|
||||||
mp_filterstore.sas.
|
@li LOCKANYTABLE
|
||||||
@li FILTER_SUMMARY - For storing summary filter values. Used by
|
@li MAXKEYTABLE
|
||||||
mp_filterstore.sas.
|
|
||||||
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
|
|
||||||
mp_lockanytable.sas
|
|
||||||
@li MAXKEYTABLE - For storing the maximum retained key information. Used
|
|
||||||
by mp_retainedkey.sas
|
|
||||||
@param [in] libds= (0) The library.dataset reference used to create the table.
|
@param [in] libds= (0) The library.dataset reference used to create the table.
|
||||||
If not provided, then the DDL is simply printed to the log.
|
If not provided, then the DDL is simply printed to the log.
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mddl_dc_difftable.sas
|
||||||
|
@li mddl_dc_filterdetail.sas
|
||||||
|
@li mddl_dc_filtersummary.sas
|
||||||
|
@li mddl_dc_locktable.sas
|
||||||
|
@li mddl_dc_maxkeytable.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_filterstore.sas
|
@li mp_filterstore.sas
|
||||||
@li mp_lockanytable.sas
|
@li mp_lockanytable.sas
|
||||||
@@ -38,80 +47,26 @@
|
|||||||
%macro mp_coretable(table_ref,libds=0
|
%macro mp_coretable(table_ref,libds=0
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local outds ;
|
%local outds ;
|
||||||
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
|
%let outds=%sysfunc(ifc(&libds=0,%mf_getuniquename(),&libds));
|
||||||
proc sql;
|
proc sql;
|
||||||
%if &table_ref=DIFFTABLE %then %do;
|
%if &table_ref=DIFFTABLE %then %do;
|
||||||
create table &outds(
|
%mddl_dc_difftable(libds=&outds)
|
||||||
load_ref char(36) label='unique load reference',
|
|
||||||
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
|
|
||||||
libref char(8) label='Library Reference (8 chars)',
|
|
||||||
dsn char(32) label='Dataset Name (32 chars)',
|
|
||||||
key_hash char(32) label=
|
|
||||||
'MD5 Hash of primary key values (pipe seperated)',
|
|
||||||
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
|
|
||||||
is_pk num label='Is Primary Key Field? (1/0)',
|
|
||||||
is_diff num label=
|
|
||||||
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
|
|
||||||
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
|
|
||||||
tgtvar_nm char(32) label='Target variable name (32 chars)',
|
|
||||||
oldval_num num format=best32. label='Old (numeric) value',
|
|
||||||
newval_num num format=best32. label='New (numeric) value',
|
|
||||||
oldval_char char(32765) label='Old (character) value',
|
|
||||||
newval_char char(32765) label='New (character) value',
|
|
||||||
constraint pk_mpe_audit
|
|
||||||
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
|
|
||||||
);
|
|
||||||
%end;
|
%end;
|
||||||
%else %if &table_ref=LOCKTABLE %then %do;
|
%else %if &table_ref=LOCKTABLE %then %do;
|
||||||
create table &outds(
|
%mddl_dc_locktable(libds=&outds)
|
||||||
lock_lib char(8),
|
|
||||||
lock_ds char(32),
|
|
||||||
lock_status_cd char(10) not null,
|
|
||||||
lock_user_nm char(100) not null ,
|
|
||||||
lock_ref char(200),
|
|
||||||
lock_pid char(10),
|
|
||||||
lock_start_dttm num format=E8601DT26.6,
|
|
||||||
lock_end_dttm num format=E8601DT26.6,
|
|
||||||
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
|
|
||||||
%end;
|
%end;
|
||||||
%else %if &table_ref=FILTER_SUMMARY %then %do;
|
%else %if &table_ref=FILTER_SUMMARY %then %do;
|
||||||
create table &outds(
|
%mddl_dc_filtersummary(libds=&outds)
|
||||||
filter_rk num not null,
|
|
||||||
filter_hash char(32) not null,
|
|
||||||
filter_table char(41) not null,
|
|
||||||
processed_dttm num not null format=E8601DT26.6,
|
|
||||||
constraint pk_mpe_filteranytable
|
|
||||||
primary key(filter_rk));
|
|
||||||
%end;
|
%end;
|
||||||
%else %if &table_ref=FILTER_DETAIL %then %do;
|
%else %if &table_ref=FILTER_DETAIL %then %do;
|
||||||
create table &outds(
|
%mddl_dc_filterdetail(libds=&outds)
|
||||||
filter_hash char(32) not null,
|
|
||||||
filter_line num not null,
|
|
||||||
group_logic char(3) not null,
|
|
||||||
subgroup_logic char(3) not null,
|
|
||||||
subgroup_id num not null,
|
|
||||||
variable_nm varchar(32) not null,
|
|
||||||
operator_nm varchar(12) not null,
|
|
||||||
raw_value varchar(4000) not null,
|
|
||||||
processed_dttm num not null format=E8601DT26.6,
|
|
||||||
constraint pk_mpe_filteranytable
|
|
||||||
primary key(filter_hash,filter_line));
|
|
||||||
%end;
|
%end;
|
||||||
%else %if &table_ref=MAXKEYTABLE %then %do;
|
%else %if &table_ref=MAXKEYTABLE %then %do;
|
||||||
create table &outds(
|
%mddl_dc_maxkeytable(libds=&outds)
|
||||||
keytable varchar(41) label='Base table in libref.dataset format',
|
|
||||||
keycolumn char(32) format=$32.
|
|
||||||
label='The Retained key field containing the key values.',
|
|
||||||
max_key num label=
|
|
||||||
'Integer representing current max RK or SK value in the KEYTABLE',
|
|
||||||
processed_dttm num format=E8601DT26.6
|
|
||||||
label='Datetime this value was last updated',
|
|
||||||
constraint pk_mpe_maxkeyvalues
|
|
||||||
primary key(keytable));
|
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
|
||||||
%if &libds=0 %then %do;
|
%if &libds=0 %then %do;
|
||||||
|
proc sql;
|
||||||
describe table &syslast;
|
describe table &syslast;
|
||||||
drop table &syslast;
|
drop table &syslast;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -18,11 +18,14 @@
|
|||||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||||
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
||||||
|
|
||||||
@param inds= The input table containing the constraint info
|
@param inds= (work.mp_getconstraints) The input table containing the
|
||||||
@param outds= a table containing the create statements (create_statement column)
|
constraint info
|
||||||
@param execute= `YES|NO` - default is NO. To actually create, use YES.
|
@param outds= (work.mp_createconstraints) A table containing the create
|
||||||
|
statements (create_statement column)
|
||||||
|
@param execute= (NO) To actually create, use YES.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> Related Files </h4>
|
||||||
|
@li mp_getconstraints.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -30,7 +33,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_createconstraints(inds=mp_getconstraints
|
%macro mp_createconstraints(inds=mp_getconstraints
|
||||||
,outds=mp_createconstraints
|
,outds=work.mp_createconstraints
|
||||||
,execute=NO
|
,execute=NO
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_createwebservice.sas
|
@file mp_createwebservice.sas
|
||||||
@brief Create a web service in SAS 9 or Viya
|
@brief Create a web service in SAS 9, Viya or SASjs Server
|
||||||
@details Creates a SASJS ready Stored Process in SAS 9 or Job Execution
|
@details This is actually a wrapper for mx_createwebservice.sas, remaining
|
||||||
Service in SAS Viya
|
for legacy purposes. For new apps, use mx_createwebservice.sas.
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
%* compile macros ;
|
|
||||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
|
||||||
%inc mc;
|
|
||||||
|
|
||||||
%* write some code;
|
|
||||||
filename ft15f001 temp;
|
|
||||||
parmcards4;
|
|
||||||
%* fetch any data from frontend ;
|
|
||||||
%webout(FETCH)
|
|
||||||
data example1 example2;
|
|
||||||
set sashelp.class;
|
|
||||||
run;
|
|
||||||
%* send data back;
|
|
||||||
%webout(OPEN)
|
|
||||||
%webout(ARR,example1) * Array format, fast, suitable for large tables ;
|
|
||||||
%webout(OBJ,example2) * Object format, easier to work with ;
|
|
||||||
%webout(CLOSE)
|
|
||||||
;;;;
|
|
||||||
%mp_createwebservice(path=/Public/app/common,name=appInit,replace=YES)
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getplatform.sas
|
@li mx_createwebservice.sas
|
||||||
@li mm_createwebservice.sas
|
|
||||||
@li mv_createwebservice.sas
|
|
||||||
|
|
||||||
@param [in,out] path= The full folder path where the service will be created
|
|
||||||
@param [in,out] name= Service name. Avoid spaces.
|
|
||||||
@param [in] desc= The description of the service (optional)
|
|
||||||
@param [in] precode= Space separated list of filerefs, pointing to the code
|
|
||||||
that needs to be attached to the beginning of the service (optional)
|
|
||||||
@param [in] code= (ft15f001) Space seperated fileref(s) of the actual code to
|
|
||||||
be added
|
|
||||||
@param [in] replace= (YES) Select YES to replace any existing service in that
|
|
||||||
location
|
|
||||||
|
|
||||||
|
|
||||||
@version 9.2
|
|
||||||
@author Allan Bowe
|
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@@ -53,33 +17,16 @@ Usage:
|
|||||||
,code=ft15f001
|
,code=ft15f001
|
||||||
,desc=This service was created by the mp_createwebservice macro
|
,desc=This service was created by the mp_createwebservice macro
|
||||||
,replace=YES
|
,replace=YES
|
||||||
|
,mdebug=0
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%if &syscc ge 4 %then %do;
|
%mx_createwebservice(path=&path
|
||||||
%put syscc=&syscc - &sysmacroname will not execute in this state;
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%local platform; %let platform=%mf_getplatform();
|
|
||||||
%if &platform=SASVIYA %then %do;
|
|
||||||
%if "&path"="HOME" %then %let path=/Users/&sysuserid/My Folder;
|
|
||||||
%mv_createwebservice(path=&path
|
|
||||||
,name=&name
|
,name=&name
|
||||||
,code=&code
|
|
||||||
,precode=&precode
|
,precode=&precode
|
||||||
|
,code=&code
|
||||||
,desc=&desc
|
,desc=&desc
|
||||||
,replace=&replace
|
,replace=&replace
|
||||||
|
,mdebug=&mdebug
|
||||||
)
|
)
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
%if "&path"="HOME" %then %let path=/User Folders/&sysuserid/My Folder;
|
|
||||||
%mm_createwebservice(path=&path
|
|
||||||
,name=&name
|
|
||||||
,code=&code
|
|
||||||
,precode=&precode
|
|
||||||
,desc=&desc
|
|
||||||
,replace=&replace
|
|
||||||
)
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%mend mp_createwebservice;
|
%mend mp_createwebservice;
|
||||||
|
|||||||
52
base/mp_dictionary.sas
Normal file
52
base/mp_dictionary.sas
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
@file mp_dictionary.sas
|
||||||
|
@brief Creates a portal (libref) into the SQL Dictionary Views
|
||||||
|
@details Provide a libref and the macro will create a series of views against
|
||||||
|
each view in the special PROC SQL dictionary libref.
|
||||||
|
|
||||||
|
This is useful if you would like to visualise (navigate) the views in a SAS
|
||||||
|
client such as Base SAS, Enterprise Guide, or Studio (or [Data Controller](
|
||||||
|
https://datacontroller.io)).
|
||||||
|
|
||||||
|
It works by extracting the dictionary.dictionaries view into
|
||||||
|
YOURLIB.dictionaries, then uses that to create a YOURLIB.{viewName} for every
|
||||||
|
other dictionary.view, eg:
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create view YOURLIB.columns as select * from dictionary.columns;
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
libname demo "/lib/directory";
|
||||||
|
%mp_dictionary(lib=demo)
|
||||||
|
|
||||||
|
Or, to just create them in WORK:
|
||||||
|
|
||||||
|
%mp_dictionary()
|
||||||
|
|
||||||
|
If you'd just like to browse the dictionary data model, you can also check
|
||||||
|
out [this article](https://rawsas.com/dictionary-of-dictionaries/).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
@param lib= (WORK) The libref in which to create the views
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_dictionary.test.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_dictionary(lib=WORK)/*/STORE SOURCE*/;
|
||||||
|
%local list i mem;
|
||||||
|
proc sql noprint;
|
||||||
|
create view &lib..dictionaries as select * from dictionary.dictionaries;
|
||||||
|
select distinct memname into: list separated by ' ' from &lib..dictionaries;
|
||||||
|
%do i=1 %to %sysfunc(countw(&list,%str( )));
|
||||||
|
%let mem=%scan(&list,&i,%str( ));
|
||||||
|
create view &lib..&mem as select * from dictionary.&mem;
|
||||||
|
%end;
|
||||||
|
quit;
|
||||||
|
%mend mp_dictionary;
|
||||||
@@ -27,6 +27,9 @@
|
|||||||
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
recursion, set to MAX.
|
recursion, set to MAX.
|
||||||
|
@param [in] showparent= (NO) By default, the initial parent directory is not
|
||||||
|
part of the results. Set to YES to include it. For this record only,
|
||||||
|
directory=filepath.
|
||||||
@param [out] outds= (work.mp_dirlist) The output dataset to create
|
@param [out] outds= (work.mp_dirlist) The output dataset to create
|
||||||
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
|
@param [out] getattrs= (NO) If getattrs=YES then the doptname / foptname
|
||||||
functions are used to scan all properties - any characters that are not
|
functions are used to scan all properties - any characters that are not
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
, fref=0
|
, fref=0
|
||||||
, outds=work.mp_dirlist
|
, outds=work.mp_dirlist
|
||||||
, getattrs=NO
|
, getattrs=NO
|
||||||
|
, showparent=NO
|
||||||
, maxdepth=0
|
, maxdepth=0
|
||||||
, level=0 /* The level of recursion to perform. For internal use only. */
|
, level=0 /* The level of recursion to perform. For internal use only. */
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
@@ -94,6 +98,13 @@ data &out_ds(compress=no
|
|||||||
%end;
|
%end;
|
||||||
if rc = 0 then do;
|
if rc = 0 then do;
|
||||||
did = dopen(fref);
|
did = dopen(fref);
|
||||||
|
if did=0 then do;
|
||||||
|
putlog "NOTE: This directory is empty, or does not exist - &path";
|
||||||
|
msg=sysmsg();
|
||||||
|
put msg;
|
||||||
|
put _all_;
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
|
/* attribute is OS-dependent - could be "Directory" or "Directory Name" */
|
||||||
numopts=doptnum(did);
|
numopts=doptnum(did);
|
||||||
do i=1 to numopts;
|
do i=1 to numopts;
|
||||||
@@ -101,12 +112,6 @@ data &out_ds(compress=no
|
|||||||
if foption=:'Directory' then i=numopts;
|
if foption=:'Directory' then i=numopts;
|
||||||
end;
|
end;
|
||||||
directory=dinfo(did,foption);
|
directory=dinfo(did,foption);
|
||||||
if did=0 then do;
|
|
||||||
putlog "NOTE: This directory is empty - " directory;
|
|
||||||
msg=sysmsg();
|
|
||||||
put _all_;
|
|
||||||
stop;
|
|
||||||
end;
|
|
||||||
rc = filename(fref);
|
rc = filename(fref);
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
@@ -144,6 +149,15 @@ data &out_ds(compress=no
|
|||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
rc = dclose(did);
|
rc = dclose(did);
|
||||||
|
%if &showparent=YES and &level=0 %then %do;
|
||||||
|
filepath=directory;
|
||||||
|
file_or_folder='folder';
|
||||||
|
ext='';
|
||||||
|
filename=scan(directory,-1,'/\');
|
||||||
|
msg='';
|
||||||
|
level=&level;
|
||||||
|
output;
|
||||||
|
%end;
|
||||||
stop;
|
stop;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
@@ -151,6 +165,7 @@ run;
|
|||||||
data &out_ds;
|
data &out_ds;
|
||||||
set &out_ds;
|
set &out_ds;
|
||||||
length infoname infoval $60 fref $8;
|
length infoname infoval $60 fref $8;
|
||||||
|
if _n_=1 then call missing(fref);
|
||||||
rc=filename(fref,filepath);
|
rc=filename(fref,filepath);
|
||||||
drop rc infoname fid i close fref;
|
drop rc infoname fid i close fref;
|
||||||
if file_or_folder='file' then do;
|
if file_or_folder='file' then do;
|
||||||
@@ -230,6 +245,9 @@ run;
|
|||||||
data _null_;
|
data _null_;
|
||||||
set &out_ds;
|
set &out_ds;
|
||||||
where file_or_folder='folder';
|
where file_or_folder='folder';
|
||||||
|
%if &showparent=YES and &level=0 %then %do;
|
||||||
|
if filepath ne directory;
|
||||||
|
%end;
|
||||||
length code $10000;
|
length code $10000;
|
||||||
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
|
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
|
||||||
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
|
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
%mp_ds2cards(base_ds=sashelp.class
|
%mp_ds2cards(sashelp.class
|
||||||
, tgt_ds=work.class
|
, tgt_ds=work.class
|
||||||
, cards_file= "C:\temp\class.sas"
|
, cards_file= "C:\temp\class.sas"
|
||||||
, showlog=NO
|
, showlog=NO
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
- explicity setting a unix LF
|
- explicity setting a unix LF
|
||||||
- constraints / indexes etc
|
- constraints / indexes etc
|
||||||
|
|
||||||
@param [in] base_ds= Should be two level - eg work.blah. This is the table
|
@param [in] base_ds Should be two level - eg work.blah. This is the table
|
||||||
that is converted to a cards file.
|
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.
|
||||||
@@ -48,9 +48,10 @@
|
|||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
@cond
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_ds2cards(base_ds=, tgt_ds=
|
%macro mp_ds2cards(base_ds, tgt_ds=
|
||||||
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
|
,cards_file="%sysfunc(pathname(work))/cardgen.sas"
|
||||||
,maxobs=max
|
,maxobs=max
|
||||||
,random_sample=NO
|
,random_sample=NO
|
||||||
@@ -219,7 +220,8 @@ data _null_;
|
|||||||
put ' @file';
|
put ' @file';
|
||||||
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
|
||||||
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
put " @details Generated by %nrstr(%%)mp_ds2cards()";
|
||||||
put " Available on github.com/sasjs/core";
|
put " Source: https://github.com/sasjs/core";
|
||||||
|
put ' @cond ';
|
||||||
put '**/';
|
put '**/';
|
||||||
put "data &tgt_ds &indexes;";
|
put "data &tgt_ds &indexes;";
|
||||||
put "attrib ";
|
put "attrib ";
|
||||||
@@ -252,6 +254,7 @@ data _null_;
|
|||||||
;
|
;
|
||||||
%end;
|
%end;
|
||||||
put ";";
|
put ";";
|
||||||
|
put 'missing a b c d e f g h i j k l m n o p q r s t u v w x y z _;';
|
||||||
put "datalines4;";
|
put "datalines4;";
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -264,6 +267,7 @@ data _null_;
|
|||||||
if __lastobs then do;
|
if __lastobs then do;
|
||||||
put ';;;;';
|
put ';;;;';
|
||||||
put 'run;';
|
put 'run;';
|
||||||
|
put '/** @endcond **/';
|
||||||
stop;
|
stop;
|
||||||
end;
|
end;
|
||||||
run;
|
run;
|
||||||
@@ -284,3 +288,4 @@ quit;
|
|||||||
%put NOTE- %sysfunc(dequote(&cards_file.));
|
%put NOTE- %sysfunc(dequote(&cards_file.));
|
||||||
%put NOTE-;%put NOTE-;
|
%put NOTE-;%put NOTE-;
|
||||||
%mend mp_ds2cards;
|
%mend mp_ds2cards;
|
||||||
|
/** @endcond **/
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
numbers).
|
numbers).
|
||||||
@param [out] outfile= The output filename - should be quoted.
|
@param [out] outfile= The output filename - should be quoted.
|
||||||
@param [out] outref= (0) The output fileref (takes precedence if provided)
|
@param [out] outref= (0) The output fileref (takes precedence if provided)
|
||||||
@param [in] outencoding= (0) The output encoding to use (unquoted)
|
@param [in] outencoding= (0) The (quoted) output encoding to use, eg `"UTF-8"`
|
||||||
@param [in] termstr= (CRLF) The line seperator to use. For SASJS, will
|
@param [in] termstr= (CRLF) The line seperator to use. For SASJS, will
|
||||||
always be CRLF. Valid values:
|
always be CRLF. Valid values:
|
||||||
@li CRLF
|
@li CRLF
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
|
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
|
||||||
|
|
||||||
%if &outencoding=0 %then %let outencoding=;
|
%if &outencoding=0 %then %let outencoding=;
|
||||||
%else %let outencoding=encoding="&outencoding";
|
%else %let outencoding=encoding=&outencoding;
|
||||||
|
|
||||||
%if &outref=0 %then %let outloc=&outfile;
|
%if &outref=0 %then %let outloc=&outfile;
|
||||||
%else %let outloc=&outref;
|
%else %let outloc=&outref;
|
||||||
|
|||||||
30
base/mp_ds2ddl.sas
Normal file
30
base/mp_ds2ddl.sas
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief A wrapper for mp_getddl.sas
|
||||||
|
@details In the next release, this will be the main version.
|
||||||
|
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_getddl.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_ds2ddl(libds,fref=getddl,flavour=SAS,showlog=YES,schema=
|
||||||
|
,applydttm=NO
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local libref;
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%let libref=%scan(&libds,1,.);
|
||||||
|
%if &libref=&libds %then %let libds=WORK.&libds;
|
||||||
|
|
||||||
|
%mp_getddl(%scan(&libds,1,.)
|
||||||
|
,%scan(&libds,2,.)
|
||||||
|
,fref=&fref
|
||||||
|
,flavour=SAS
|
||||||
|
,showlog=&showlog
|
||||||
|
,schema=&schema
|
||||||
|
,applydttm=&applydttm
|
||||||
|
)
|
||||||
|
|
||||||
|
%mend mp_ds2ddl;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_existds.sas
|
@li mf_existds.sas
|
||||||
|
|
||||||
<h4> Related Macros <h4>
|
<h4> Related Macros </h4>
|
||||||
@li mp_jsonout.sas
|
@li mp_jsonout.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
|
|||||||
@@ -57,6 +57,11 @@
|
|||||||
%local vars;
|
%local vars;
|
||||||
%let vars=%upcase(%mf_getvarlist(&libds));
|
%let vars=%upcase(%mf_getvarlist(&libds));
|
||||||
|
|
||||||
|
%if %trim(X&vars)=X %then %do;
|
||||||
|
%put &sysmacroname: Table &libds has no columns!!;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
/* create the header row */
|
/* create the header row */
|
||||||
data _null_;
|
data _null_;
|
||||||
file &outref;
|
file &outref;
|
||||||
@@ -87,7 +92,7 @@ data _null_;
|
|||||||
run;
|
run;
|
||||||
|
|
||||||
%if %upcase(&showlog)=YES %then %do;
|
%if %upcase(&showlog)=YES %then %do;
|
||||||
options ps=max;
|
options ps=max lrecl=max;
|
||||||
data _null_;
|
data _null_;
|
||||||
infile &outref;
|
infile &outref;
|
||||||
input;
|
input;
|
||||||
|
|||||||
110
base/mp_dsmeta.sas
Normal file
110
base/mp_dsmeta.sas
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Export dataset metadata to a single output table
|
||||||
|
@details Exports the dataset attributes and enginehost information, then
|
||||||
|
converts the datasets into a single output table in the following format:
|
||||||
|
|
||||||
|
|ODS_TABLE:$10.|NAME:$100.|VALUE:$1000.|
|
||||||
|
|---|---|---|
|
||||||
|
|`ATTRIBUTES `|`Data Set Name `|`SASHELP.CLASS `|
|
||||||
|
|`ATTRIBUTES `|`Observations `|`19 `|
|
||||||
|
|`ATTRIBUTES `|`Member Type `|`DATA `|
|
||||||
|
|`ATTRIBUTES `|`Variables `|`5 `|
|
||||||
|
|`ATTRIBUTES `|`Engine `|`V9 `|
|
||||||
|
|`ATTRIBUTES `|`Indexes `|`0 `|
|
||||||
|
|`ATTRIBUTES `|`Created `|`06/08/2020 00:59:14 `|
|
||||||
|
|`ATTRIBUTES `|`Observation Length `|`40 `|
|
||||||
|
|`ATTRIBUTES `|`Last Modified `|`06/08/2020 00:59:14 `|
|
||||||
|
|`ATTRIBUTES `|`Deleted Observations `|`0 `|
|
||||||
|
|`ATTRIBUTES `|`Protection `|`. `|
|
||||||
|
|`ATTRIBUTES `|`Compressed `|`NO `|
|
||||||
|
|`ATTRIBUTES `|`Data Set Type `|`. `|
|
||||||
|
|`ATTRIBUTES `|`Sorted `|`NO `|
|
||||||
|
|`ATTRIBUTES `|`Label `|`Student Data `|
|
||||||
|
|`ATTRIBUTES `|`Data Representation `|`SOLARIS_X86_64, LINUX_X86_64, ALPHA_TRU64, LINUX_IA64 `|
|
||||||
|
|`ATTRIBUTES `|`Encoding `|`us-ascii ASCII (ANSI) `|
|
||||||
|
|`ENGINEHOST `|`Data Set Page Size `|`65536 `|
|
||||||
|
|`ENGINEHOST `|`Number of Data Set Pages `|`1 `|
|
||||||
|
|`ENGINEHOST `|`First Data Page `|`1 `|
|
||||||
|
|`ENGINEHOST `|`Max Obs per Page `|`1632 `|
|
||||||
|
|`ENGINEHOST `|`Obs in First Data Page `|`19 `|
|
||||||
|
|`ENGINEHOST `|`Number of Data Set Repairs `|`0 `|
|
||||||
|
|`ENGINEHOST `|`Filename `|`/opt/sas/sas9/SASHome/SASFoundation/9.4/sashelp/class.sas7bdat `|
|
||||||
|
|`ENGINEHOST `|`Release Created `|`9.0401M7 `|
|
||||||
|
|`ENGINEHOST `|`Host Created `|`Linux `|
|
||||||
|
|`ENGINEHOST `|`Inode Number `|`28314616 `|
|
||||||
|
|`ENGINEHOST `|`Access Permission `|`rw-r--r-- `|
|
||||||
|
|`ENGINEHOST `|`Owner Name `|`sas `|
|
||||||
|
|`ENGINEHOST `|`File Size `|`128KB `|
|
||||||
|
|`ENGINEHOST `|`File Size (bytes) `|`131072 `|
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
%mp_dsmeta(sashelp.class,outds=work.mymeta)
|
||||||
|
proc print data=work.mymeta;
|
||||||
|
run;
|
||||||
|
|
||||||
|
For more details on creating datasets from PROC CONTENTS check out this
|
||||||
|
excellent [paper](
|
||||||
|
https://support.sas.com/resources/papers/proceedings14/1549-2014.pdf) by
|
||||||
|
[Louise Hadden](https://www.linkedin.com/in/louisehadden/).
|
||||||
|
|
||||||
|
@param libds The library.dataset to export the metadata for
|
||||||
|
@param outds= (work.dsmeta) The output table to contain the metadata
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_dsmeta.test.sas
|
||||||
|
@li mp_getcols.sas
|
||||||
|
@li mp_getdbml.sas
|
||||||
|
@li mp_getddl.sas
|
||||||
|
@li mp_getformats.sas
|
||||||
|
@li mp_getpk.sas
|
||||||
|
@li mp_guesspk.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_dsmeta(libds,outds=work.dsmeta);
|
||||||
|
|
||||||
|
%local ds1 ds2;
|
||||||
|
data;run; %let ds1=&syslast;
|
||||||
|
data;run; %let ds2=&syslast;
|
||||||
|
|
||||||
|
/* setup the ODS capture */
|
||||||
|
ods output attributes=&ds1 enginehost=&ds2;
|
||||||
|
|
||||||
|
/* export the metadata */
|
||||||
|
proc contents data=&libds;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* load it into a single table */
|
||||||
|
data &outds (keep=ods_table name value);
|
||||||
|
length ods_table $10 name label2 label1 label $100
|
||||||
|
value cvalue cvalue1 cvalue2 $1000
|
||||||
|
nvalue nvalue1 nvalue2 8;
|
||||||
|
if _n_=1 then call missing (of _all_);
|
||||||
|
* putlog (_all_)(=);
|
||||||
|
set &ds1 (in=atrs) &ds2 (in=eng);
|
||||||
|
if atrs then do;
|
||||||
|
ods_table='ATTRIBUTES';
|
||||||
|
name=coalescec(label1,label);
|
||||||
|
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||||
|
output;
|
||||||
|
if label2 ne '' then do;
|
||||||
|
name=label2;
|
||||||
|
value=coalescec(cvalue2,put(nvalue2,best.));
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
else if eng then do;
|
||||||
|
ods_table='ENGINEHOST';
|
||||||
|
name=coalescec(label1,label);
|
||||||
|
value=coalescec(cvalue1,cvalue,put(coalesce(nvalue1,nvalue),best.));
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
drop table &ds1, &ds2;
|
||||||
|
|
||||||
|
%mend mp_dsmeta;
|
||||||
|
|
||||||
@@ -6,7 +6,8 @@
|
|||||||
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
|
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
|
||||||
graceful service exit (configurable).
|
graceful service exit (configurable).
|
||||||
|
|
||||||
Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io).
|
Used for dynamic filtering in [Data Controller for SAS®](
|
||||||
|
https://datacontroller.io).
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@@ -91,7 +92,37 @@ data &outds;
|
|||||||
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
||||||
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
||||||
set &inds;
|
set &inds;
|
||||||
length reason_cd $4032;
|
length reason_cd $4032 vtype $1 vnum dsid 8 tmp $4000;
|
||||||
|
drop tmp;
|
||||||
|
|
||||||
|
/* quick check to ensure column exists */
|
||||||
|
if upcase(VARIABLE_NM) not in
|
||||||
|
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
|
||||||
|
then do;
|
||||||
|
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
|
||||||
|
putlog REASON_CD= VARIABLE_NM=;
|
||||||
|
call symputx('reason_cd',reason_cd,'l');
|
||||||
|
call symputx('nobs',_n_,'l');
|
||||||
|
output;
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
|
||||||
|
/* need to open the dataset to get the column type */
|
||||||
|
dsid=open("&targetds","i");
|
||||||
|
if dsid>0 then do;
|
||||||
|
vnum=varnum(dsid,VARIABLE_NM);
|
||||||
|
if vnum<1 then do;
|
||||||
|
/* should not happen as was also tested for above */
|
||||||
|
REASON_CD=cats("Variable (",VARIABLE_NM,") not found in &targetds");
|
||||||
|
putlog REASON_CD= dsid=;
|
||||||
|
call symputx('reason_cd',reason_cd,'l');
|
||||||
|
call symputx('nobs',_n_,'l');
|
||||||
|
output;
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
/* now we can get the type */
|
||||||
|
else vtype=vartype(dsid,vnum);
|
||||||
|
end;
|
||||||
|
|
||||||
/* closed list checks */
|
/* closed list checks */
|
||||||
if GROUP_LOGIC not in ('AND','OR') then do;
|
if GROUP_LOGIC not in ('AND','OR') then do;
|
||||||
@@ -115,17 +146,8 @@ data &outds;
|
|||||||
call symputx('nobs',_n_,'l');
|
call symputx('nobs',_n_,'l');
|
||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
if upcase(VARIABLE_NM) not in
|
|
||||||
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
|
|
||||||
then do;
|
|
||||||
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
|
|
||||||
putlog REASON_CD= VARIABLE_NM=;
|
|
||||||
call symputx('reason_cd',reason_cd,'l');
|
|
||||||
call symputx('nobs',_n_,'l');
|
|
||||||
output;
|
|
||||||
end;
|
|
||||||
if OPERATOR_NM not in
|
if OPERATOR_NM not in
|
||||||
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
|
('=','>','<','<=','>=','NE','GE','LE','BETWEEN','IN','NOT IN','CONTAINS')
|
||||||
then do;
|
then do;
|
||||||
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
|
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
|
||||||
putlog REASON_CD= OPERATOR_NM=;
|
putlog REASON_CD= OPERATOR_NM=;
|
||||||
@@ -134,9 +156,22 @@ data &outds;
|
|||||||
output;
|
output;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
/* special missing logic */
|
||||||
|
if vtype='N'
|
||||||
|
and OPERATOR_NM in ('=','>','<','<=','>=','NE','GE','LE')
|
||||||
|
and cats(upcase(raw_value)) in (
|
||||||
|
'.','.A','.B','.C','.D','.E','.F','.G','.H','.I','.J','.K','.L','.M','.N'
|
||||||
|
'.N','.O','.P','.Q','.R','.S','.T','.U','.V','.W','.X','.Y','.Z','._'
|
||||||
|
)
|
||||||
|
then do;
|
||||||
|
/* valid numeric - exit data step loop */
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
|
||||||
/* special logic */
|
/* special logic */
|
||||||
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
|
if OPERATOR_NM in ('IN','NOT IN','BETWEEN') then do;
|
||||||
else if OPERATOR_NM in ('IN','NOT IN') then do;
|
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ',',');
|
||||||
|
else do;
|
||||||
if substr(raw_value,1,1) ne '('
|
if substr(raw_value,1,1) ne '('
|
||||||
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
||||||
then do;
|
then do;
|
||||||
@@ -148,6 +183,19 @@ data &outds;
|
|||||||
end;
|
end;
|
||||||
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
||||||
end;
|
end;
|
||||||
|
/* we now have a comma seperated list of values */
|
||||||
|
if vtype='N' then do i=1 to countc(raw_value1, ',')+1;
|
||||||
|
tmp=scan(raw_value1,i,',');
|
||||||
|
if cats(tmp) ne '.' and input(tmp, ?? 8.) eq . then do;
|
||||||
|
REASON_CD='Non Numeric value provided';
|
||||||
|
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
||||||
|
call symputx('reason_cd',reason_cd,'l');
|
||||||
|
call symputx('nobs',_n_,'l');
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
return;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
else raw_value1=raw_value;
|
else raw_value1=raw_value;
|
||||||
|
|
||||||
/* remove nested literals eg '' */
|
/* remove nested literals eg '' */
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ filename &outref temp;
|
|||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
|
proc sort data=&inds;
|
||||||
|
by SUBGROUP_ID;
|
||||||
|
run;
|
||||||
data _null_;
|
data _null_;
|
||||||
file &outref lrecl=32800;
|
file &outref lrecl=32800;
|
||||||
set &inds end=last;
|
set &inds end=last;
|
||||||
|
|||||||
@@ -8,8 +8,13 @@
|
|||||||
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
|
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
|
||||||
https://datacontroller.io).
|
https://datacontroller.io).
|
||||||
|
|
||||||
|
A more recent feature of this macro is the ability to support filter queries
|
||||||
|
on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
|
||||||
|
parameter - where the "ds" in this case is the catalog name.
|
||||||
|
|
||||||
@param [in] libds= The target dataset to be filtered (lib should be assigned)
|
|
||||||
|
@param [in] libds= The target dataset to be filtered (lib should be assigned).
|
||||||
|
If filtering a format catalog, add the following suffix: `-FC`.
|
||||||
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
|
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
|
||||||
be validated. Has the following format:
|
be validated. Has the following format:
|
||||||
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
|
||||||
@@ -45,6 +50,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mddl_sas_cntlout.sas
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@li mf_getvalue.sas
|
@li mf_getvalue.sas
|
||||||
@li mf_islibds.sas
|
@li mf_islibds.sas
|
||||||
@@ -78,7 +84,10 @@
|
|||||||
%put &sysmacroname entry vars:;
|
%put &sysmacroname entry vars:;
|
||||||
%put _local_;
|
%put _local_;
|
||||||
|
|
||||||
%local ds1 ds2 ds3 ds4 filter_hash;
|
%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%let orig_libds=&libds;
|
||||||
|
|
||||||
%mp_abort(iftrue= (&syscc ne 0)
|
%mp_abort(iftrue= (&syscc ne 0)
|
||||||
,mac=mp_filterstore
|
,mac=mp_filterstore
|
||||||
,msg=%str(syscc=&syscc on macro entry)
|
,msg=%str(syscc=&syscc on macro entry)
|
||||||
@@ -96,12 +105,27 @@
|
|||||||
,msg=%str(Invalid lock_table value: &lock_table)
|
,msg=%str(Invalid lock_table value: &lock_table)
|
||||||
)
|
)
|
||||||
|
|
||||||
/* validate query */
|
/**
|
||||||
|
* validate query
|
||||||
|
* use format catalog export, if a format
|
||||||
|
*/
|
||||||
|
%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
|
||||||
|
%let libds=%scan(&libds,1,-); /* chop off -FC extension */
|
||||||
|
%let ds0=%mf_getuniquename(prefix=fmtds_);
|
||||||
|
%let libds=&ds0;
|
||||||
|
/*
|
||||||
|
There is no need to export the entire format catalog here - the validations
|
||||||
|
are done against the data model, not the data values. So we can simply
|
||||||
|
hardcode the structure based on the cntlout dataset.
|
||||||
|
*/
|
||||||
|
%mddl_sas_cntlout(libds=&ds0)
|
||||||
|
|
||||||
|
%end;
|
||||||
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
|
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
|
||||||
|
|
||||||
/* hash the result */
|
/* hash the result */
|
||||||
%let ds1=%mf_getuniquename(prefix=hashds);
|
%let ds1=%mf_getuniquename(prefix=hashds);
|
||||||
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
|
%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
|
||||||
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
|
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
|
||||||
%if &mdebug=1 %then %do;
|
%if &mdebug=1 %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -132,7 +156,7 @@ run;
|
|||||||
%let ds3=%mf_getuniquename(prefix=filtersum);
|
%let ds3=%mf_getuniquename(prefix=filtersum);
|
||||||
data work.&ds3;
|
data work.&ds3;
|
||||||
if 0 then set &filter_summary;
|
if 0 then set &filter_summary;
|
||||||
filter_table=symget('libds');
|
filter_table="&orig_libds";
|
||||||
filter_hash="&filter_hash";
|
filter_hash="&filter_hash";
|
||||||
PROCESSED_DTTM=%sysfunc(datetime());
|
PROCESSED_DTTM=%sysfunc(datetime());
|
||||||
output;
|
output;
|
||||||
|
|||||||
@@ -40,6 +40,22 @@
|
|||||||
%let lib=%upcase(&lib);
|
%let lib=%upcase(&lib);
|
||||||
%let ds=%upcase(&ds);
|
%let ds=%upcase(&ds);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cater for environments where sashelp.vcncolu is not available
|
||||||
|
*/
|
||||||
|
%if %sysfunc(exist(sashelp.vcncolu,view))=0 %then %do;
|
||||||
|
proc sql;
|
||||||
|
create table &outds(
|
||||||
|
libref char(8)
|
||||||
|
,TABLE_NAME char(32)
|
||||||
|
,constraint_type char(8) label='Constraint Type'
|
||||||
|
,constraint_name char(32) label='Constraint Name'
|
||||||
|
,column_name char(32) label='Column'
|
||||||
|
,constraint_order num
|
||||||
|
);
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Neither dictionary tables nor sashelp provides a constraint order column,
|
* Neither dictionary tables nor sashelp provides a constraint order column,
|
||||||
* however they DO arrive in the correct order. So, create the col.
|
* however they DO arrive in the correct order. So, create the col.
|
||||||
@@ -78,8 +94,11 @@ create table &outds as
|
|||||||
/**
|
/**
|
||||||
* We cannot apply this clause to the underlying dictionary table. See:
|
* We cannot apply this clause to the underlying dictionary table. See:
|
||||||
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
|
||||||
|
* cannot use`where calculated libref="&lib"` either as it will STILL execute
|
||||||
|
* all the underlying constraint queries, causing exception errors in some
|
||||||
|
* cases: https://github.com/sasjs/core/issues/283
|
||||||
*/
|
*/
|
||||||
where calculated libref="&lib"
|
where a.TABLE_CATALOG="&lib"
|
||||||
%if "&ds" ne "" %then %do;
|
%if "&ds" ne "" %then %do;
|
||||||
and upcase(a.TABLE_NAME)="&ds"
|
and upcase(a.TABLE_NAME)="&ds"
|
||||||
and upcase(b.TABLE_NAME)="&ds"
|
and upcase(b.TABLE_NAME)="&ds"
|
||||||
|
|||||||
@@ -130,13 +130,13 @@ run;
|
|||||||
|
|
||||||
%local x curds;
|
%local x curds;
|
||||||
%if &flavour=SAS %then %do;
|
%if &flavour=SAS %then %do;
|
||||||
|
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||||
|
%let curds=%scan(&dsnlist,&x);
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
|
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
|
||||||
put "proc sql;";
|
put "proc sql;";
|
||||||
run;
|
run;
|
||||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
|
||||||
%let curds=%scan(&dsnlist,&x);
|
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fref mod;
|
file &fref mod;
|
||||||
length lab $1024 typ $20;
|
length lab $1024 typ $20;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|
|||||||
|`WHICHPATH `|`**OTHER** `|`**OTHER** `|`big fat problem if not path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|`O `|` `|` `|` `|` `|
|
|`WHICHPATH `|`**OTHER** `|`**OTHER** `|`big fat problem if not path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|`O `|` `|` `|` `|` `|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mddl_sas_cntlout.sas
|
||||||
@li mf_dedup.sas
|
@li mf_dedup.sas
|
||||||
@li mf_getfmtlist.sas
|
@li mf_getfmtlist.sas
|
||||||
@li mf_getfmtname.sas
|
@li mf_getfmtname.sas
|
||||||
@@ -94,30 +95,7 @@ create table &outsummary as
|
|||||||
|
|
||||||
%if "&outdetail" ne "0" %then %do;
|
%if "&outdetail" ne "0" %then %do;
|
||||||
/* ensure base table always exists */
|
/* ensure base table always exists */
|
||||||
proc sql;
|
%mddl_sas_cntlout(libds=&outdetail)
|
||||||
create table &outdetail(
|
|
||||||
FMTNAME char(32) label='Format name'
|
|
||||||
,START char(16) label='Starting value for format'
|
|
||||||
,END char(16) label='Ending value for format'
|
|
||||||
,LABEL char(256) label='Format value label'
|
|
||||||
,MIN num length=3 label='Minimum length'
|
|
||||||
,MAX num length=3 label='Maximum length'
|
|
||||||
,DEFAULT num length=3 label='Default length'
|
|
||||||
,LENGTH num length=3 label='Format length'
|
|
||||||
,FUZZ num label='Fuzz value'
|
|
||||||
,PREFIX char(2) label='Prefix characters'
|
|
||||||
,MULT num label='Multiplier'
|
|
||||||
,FILL char(1) label='Fill character'
|
|
||||||
,NOEDIT num length=3 label='Is picture string noedit?'
|
|
||||||
,TYPE char(1) label='Type of format'
|
|
||||||
,SEXCL char(1) label='Start exclusion'
|
|
||||||
,EEXCL char(1) label='End exclusion'
|
|
||||||
,HLO char(13) label='Additional information'
|
|
||||||
,DECSEP char(1) label='Decimal separator'
|
|
||||||
,DIG3SEP char(1) label='Three-digit separator'
|
|
||||||
,DATATYPE char(8) label='Date/time/datetime?'
|
|
||||||
,LANGUAGE char(8) label='Language for date strings'
|
|
||||||
);
|
|
||||||
/* grab the location of each format */
|
/* grab the location of each format */
|
||||||
%let fmtcnt=0;
|
%let fmtcnt=0;
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -134,6 +112,10 @@ create table &outsummary as
|
|||||||
proc format library=&&fmtloc&i CNTLOUT=&tempds;
|
proc format library=&&fmtloc&i CNTLOUT=&tempds;
|
||||||
select &&fmtname&i;
|
select &&fmtname&i;
|
||||||
run;
|
run;
|
||||||
|
data &tempds;
|
||||||
|
if 0 then set &outdetail;
|
||||||
|
set &tempds;
|
||||||
|
run;
|
||||||
proc append base=&outdetail data=&tempds ;
|
proc append base=&outdetail data=&tempds ;
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mcf_length.sas
|
@li mcf_length.sas
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
|
@li mf_getvarcount.sas
|
||||||
@li mf_getvarlist.sas
|
@li mf_getvarlist.sas
|
||||||
@li mf_getvartype.sas
|
@li mf_getvartype.sas
|
||||||
@li mf_getvarformat.sas
|
@li mf_getvarformat.sas
|
||||||
@@ -60,7 +61,7 @@
|
|||||||
,outds=work.mp_getmaxvarlengths
|
,outds=work.mp_getmaxvarlengths
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%local vars prefix x var fmt;
|
%local vars prefix x var fmt srcds;
|
||||||
%let vars=%mf_getvarlist(libds=&libds);
|
%let vars=%mf_getvarlist(libds=&libds);
|
||||||
%let prefix=%substr(%mf_getuniquename(),1,25);
|
%let prefix=%substr(%mf_getuniquename(),1,25);
|
||||||
%let num2char=%upcase(&num2char);
|
%let num2char=%upcase(&num2char);
|
||||||
@@ -70,6 +71,24 @@
|
|||||||
%mcf_length(wrap=YES, insert_cmplib=YES)
|
%mcf_length(wrap=YES, insert_cmplib=YES)
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
%if &num2char=NO
|
||||||
|
and ("%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5")
|
||||||
|
and %mf_getvarcount(&libds,typefilter=N) gt 0
|
||||||
|
%then %do;
|
||||||
|
/* custom functions not supported in summary operations */
|
||||||
|
%let srcds=%mf_getuniquename();
|
||||||
|
data &srcds/view=&srcds;
|
||||||
|
set &libds;
|
||||||
|
%do x=1 %to %sysfunc(countw(&vars,%str( )));
|
||||||
|
%let var=%scan(&vars,&x);
|
||||||
|
%if %mf_getvartype(&libds,&var)=N %then %do;
|
||||||
|
&prefix.&x=mcf_length(&var);
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
%else %let srcds=&libds;
|
||||||
|
|
||||||
proc sql;
|
proc sql;
|
||||||
create table &outds (rename=(
|
create table &outds (rename=(
|
||||||
%do x=1 %to %sysfunc(countw(&vars,%str( )));
|
%do x=1 %to %sysfunc(countw(&vars,%str( )));
|
||||||
@@ -93,11 +112,16 @@ create table &outds (rename=(
|
|||||||
max(lengthn(put(&var,&fmt))) as &prefix.&x
|
max(lengthn(put(&var,&fmt))) as &prefix.&x
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
%else %do;
|
||||||
|
%if "%substr(&sysver,1,1)"="4" or "%substr(&sysver,1,1)"="5" %then %do;
|
||||||
|
max(&prefix.&x) as &prefix.&x
|
||||||
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
max(mcf_length(&var)) as &prefix.&x
|
max(mcf_length(&var)) as &prefix.&x
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
from &libds;
|
%end;
|
||||||
|
from &srcds;
|
||||||
|
|
||||||
proc transpose data=&outds
|
proc transpose data=&outds
|
||||||
out=&outds(rename=(_name_=NAME COL1=MAXLEN));
|
out=&outds(rename=(_name_=NAME COL1=MAXLEN));
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
@param [out] outds= (work.mp_getpk) The name of the output table to create.
|
@param [out] outds= (work.mp_getpk) The name of the output table to create.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_existfeature.sas
|
||||||
@li mf_getengine.sas
|
@li mf_getengine.sas
|
||||||
@li mf_getschema.sas
|
@li mf_getschema.sas
|
||||||
@li mp_dropmembers.sas
|
@li mp_dropmembers.sas
|
||||||
@@ -55,7 +56,8 @@
|
|||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
|
||||||
%local engine schema ds1 ds2 ds3 dsn tabs1 tabs2 sum pk4sure pkdefault finalpks;
|
%local engine schema ds1 ds2 ds3 dsn tabs1 tabs2 sum pk4sure pkdefault finalpks
|
||||||
|
pkfromindex;
|
||||||
|
|
||||||
%let lib=%upcase(&lib);
|
%let lib=%upcase(&lib);
|
||||||
%let ds=%upcase(&ds);
|
%let ds=%upcase(&ds);
|
||||||
@@ -70,6 +72,7 @@
|
|||||||
%let sum=%mf_getuniquename(prefix=getpk_sum);
|
%let sum=%mf_getuniquename(prefix=getpk_sum);
|
||||||
%let pk4sure=%mf_getuniquename(prefix=getpk_pk4sure);
|
%let pk4sure=%mf_getuniquename(prefix=getpk_pk4sure);
|
||||||
%let pkdefault=%mf_getuniquename(prefix=getpk_pkdefault);
|
%let pkdefault=%mf_getuniquename(prefix=getpk_pkdefault);
|
||||||
|
%let pkfromindex=%mf_getuniquename(prefix=getpk_pkfromindex);
|
||||||
%let finalpks=%mf_getuniquename(prefix=getpk_finalpks);
|
%let finalpks=%mf_getuniquename(prefix=getpk_finalpks);
|
||||||
|
|
||||||
%local dbg;
|
%local dbg;
|
||||||
@@ -180,9 +183,23 @@ create table &ds1 as
|
|||||||
and a.constraint_name=b.constraint_name
|
and a.constraint_name=b.constraint_name
|
||||||
order by 1,2,3,4;
|
order by 1,2,3,4;
|
||||||
|
|
||||||
|
/* extract cols from the relevant unique INDEXES */
|
||||||
|
create table &pkfromindex as
|
||||||
|
select libname as libref
|
||||||
|
,memname as table_name
|
||||||
|
,indxname as constraint_name
|
||||||
|
,indxpos as constraint_order
|
||||||
|
,name
|
||||||
|
from dictionary.indexes
|
||||||
|
where nomiss='yes' and unique='yes' and upcase(libname)="&lib"
|
||||||
|
%if &ds ne 0 %then %do;
|
||||||
|
and upcase(memname)="&ds"
|
||||||
|
%end;
|
||||||
|
order by 1,2,3,4;
|
||||||
|
|
||||||
/* create one table */
|
/* create one table */
|
||||||
data &finalpks;
|
data &finalpks;
|
||||||
set &pkdefault &pk4sure ;
|
set &pkdefault &pk4sure &pkfromindex;
|
||||||
pk_ind=1;
|
pk_ind=1;
|
||||||
/* if there are multiple unique constraints, take the first */
|
/* if there are multiple unique constraints, take the first */
|
||||||
by libref table_name constraint_name;
|
by libref table_name constraint_name;
|
||||||
@@ -213,7 +230,12 @@ create table work.&tabs1 as select
|
|||||||
libname as libref
|
libname as libref
|
||||||
,upcase(memname) as dsn
|
,upcase(memname) as dsn
|
||||||
,memtype
|
,memtype
|
||||||
|
%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;
|
||||||
,dbms_memtype
|
,dbms_memtype
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
,'n/a' as dbms_memtype format=$32.
|
||||||
|
%end;
|
||||||
,typemem
|
,typemem
|
||||||
,memlabel
|
,memlabel
|
||||||
,nvar
|
,nvar
|
||||||
|
|||||||
@@ -48,6 +48,11 @@
|
|||||||
outfile=0
|
outfile=0
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%if "%substr(&sysver.XX,1,4)"="V.04" %then %do;
|
||||||
|
%put %str(ERR)OR: Viya 4 does not support the IO library in lua;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
%ml_gsubfile()
|
%ml_gsubfile()
|
||||||
|
|
||||||
%mend mp_gsubfile;
|
%mend mp_gsubfile;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Returns a unique hash for a dataset
|
@brief Returns a unique hash for a dataset
|
||||||
@details Ignores metadata attributes, used only to hash values. Compared
|
@details Ignores metadata attributes, used only to hash values. If used to
|
||||||
datasets must be in the same order.
|
compare datasets, they must have their columns and rows in the same order.
|
||||||
|
|
||||||
%mp_hashdataset(sashelp.class,outds=myhash)
|
%mp_hashdataset(sashelp.class,outds=myhash)
|
||||||
|
|
||||||
@@ -11,19 +11,23 @@
|
|||||||
put hashkey=;
|
put hashkey=;
|
||||||
run;
|
run;
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getattrn.sas
|
@li mf_getattrn.sas
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@li mf_getvarlist.sas
|
@li mf_getvarlist.sas
|
||||||
@li mf_getvartype.sas
|
@li mp_md5.sas
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_hashdataset.test.sas
|
||||||
|
@li mp_hashdirectory.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 [in] salt= Provide a salt (could be, for instance, the dataset name)
|
||||||
@param [in] iftrue= A condition under which the macro should be executed.
|
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||||
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
|
@param [out] outds= (work._data_) 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)
|
||||||
|hashkey:$32.|
|
|hashkey:$32.|
|
||||||
|---|
|
|---|
|
||||||
@@ -35,46 +39,51 @@
|
|||||||
|
|
||||||
%macro mp_hashdataset(
|
%macro mp_hashdataset(
|
||||||
libds,
|
libds,
|
||||||
outds=,
|
outds=work._data_,
|
||||||
salt=,
|
salt=,
|
||||||
iftrue=%str(1=1)
|
iftrue=%str(1=1)
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local keyvar /* roll up the md5 */
|
||||||
|
prevkeyvar /* retain prev record md5 */
|
||||||
|
lastvar /* last var in input ds */
|
||||||
|
cvars nvars;
|
||||||
|
|
||||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
|
/* avoid naming conflict for hash key vars */
|
||||||
|
%let keyvar=%mf_getuniquename();
|
||||||
|
%let prevkeyvar=%mf_getuniquename();
|
||||||
|
%let lastvar=%mf_getuniquename();
|
||||||
|
|
||||||
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
|
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
|
||||||
%put %str(WARN)ING: Dataset &libds is empty, or is not a dataset;
|
data &outds;
|
||||||
|
length hashkey $32;
|
||||||
|
hashkey=put(md5("&salt"),$hex32.);
|
||||||
|
output;
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
%put &sysmacroname: Dataset &libds is empty, or is not a dataset;
|
||||||
|
%put &sysmacroname: hashkey of &outds is based on salt (&salt) only;
|
||||||
%end;
|
%end;
|
||||||
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
|
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
|
||||||
%put %str(ERR)OR: Dataset &libds is not a dataset;
|
%put %str(ERR)OR: Dataset &libds is not a dataset;
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%local keyvar /* roll up the md5 */
|
data &outds(rename=(&keyvar=hashkey) keep=&keyvar)
|
||||||
prevkeyvar /* retain prev record md5 */
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
lastvar /* last var in input ds */
|
/nonote2err
|
||||||
varlist var i;
|
%end;
|
||||||
/* avoid naming conflict for hash key vars */
|
;
|
||||||
%let keyvar=%mf_getuniquename();
|
|
||||||
%let prevkeyvar=%mf_getuniquename();
|
|
||||||
%let lastvar=%mf_getuniquename();
|
|
||||||
%let varlist=%mf_getvarlist(&libds);
|
|
||||||
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
|
|
||||||
length &prevkeyvar &keyvar $32;
|
length &prevkeyvar &keyvar $32;
|
||||||
retain &prevkeyvar "%sysfunc(md5(%str(&salt)),$hex32.)";
|
retain &prevkeyvar;
|
||||||
|
if _n_=1 then &prevkeyvar=put(md5("&salt"),$hex32.);
|
||||||
set &libds end=&lastvar;
|
set &libds end=&lastvar;
|
||||||
/* hash should include previous row */
|
/* hash should include previous row */
|
||||||
&keyvar=put(md5(&prevkeyvar
|
&keyvar=%mp_md5(
|
||||||
/* loop every column, hashing every individual value */
|
cvars=%mf_getvarlist(&libds,typefilter=C) &prevkeyvar,
|
||||||
%do i=1 %to %sysfunc(countw(&varlist));
|
nvars=%mf_getvarlist(&libds,typefilter=N)
|
||||||
%let var=%scan(&varlist,&i,%str( ));
|
);
|
||||||
%if %mf_getvartype(&libds,&var)=C %then %do;
|
|
||||||
!!put(md5(trim(&var)),$hex32.)
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
),$hex32.);
|
|
||||||
&prevkeyvar=&keyvar;
|
&prevkeyvar=&keyvar;
|
||||||
if &lastvar then output;
|
if &lastvar then output;
|
||||||
run;
|
run;
|
||||||
|
|||||||
162
base/mp_hashdirectory.sas
Normal file
162
base/mp_hashdirectory.sas
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Returns a unique hash for each file in a directory
|
||||||
|
@details Hashes each file in each directory, and then hashes the hashes to
|
||||||
|
create a hash for each directory also.
|
||||||
|
|
||||||
|
This makes use of the new `hashing_file()` and `hashing` functions, available
|
||||||
|
since 9.4m6. Interestingly, these can even be used in pure macro, eg:
|
||||||
|
|
||||||
|
%put %sysfunc(hashing_file(md5,/path/to/file.blob,0));
|
||||||
|
|
||||||
|
Actual usage:
|
||||||
|
|
||||||
|
%let fpath=/some/directory;
|
||||||
|
|
||||||
|
%mp_hashdirectory(&fpath,outds=myhash,maxdepth=2)
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set work.myhash;
|
||||||
|
put (_all_)(=);
|
||||||
|
run;
|
||||||
|
|
||||||
|
Whilst files are hashed in their entirety, the logic for creating a folder
|
||||||
|
hash is as follows:
|
||||||
|
|
||||||
|
@li Sort the files by filename (case sensitive, uppercase then lower)
|
||||||
|
@li Take the first 100 hashes, concatenate and hash
|
||||||
|
@li Concatenate this hash with another 100 hashes and hash again
|
||||||
|
@li Continue until the end of the folder. This is the folder hash
|
||||||
|
@li If a folder contains other folders, start from the bottom of the tree -
|
||||||
|
the folder hashes cascade upwards so you know immediately if there is a
|
||||||
|
change in a sub/sub directory
|
||||||
|
@li If the folder has no content (empty) then it is ignored. No hash created.
|
||||||
|
@li If the file is empty, it is also ignored / no hash created.
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_dirlist.sas
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mp_hashdataset.sas
|
||||||
|
@li mp_hashdirectory.test.sas
|
||||||
|
@li mp_md5.sas
|
||||||
|
|
||||||
|
@param [in] inloc Full filepath of the file to be hashed (unquoted)
|
||||||
|
@param [in] iftrue= (1=1) A condition under which the macro should be executed
|
||||||
|
@param [in] maxdepth= (0) Set to a positive integer to indicate the level of
|
||||||
|
subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
|
||||||
|
recursion, set to MAX.
|
||||||
|
@param [in] method= (MD5) the hashing method to use. Available options:
|
||||||
|
@li MD5
|
||||||
|
@li SH1
|
||||||
|
@li SHA256
|
||||||
|
@li SHA384
|
||||||
|
@li SHA512
|
||||||
|
@li CRC32
|
||||||
|
@param [out] outds= (work.mp_hashdirectory) The output dataset. Contains:
|
||||||
|
@li directory - the parent folder
|
||||||
|
@li file_hash - the hash output
|
||||||
|
@li hash_duration - how long the hash took (first hash always takes longer)
|
||||||
|
@li file_path - /full/path/to/each/file.ext
|
||||||
|
@li file_or_folder - contains either "file" or "folder"
|
||||||
|
@li level - the depth of the directory (top level is 0)
|
||||||
|
|
||||||
|
@version 9.4m6
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_hashdirectory(inloc,
|
||||||
|
outds=work.mp_hashdirectory,
|
||||||
|
method=MD5,
|
||||||
|
maxdepth=0,
|
||||||
|
iftrue=%str(1=1)
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local curlevel tempds ;
|
||||||
|
|
||||||
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
|
|
||||||
|
/* get the directory listing */
|
||||||
|
%mp_dirlist(path=&inloc, outds=&outds, maxdepth=&maxdepth, showparent=YES)
|
||||||
|
|
||||||
|
/* create the hashes */
|
||||||
|
data &outds;
|
||||||
|
set &outds (rename=(filepath=file_path));
|
||||||
|
length FILE_HASH $32 HASH_DURATION 8;
|
||||||
|
keep directory file_hash hash_duration file_path file_or_folder level;
|
||||||
|
|
||||||
|
ts=datetime();
|
||||||
|
if file_or_folder='file' then do;
|
||||||
|
/* if file is empty, hashing_file will break - so ignore / delete */
|
||||||
|
length fname val $8;
|
||||||
|
drop fname val fid is_empty;
|
||||||
|
rc=filename(fname,file_path);
|
||||||
|
fid=fopen(fname);
|
||||||
|
if fid > 0 then do;
|
||||||
|
rc=fread(fid);
|
||||||
|
is_empty=fget(fid,val);
|
||||||
|
end;
|
||||||
|
rc=fclose(fid);
|
||||||
|
rc=filename(fname);
|
||||||
|
if is_empty ne 0 then delete;
|
||||||
|
else file_hash=hashing_file("&method",cats(file_path),0);
|
||||||
|
end;
|
||||||
|
hash_duration=datetime()-ts;
|
||||||
|
run;
|
||||||
|
|
||||||
|
proc sort data=&outds ;
|
||||||
|
by descending level directory file_path;
|
||||||
|
run;
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set &outds;
|
||||||
|
call symputx('maxlevel',level,'l');
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* now hash the hashes to populate folder hashes, starting from the bottom */
|
||||||
|
%do curlevel=&maxlevel %to 0 %by -1;
|
||||||
|
data work._data_ (keep=directory file_hash);
|
||||||
|
set &outds;
|
||||||
|
where level=&curlevel;
|
||||||
|
by descending level directory file_path;
|
||||||
|
length str $32767 tmp_hash $32;
|
||||||
|
retain str tmp_hash ;
|
||||||
|
/* reset vars when starting a new directory */
|
||||||
|
if first.directory then do;
|
||||||
|
str='';
|
||||||
|
tmp_hash='';
|
||||||
|
i=0;
|
||||||
|
end;
|
||||||
|
/* hash each chunk of 100 file paths */
|
||||||
|
i+1;
|
||||||
|
str=cats(str,file_hash);
|
||||||
|
if mod(i,100)=0 or last.directory then do;
|
||||||
|
tmp_hash=hashing("&method",cats(tmp_hash,str));
|
||||||
|
str='';
|
||||||
|
end;
|
||||||
|
/* output the hash at directory level */
|
||||||
|
if last.directory then do;
|
||||||
|
file_hash=tmp_hash;
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
if last.level then stop;
|
||||||
|
run;
|
||||||
|
%let tempds=&syslast;
|
||||||
|
/* join the hash back into the main table */
|
||||||
|
proc sql undo_policy=none;
|
||||||
|
create table &outds as
|
||||||
|
select a.directory
|
||||||
|
,coalesce(b.file_hash,a.file_hash) as file_hash
|
||||||
|
,a.hash_duration
|
||||||
|
,a.file_path
|
||||||
|
,a.file_or_folder
|
||||||
|
,a.level
|
||||||
|
from &outds a
|
||||||
|
left join &tempds b
|
||||||
|
on a.file_path=b.directory
|
||||||
|
order by level desc, directory, file_path;
|
||||||
|
drop table &tempds;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mend mp_hashdirectory;
|
||||||
@@ -67,7 +67,7 @@ options
|
|||||||
validvarname=V7 /* avoid special characters etc in variable names */
|
validvarname=V7 /* avoid special characters etc in variable names */
|
||||||
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
|
varinitchk=%str(ERR)OR /* avoid data mistakes from variable name typos */
|
||||||
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
|
varlenchk=%str(ERR)OR /* fail hard if truncation (data loss) can result */
|
||||||
%if %substr(&sysver,1,1) ne 4 %then %do;
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
noautocorrect /* disallow misspelled procedure names */
|
noautocorrect /* disallow misspelled procedure names */
|
||||||
dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
|
dsoptions=note2err /* undocumented - convert bad NOTEs to ERRs */
|
||||||
%end;
|
%end;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
/**
|
/**
|
||||||
@file mp_jsonout.sas
|
@file mp_jsonout.sas
|
||||||
@brief Writes JSON in SASjs format to a fileref
|
@brief Writes JSON in SASjs format to a fileref
|
||||||
@details PROC JSON is faster but will produce errs like the ones below if
|
@details This macro can be used to OPEN a JSON stream and send one or more
|
||||||
|
tables as arrays of rows, where each row can be an object or a nested array.
|
||||||
|
|
||||||
|
There are two engines available - DATASTEP or PROCJSON.
|
||||||
|
|
||||||
|
PROC JSON is fast but will produce errs like the ones below if
|
||||||
special chars are encountered.
|
special chars are encountered.
|
||||||
|
|
||||||
> (ERR)OR: Some code points did not transcode.
|
> (ERR)OR: Some code points did not transcode.
|
||||||
@@ -12,6 +17,10 @@
|
|||||||
|
|
||||||
If this happens, try running with ENGINE=DATASTEP.
|
If this happens, try running with ENGINE=DATASTEP.
|
||||||
|
|
||||||
|
The DATASTEP engine is used to handle special SAS missing numerics, and
|
||||||
|
can also convert entire datasets to formatted values. Output JSON is always
|
||||||
|
in UTF-8.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
filename tmp temp;
|
filename tmp temp;
|
||||||
@@ -19,7 +28,7 @@
|
|||||||
|
|
||||||
%mp_jsonout(OPEN,jref=tmp)
|
%mp_jsonout(OPEN,jref=tmp)
|
||||||
%mp_jsonout(OBJ,class,jref=tmp)
|
%mp_jsonout(OBJ,class,jref=tmp)
|
||||||
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=YES)
|
%mp_jsonout(OBJ,class,dslabel=class2,jref=tmp,showmeta=Y)
|
||||||
%mp_jsonout(CLOSE,jref=tmp)
|
%mp_jsonout(CLOSE,jref=tmp)
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
@@ -46,12 +55,14 @@
|
|||||||
@li DATASTEP (more reliable when data has non standard characters)
|
@li DATASTEP (more reliable when data has non standard characters)
|
||||||
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
||||||
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
||||||
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
|
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
|
||||||
such as the column formats and types. The metadata is contained inside an
|
such as the column formats and types. The metadata is contained inside an
|
||||||
object with the same name as the table but prefixed with a dollar sign - ie,
|
object with the same name as the table but prefixed with a dollar sign - ie,
|
||||||
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
||||||
|
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
|
||||||
|
that should be converted to JSON
|
||||||
|
|
||||||
<h4> Related Macros <h4>
|
<h4> Related Files </h4>
|
||||||
@li mp_ds2fmtds.sas
|
@li mp_ds2fmtds.sas
|
||||||
|
|
||||||
@version 9.2
|
@version 9.2
|
||||||
@@ -59,25 +70,41 @@
|
|||||||
@source https://github.com/sasjs/core
|
@source https://github.com/sasjs/core
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
|
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y
|
||||||
,engine=DATASTEP
|
,engine=DATASTEP
|
||||||
,missing=NULL
|
,missing=NULL
|
||||||
,showmeta=NO
|
,showmeta=N
|
||||||
|
,maxobs=MAX
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local tempds colinfo fmtds i numcols;
|
%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval
|
||||||
|
tmpds1 tmpds2 tmpds3 tmpds4;
|
||||||
%let numcols=0;
|
%let numcols=0;
|
||||||
|
%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;);
|
||||||
|
|
||||||
%if &action=OPEN %then %do;
|
%if &action=OPEN %then %do;
|
||||||
options nobomfile;
|
options nobomfile;
|
||||||
data _null_;file &jref encoding='utf-8' ;
|
data _null_;file &jref encoding='utf-8' lrecl=200;
|
||||||
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
put '{"PROCESSED_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '"';
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if (&action=ARR or &action=OBJ) %then %do;
|
%else %if (&action=ARR or &action=OBJ) %then %do;
|
||||||
|
/* force variable names to always be uppercase in the JSON */
|
||||||
options validvarname=upcase;
|
options validvarname=upcase;
|
||||||
data _null_; file &jref encoding='utf-8' mod;
|
/* To avoid issues with _webout on EBI - such as encoding diffs and truncation
|
||||||
|
(https://support.sas.com/kb/49/325.html) we use temporary files */
|
||||||
|
filename _sjs1 temp lrecl=200 ;
|
||||||
|
data _null_; file _sjs1 encoding='utf-8';
|
||||||
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":";
|
||||||
|
run;
|
||||||
|
/* now write to _webout 1 char at a time */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs1 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs1 clear;
|
||||||
|
|
||||||
/* grab col defs */
|
/* grab col defs */
|
||||||
proc contents noprint data=&ds
|
proc contents noprint data=&ds
|
||||||
@@ -88,7 +115,7 @@
|
|||||||
by varnum;
|
by varnum;
|
||||||
run;
|
run;
|
||||||
/* move meta to mac vars */
|
/* move meta to mac vars */
|
||||||
data _null_;
|
data &colinfo;
|
||||||
if _n_=1 then call symputx('numcols',nobs,'l');
|
if _n_=1 then call symputx('numcols',nobs,'l');
|
||||||
set &colinfo end=last nobs=nobs;
|
set &colinfo end=last nobs=nobs;
|
||||||
name=upcase(name);
|
name=upcase(name);
|
||||||
@@ -99,7 +126,6 @@
|
|||||||
if format='' then fmt=cats('$',length,'.');
|
if format='' then fmt=cats('$',length,'.');
|
||||||
else if formatl=0 then fmt=cats(format,'.');
|
else if formatl=0 then fmt=cats(format,'.');
|
||||||
else fmt=cats(format,formatl,'.');
|
else fmt=cats(format,formatl,'.');
|
||||||
newlen=max(formatl,length);
|
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
typelong='num';
|
typelong='num';
|
||||||
@@ -107,23 +133,26 @@
|
|||||||
else if formatl=0 then fmt=cats(format,'.');
|
else if formatl=0 then fmt=cats(format,'.');
|
||||||
else if formatd=0 then fmt=cats(format,formatl,'.');
|
else if formatd=0 then fmt=cats(format,formatl,'.');
|
||||||
else fmt=cats(format,formatl,'.',formatd);
|
else fmt=cats(format,formatl,'.',formatd);
|
||||||
/* needs to be wide, for datetimes etc */
|
|
||||||
newlen=max(length,formatl,24);
|
|
||||||
end;
|
end;
|
||||||
/* 32 char unique name */
|
/* 32 char unique name */
|
||||||
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
||||||
|
|
||||||
call symputx(cats('name',_n_),name,'l');
|
call symputx(cats('name',_n_),name,'l');
|
||||||
call symputx(cats('newname',_n_),newname,'l');
|
call symputx(cats('newname',_n_),newname,'l');
|
||||||
call symputx(cats('len',_n_),newlen,'l');
|
|
||||||
call symputx(cats('length',_n_),length,'l');
|
call symputx(cats('length',_n_),length,'l');
|
||||||
call symputx(cats('fmt',_n_),fmt,'l');
|
call symputx(cats('fmt',_n_),fmt,'l');
|
||||||
call symputx(cats('type',_n_),type,'l');
|
call symputx(cats('type',_n_),type,'l');
|
||||||
call symputx(cats('typelong',_n_),typelong,'l');
|
call symputx(cats('typelong',_n_),typelong,'l');
|
||||||
call symputx(cats('label',_n_),coalescec(label,name),'l');
|
call symputx(cats('label',_n_),coalescec(label,name),'l');
|
||||||
|
/* overwritten when fmt=Y and a custom format exists in catalog */
|
||||||
|
if typelong='num' then call symputx(cats('fmtlen',_n_),200,'l');
|
||||||
|
else call symputx(cats('fmtlen',_n_),min(32767,ceil((length+10)*1.5)),'l');
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
%let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
proc sql;
|
||||||
|
select count(*) into: lastobs from &ds;
|
||||||
|
%if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs));
|
||||||
|
|
||||||
%if &engine=PROCJSON %then %do;
|
%if &engine=PROCJSON %then %do;
|
||||||
%if &missing=STRING %then %do;
|
%if &missing=STRING %then %do;
|
||||||
@@ -131,13 +160,25 @@
|
|||||||
%put &sysmacroname: Switching to DATASTEP engine;
|
%put &sysmacroname: Switching to DATASTEP engine;
|
||||||
%goto datastep;
|
%goto datastep;
|
||||||
%end;
|
%end;
|
||||||
data &tempds;set &ds;
|
data &tempds;
|
||||||
|
set &ds;
|
||||||
|
&stmt_obs;
|
||||||
%if &fmt=N %then format _numeric_ best32.;;
|
%if &fmt=N %then format _numeric_ best32.;;
|
||||||
/* PRETTY is necessary to avoid line truncation in large files */
|
/* PRETTY is necessary to avoid line truncation in large files */
|
||||||
proc json out=&jref pretty
|
filename _sjs2 temp lrecl=131068 encoding='utf-8';
|
||||||
|
proc json out=_sjs2 pretty
|
||||||
%if &action=ARR %then nokeys ;
|
%if &action=ARR %then nokeys ;
|
||||||
;export &tempds / nosastags fmtnumeric;
|
;export &tempds / nosastags fmtnumeric;
|
||||||
run;
|
run;
|
||||||
|
/* send back to webout */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs2 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs2 clear;
|
||||||
%end;
|
%end;
|
||||||
%else %if &engine=DATASTEP %then %do;
|
%else %if &engine=DATASTEP %then %do;
|
||||||
%datastep:
|
%datastep:
|
||||||
@@ -148,26 +189,99 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
%if &fmt=Y %then %do;
|
%if &fmt=Y %then %do;
|
||||||
data _data_;
|
/**
|
||||||
|
* Extract format definitions
|
||||||
|
* First, by getting library locations from dictionary.formats
|
||||||
|
* Then, by exporting the width using proc format
|
||||||
|
* Cannot use maxw from sashelp.vformat as not always populated
|
||||||
|
* Cannot use fmtinfo() as not supported in all flavours
|
||||||
|
*/
|
||||||
|
%let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
%let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32);
|
||||||
|
proc sql noprint;
|
||||||
|
create table &tmpds1 as
|
||||||
|
select cats(libname,'.',memname) as FMTCAT,
|
||||||
|
FMTNAME
|
||||||
|
from dictionary.formats
|
||||||
|
where fmttype='F' and libname is not null
|
||||||
|
and fmtname in (select format from &colinfo where format is not null)
|
||||||
|
order by 1;
|
||||||
|
create table &tmpds2(
|
||||||
|
FMTNAME char(32),
|
||||||
|
LENGTH num
|
||||||
|
);
|
||||||
|
%local catlist cat fmtlist i;
|
||||||
|
select distinct fmtcat into: catlist separated by ' ' from &tmpds1;
|
||||||
|
%do i=1 %to %sysfunc(countw(&catlist,%str( )));
|
||||||
|
%let cat=%scan(&catlist,&i,%str( ));
|
||||||
|
proc sql;
|
||||||
|
select distinct fmtname into: fmtlist separated by ' '
|
||||||
|
from &tmpds1 where fmtcat="&cat";
|
||||||
|
proc format lib=&cat cntlout=&tmpds3(keep=fmtname length);
|
||||||
|
select &fmtlist;
|
||||||
|
run;
|
||||||
|
proc sql;
|
||||||
|
insert into &tmpds2 select distinct fmtname,length from &tmpds3;
|
||||||
|
%end;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &tmpds4 as
|
||||||
|
select a.*, b.length as MAXW
|
||||||
|
from &colinfo a
|
||||||
|
left join &tmpds2 b
|
||||||
|
on cats(a.format)=cats(upcase(b.fmtname))
|
||||||
|
order by a.varnum;
|
||||||
|
data _null_;
|
||||||
|
set &tmpds4;
|
||||||
|
if not missing(maxw);
|
||||||
|
call symputx(
|
||||||
|
cats('fmtlen',_n_),
|
||||||
|
/* vars need extra padding due to JSON escaping of special chars */
|
||||||
|
min(32767,ceil((max(length,maxw)+10)*1.5))
|
||||||
|
,'l'
|
||||||
|
);
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* configure varlenchk - as we are explicitly shortening the variables */
|
||||||
|
%let optval=%sysfunc(getoption(varlenchk));
|
||||||
|
options varlenchk=NOWARN;
|
||||||
|
data _data_(compress=char);
|
||||||
|
/* shorten the new vars */
|
||||||
|
length
|
||||||
|
%do i=1 %to &numcols;
|
||||||
|
&&name&i $&&fmtlen&i
|
||||||
|
%end;
|
||||||
|
;
|
||||||
/* rename on entry */
|
/* rename on entry */
|
||||||
set &ds(rename=(
|
set &ds(rename=(
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
&&name&i=&&newname&i
|
&&name&i=&&newname&i
|
||||||
%end;
|
%end;
|
||||||
));
|
));
|
||||||
|
&stmt_obs;
|
||||||
|
|
||||||
|
drop
|
||||||
|
%do i=1 %to &numcols;
|
||||||
|
&&newname&i
|
||||||
|
%end;
|
||||||
|
;
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
length &&name&i $&&len&i;
|
|
||||||
%if &&typelong&i=num %then %do;
|
%if &&typelong&i=num %then %do;
|
||||||
&&name&i=left(put(&&newname&i,&&fmt&i));
|
&&name&i=cats(put(&&newname&i,&&fmt&i));
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
&&name&i=put(&&newname&i,&&fmt&i);
|
&&name&i=put(&&newname&i,&&fmt&i);
|
||||||
%end;
|
%end;
|
||||||
drop &&newname&i;
|
|
||||||
%end;
|
%end;
|
||||||
if _error_ then call symputx('syscc',1012);
|
if _error_ then do;
|
||||||
|
call symputx('syscc',1012);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
run;
|
run;
|
||||||
%let fmtds=&syslast;
|
%let fmtds=&syslast;
|
||||||
|
options varlenchk=&optval;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
proc format; /* credit yabwon for special null removal */
|
proc format; /* credit yabwon for special null removal */
|
||||||
@@ -186,8 +300,8 @@
|
|||||||
attrib _all_ label='';
|
attrib _all_ label='';
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
%if &&typelong&i=char or &fmt=Y %then %do;
|
%if &&typelong&i=char or &fmt=Y %then %do;
|
||||||
length &&name&i $32767;
|
length &&name&i $&&fmtlen&i...;
|
||||||
format &&name&i $32767.;
|
format &&name&i $&&fmtlen&i...;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%if &fmt=Y %then %do;
|
%if &fmt=Y %then %do;
|
||||||
@@ -196,23 +310,35 @@
|
|||||||
%else %do;
|
%else %do;
|
||||||
set &ds;
|
set &ds;
|
||||||
%end;
|
%end;
|
||||||
|
&stmt_obs;
|
||||||
format _numeric_ bart.;
|
format _numeric_ bart.;
|
||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
%if &&typelong&i=char or &fmt=Y %then %do;
|
%if &&typelong&i=char or &fmt=Y %then %do;
|
||||||
&&name&i='"'!!trim(prxchange('s/"/\"/',-1,
|
if findc(&&name&i,'"\'!!'0A0D09000E0F010210111A'x) then do;
|
||||||
prxchange('s/'!!'0A'x!!'/\n/',-1,
|
&&name&i='"'!!trim(
|
||||||
prxchange('s/'!!'0D'x!!'/\r/',-1,
|
prxchange('s/"/\\"/',-1, /* double quote */
|
||||||
prxchange('s/'!!'09'x!!'/\t/',-1,
|
prxchange('s/\x0A/\n/',-1, /* new line */
|
||||||
|
prxchange('s/\x0D/\r/',-1, /* carriage return */
|
||||||
|
prxchange('s/\x09/\\t/',-1, /* tab */
|
||||||
|
prxchange('s/\x00/\\u0000/',-1, /* NUL */
|
||||||
|
prxchange('s/\x0E/\\u000E/',-1, /* SS */
|
||||||
|
prxchange('s/\x0F/\\u000F/',-1, /* SF */
|
||||||
|
prxchange('s/\x01/\\u0001/',-1, /* SOH */
|
||||||
|
prxchange('s/\x02/\\u0002/',-1, /* STX */
|
||||||
|
prxchange('s/\x10/\\u0010/',-1, /* DLE */
|
||||||
|
prxchange('s/\x11/\\u0011/',-1, /* DC1 */
|
||||||
|
prxchange('s/\x1A/\\u001A/',-1, /* SUB */
|
||||||
prxchange('s/\\/\\\\/',-1,&&name&i)
|
prxchange('s/\\/\\\\/',-1,&&name&i)
|
||||||
)))))!!'"';
|
)))))))))))))!!'"';
|
||||||
|
end;
|
||||||
|
else &&name&i=quote(cats(&&name&i));
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
/* write to temp loc to avoid _webout truncation
|
filename _sjs3 temp lrecl=131068 ;
|
||||||
- https://support.sas.com/kb/49/325.html */
|
data _null_;
|
||||||
filename _sjs temp lrecl=131068 encoding='utf-8';
|
file _sjs3 encoding='utf-8';
|
||||||
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod ;
|
|
||||||
if _n_=1 then put "[";
|
if _n_=1 then put "[";
|
||||||
set &tempds;
|
set &tempds;
|
||||||
if _n_>1 then put "," @; put
|
if _n_>1 then put "," @; put
|
||||||
@@ -220,39 +346,38 @@
|
|||||||
%do i=1 %to &numcols;
|
%do i=1 %to &numcols;
|
||||||
%if &i>1 %then "," ;
|
%if &i>1 %then "," ;
|
||||||
%if &action=OBJ %then """&&name&i"":" ;
|
%if &action=OBJ %then """&&name&i"":" ;
|
||||||
&&name&i
|
"&&name&i"n /* name literal for reserved variable names */
|
||||||
%end;
|
%end;
|
||||||
%if &action=ARR %then "]" ; %else "}" ; ;
|
%if &action=ARR %then "]" ; %else "}" ; ;
|
||||||
/* now write the long strings to _webout 1 byte at a time */
|
|
||||||
data _null_;
|
|
||||||
length filein 8 fileid 8;
|
|
||||||
filein=fopen("_sjs",'I',1,'B');
|
|
||||||
fileid=fopen("&jref",'A',1,'B');
|
|
||||||
rec='20'x;
|
|
||||||
do while(fread(filein)=0);
|
|
||||||
rc=fget(filein,rec,1);
|
|
||||||
rc=fput(fileid, rec);
|
|
||||||
rc=fwrite(fileid);
|
|
||||||
end;
|
|
||||||
/* close out the table */
|
/* close out the table */
|
||||||
rc=fput(fileid, "]");
|
data _null_;
|
||||||
rc=fwrite(fileid);
|
file _sjs3 mod encoding='utf-8';
|
||||||
rc=fclose(filein);
|
put ']';
|
||||||
rc=fclose(fileid);
|
|
||||||
run;
|
run;
|
||||||
filename _sjs clear;
|
data _null_;
|
||||||
|
infile _sjs3 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs3 clear;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
proc sql;
|
proc sql;
|
||||||
drop table &colinfo, &tempds;
|
drop table &colinfo, &tempds;
|
||||||
|
|
||||||
%if &showmeta=YES %then %do;
|
%if %substr(&showmeta,1,1)=Y %then %do;
|
||||||
data _null_; file &jref encoding='utf-8' mod;
|
filename _sjs4 temp lrecl=131068 encoding='utf-8';
|
||||||
|
data _null_;
|
||||||
|
file _sjs4;
|
||||||
|
length label $350;
|
||||||
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
|
put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{";
|
||||||
do i=1 to &numcols;
|
do i=1 to &numcols;
|
||||||
name=quote(trim(symget(cats('name',i))));
|
name=quote(trim(symget(cats('name',i))));
|
||||||
format=quote(trim(symget(cats('fmt',i))));
|
format=quote(trim(symget(cats('fmt',i))));
|
||||||
label=quote(trim(symget(cats('label',i))));
|
label=quote(prxchange('s/\\/\\\\/',-1,trim(symget(cats('label',i)))));
|
||||||
length=quote(trim(symget(cats('length',i))));
|
length=quote(trim(symget(cats('length',i))));
|
||||||
type=quote(trim(symget(cats('typelong',i))));
|
type=quote(trim(symget(cats('typelong',i))));
|
||||||
if i>1 then put "," @@;
|
if i>1 then put "," @@;
|
||||||
@@ -261,6 +386,15 @@
|
|||||||
end;
|
end;
|
||||||
put '}}';
|
put '}}';
|
||||||
run;
|
run;
|
||||||
|
/* send back to webout */
|
||||||
|
data _null_;
|
||||||
|
infile _sjs4 lrecl=1 recfm=n;
|
||||||
|
file &jref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjs4 clear;
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
|||||||
303
base/mp_loadformat.sas
Normal file
303
base/mp_loadformat.sas
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Loads a format catalog from a staging dataset
|
||||||
|
@details When loading staged data, it is common to receive only the records
|
||||||
|
that have actually changed. However, when loading a format catalog, if
|
||||||
|
records are missing they are presumed to be no longer required.
|
||||||
|
|
||||||
|
This macro will augment a staging dataset with other records from the same
|
||||||
|
format, to prevent loss of data - UNLESS the input dataset contains a marker
|
||||||
|
column, specifying that a particular row needs to be deleted (`delete_col=`).
|
||||||
|
|
||||||
|
This macro can also be used to identify which records would be (or were)
|
||||||
|
considered new, modified or deleted (`loadtarget=`) by creating the following
|
||||||
|
tables:
|
||||||
|
|
||||||
|
@li work.outds_add
|
||||||
|
@li work.outds_del
|
||||||
|
@li work.outds_mod
|
||||||
|
|
||||||
|
For example usage, see mp_loadformat.test.sas
|
||||||
|
|
||||||
|
@param [in] libcat The format catalog to be loaded
|
||||||
|
@param [in] libds The staging table to load
|
||||||
|
@param [in] loadtarget= (NO) Set to YES to actually load the target catalog
|
||||||
|
@param [in] delete_col= (_____DELETE__THIS__RECORD_____) The column used to
|
||||||
|
mark a record for deletion. Values should be "Yes" or "No".
|
||||||
|
@param [out] auditlibds= (0) For change tracking, set to the libds of an audit
|
||||||
|
table as defined in mddl_dc_difftable.sas
|
||||||
|
@param [in] locklibds= (0) For multi-user (parallel) situations, set to the
|
||||||
|
libds of the DC lock table as defined in the mddl_dc_locktable.sas macro.
|
||||||
|
@param [out] outds_add= (0) Set a libds here to see the new records added
|
||||||
|
@param [out] outds_del= (0) Set a libds here to see the records deleted
|
||||||
|
@param [out] outds_mod= (0) Set a libds here to see the modified records
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mddl_sas_cntlout.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
@li mf_nobs.sas
|
||||||
|
@li mp_abort.sas
|
||||||
|
@li mp_cntlout.sas
|
||||||
|
@li mp_lockanytable.sas
|
||||||
|
@li mp_storediffs.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mddl_dc_difftable.sas
|
||||||
|
@li mddl_dc_locktable.sas
|
||||||
|
@li mp_loadformat.test.sas
|
||||||
|
@li mp_lockanytable.sas
|
||||||
|
@li mp_stackdiffs.sas
|
||||||
|
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_loadformat(libcat,libds
|
||||||
|
,loadtarget=NO
|
||||||
|
,auditlibds=0
|
||||||
|
,locklibds=0
|
||||||
|
,delete_col=_____DELETE__THIS__RECORD_____
|
||||||
|
,outds_add=0
|
||||||
|
,outds_del=0
|
||||||
|
,outds_mod=0
|
||||||
|
,mdebug=0
|
||||||
|
);
|
||||||
|
/* set up local macro variables and temporary tables (with a prefix) */
|
||||||
|
%local err msg prefix dslist i var fmtlist ibufsize;
|
||||||
|
%let dslist=base_fmts template inlibds ds1 stagedata storediffs;
|
||||||
|
%if &outds_add=0 %then %let dslist=&dslist outds_add;
|
||||||
|
%if &outds_del=0 %then %let dslist=&dslist outds_del;
|
||||||
|
%if &outds_mod=0 %then %let dslist=&dslist outds_mod;
|
||||||
|
%let prefix=%substr(%mf_getuniquename(),1,21);
|
||||||
|
%do i=1 %to %sysfunc(countw(&dslist));
|
||||||
|
%let var=%scan(&dslist,&i);
|
||||||
|
%local &var;
|
||||||
|
%let &var=%upcase(&prefix._&var);
|
||||||
|
%end;
|
||||||
|
|
||||||
|
/*
|
||||||
|
format values can be up to 32767 wide. SQL joins on such a wide column can
|
||||||
|
cause buffer issues. Update ibufsize and reset at the end.
|
||||||
|
*/
|
||||||
|
%let ibufsize=%sysfunc(getoption(ibufsize));
|
||||||
|
options ibufsize=32767 ;
|
||||||
|
|
||||||
|
/* in DC, format catalogs maybe specified in the libds with a -FC extension */
|
||||||
|
%let libcat=%scan(&libcat,1,-);
|
||||||
|
|
||||||
|
/* perform input validations */
|
||||||
|
%let err=0;
|
||||||
|
%let msg=0;
|
||||||
|
data _null_;
|
||||||
|
if _n_=1 then putlog "&sysmacroname entry vars:";
|
||||||
|
set sashelp.vmacro;
|
||||||
|
where scope="&sysmacroname";
|
||||||
|
value=upcase(value);
|
||||||
|
if &mdebug=0 then put name '=' value;
|
||||||
|
if name=:'LOAD' and value not in ('YES','NO') then do;
|
||||||
|
call symputx('msg',"invalid value for "!!name!!":"!!value);
|
||||||
|
call symputx('err',1);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
else if name='LIBCAT' then do;
|
||||||
|
if exist(value,'CATALOG') le 0 then do;
|
||||||
|
call symputx('msg',"Unable to open catalog: "!!value);
|
||||||
|
call symputx('err',1);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
else if name='LIBDS' then do;
|
||||||
|
if exist(value) le 0 then do;
|
||||||
|
call symputx('msg',"Unable to open staging table: "!!value);
|
||||||
|
call symputx('err',1);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
else if (name=:'OUTDS' or name in ('DELETE_COL','LOCKLIBDS','AUDITLIBDS'))
|
||||||
|
and missing(value) then do;
|
||||||
|
call symputx('msg',"missing value in var: "!!name);
|
||||||
|
call symputx('err',1);
|
||||||
|
stop;
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mp_abort(
|
||||||
|
iftrue=(&err ne 0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(&msg)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First, extract only relevant formats from the catalog
|
||||||
|
*/
|
||||||
|
proc sql noprint;
|
||||||
|
select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
|
||||||
|
|
||||||
|
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure input table and base_formats have consistent lengths and types
|
||||||
|
*/
|
||||||
|
%mddl_sas_cntlout(libds=&template)
|
||||||
|
data &inlibds;
|
||||||
|
length &delete_col $3;
|
||||||
|
if 0 then set &template;
|
||||||
|
set &libds;
|
||||||
|
if &delete_col='' then &delete_col='No';
|
||||||
|
fmtname=upcase(fmtname);
|
||||||
|
if missing(type) then do;
|
||||||
|
if substr(fmtname,1,1)='$' then type='C';
|
||||||
|
else type='N';
|
||||||
|
end;
|
||||||
|
if type='N' then do;
|
||||||
|
start=cats(start);
|
||||||
|
end=cats(end);
|
||||||
|
end;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify new records
|
||||||
|
*/
|
||||||
|
proc sql;
|
||||||
|
create table &outds_add(drop=&delete_col) as
|
||||||
|
select a.*
|
||||||
|
from &inlibds a
|
||||||
|
left join &base_fmts b
|
||||||
|
on a.fmtname=b.fmtname
|
||||||
|
and a.start=b.start
|
||||||
|
where b.fmtname is null
|
||||||
|
and upcase(a.&delete_col) ne "YES"
|
||||||
|
order by fmtname, start;;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify deleted records
|
||||||
|
*/
|
||||||
|
create table &outds_del(drop=&delete_col) as
|
||||||
|
select a.*
|
||||||
|
from &inlibds a
|
||||||
|
inner join &base_fmts b
|
||||||
|
on a.fmtname=b.fmtname
|
||||||
|
and a.start=b.start
|
||||||
|
where upcase(a.&delete_col)="YES"
|
||||||
|
order by fmtname, start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identify modified records
|
||||||
|
*/
|
||||||
|
create table &outds_mod (drop=&delete_col) as
|
||||||
|
select a.*
|
||||||
|
from &inlibds a
|
||||||
|
inner join &base_fmts b
|
||||||
|
on a.fmtname=b.fmtname
|
||||||
|
and a.start=b.start
|
||||||
|
where upcase(a.&delete_col) ne "YES"
|
||||||
|
order by fmtname, start;
|
||||||
|
|
||||||
|
options ibufsize=&ibufsize;
|
||||||
|
|
||||||
|
%mp_abort(
|
||||||
|
iftrue=(&syscc ne 0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(SYSCC=&syscc prior to load prep)
|
||||||
|
)
|
||||||
|
|
||||||
|
%if &loadtarget=YES %then %do;
|
||||||
|
data &ds1;
|
||||||
|
merge &base_fmts(in=base)
|
||||||
|
&outds_mod(in=mod)
|
||||||
|
&outds_add(in=add)
|
||||||
|
&outds_del(in=del);
|
||||||
|
if not del and not mod;
|
||||||
|
by fmtname start;
|
||||||
|
run;
|
||||||
|
data &stagedata;
|
||||||
|
set &ds1 &outds_mod;
|
||||||
|
run;
|
||||||
|
proc sort;
|
||||||
|
by fmtname start;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
/* mp abort needs to run outside of conditional blocks */
|
||||||
|
%mp_abort(
|
||||||
|
iftrue=(&syscc ne 0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(SYSCC=&syscc prior to actual load)
|
||||||
|
)
|
||||||
|
%if &loadtarget=YES %then %do;
|
||||||
|
%if %mf_nobs(&stagedata)=0 %then %do;
|
||||||
|
%put There are no changes to load in &libcat!;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
%if &locklibds ne 0 %then %do;
|
||||||
|
/* prevent parallel updates */
|
||||||
|
%mp_lockanytable(LOCK
|
||||||
|
,lib=%scan(&libcat,1,.)
|
||||||
|
,ds=%scan(&libcat,2,.)-FC
|
||||||
|
,ref=MP_LOADFORMAT commencing format load
|
||||||
|
,ctl_ds=&locklibds
|
||||||
|
)
|
||||||
|
%end;
|
||||||
|
/* do the actual load */
|
||||||
|
proc format lib=&libcat cntlin=&stagedata;
|
||||||
|
run;
|
||||||
|
%if &locklibds ne 0 %then %do;
|
||||||
|
/* unlock the table */
|
||||||
|
%mp_lockanytable(UNLOCK
|
||||||
|
,lib=%scan(&libcat,1,.)
|
||||||
|
,ds=%scan(&libcat,2,.)-FC
|
||||||
|
,ref=MP_LOADFORMAT completed format load
|
||||||
|
,ctl_ds=&locklibds
|
||||||
|
)
|
||||||
|
%end;
|
||||||
|
/* track the changes */
|
||||||
|
%if &auditlibds ne 0 %then %do;
|
||||||
|
%if &locklibds ne 0 %then %do;
|
||||||
|
%mp_lockanytable(LOCK
|
||||||
|
,lib=%scan(&auditlibds,1,.)
|
||||||
|
,ds=%scan(&auditlibds,2,.)
|
||||||
|
,ref=MP_LOADFORMAT commencing audit table load
|
||||||
|
,ctl_ds=&locklibds
|
||||||
|
)
|
||||||
|
%end;
|
||||||
|
|
||||||
|
%mp_storediffs(&libcat-FC
|
||||||
|
,&base_fmts
|
||||||
|
,FMTNAME START
|
||||||
|
,delds=&outds_del
|
||||||
|
,modds=&outds_mod
|
||||||
|
,appds=&outds_add
|
||||||
|
,outds=&storediffs
|
||||||
|
,mdebug=&mdebug
|
||||||
|
)
|
||||||
|
|
||||||
|
proc append base=&auditlibds data=&storediffs;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%if &locklibds ne 0 %then %do;
|
||||||
|
%mp_lockanytable(UNLOCK
|
||||||
|
,lib=%scan(&auditlibds,1,.)
|
||||||
|
,ds=%scan(&auditlibds,2,.)
|
||||||
|
,ref=MP_LOADFORMAT commencing audit table load
|
||||||
|
,ctl_ds=&locklibds
|
||||||
|
)
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%mp_abort(
|
||||||
|
iftrue=(&syscc ne 0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(SYSCC=&syscc after load)
|
||||||
|
)
|
||||||
|
|
||||||
|
%if &mdebug=0 %then %do;
|
||||||
|
proc datasets lib=work;
|
||||||
|
delete &prefix:;
|
||||||
|
run;
|
||||||
|
%put &sysmacroname exit vars:;
|
||||||
|
%put _local_;
|
||||||
|
%end;
|
||||||
|
%mend mp_loadformat;
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Mechanism for locking tables to prevent parallel modifications
|
@brief Mechanism for locking tables to prevent parallel modifications
|
||||||
@details Uses a control table to enable ANY table to be locked for updates.
|
@details Uses a control table to enable ANY table to be locked for updates
|
||||||
|
(not just SAS datasets).
|
||||||
Only useful if every update uses the macro! Used heavily within
|
Only useful if every update uses the macro! Used heavily within
|
||||||
[Data Controller for SAS](https://datacontroller.io).
|
[Data Controller for SAS](https://datacontroller.io).
|
||||||
|
|
||||||
@@ -15,12 +16,13 @@
|
|||||||
length is 200 characters.
|
length is 200 characters.
|
||||||
@param [out] ctl_ds= (0) The control table which controls the actual locking.
|
@param [out] ctl_ds= (0) The control table which controls the actual locking.
|
||||||
Should already be assigned and available. The definition is available by
|
Should already be assigned and available. The definition is available by
|
||||||
running mp_coretable.sas as follows: `%mp_coretable(LOCKTABLE)`.
|
running the mddl_dc_locktable.sas macro.
|
||||||
|
|
||||||
@param [in] loops= (25) Number of times to check for a lock.
|
@param [in] loops= (25) Number of times to check for a lock.
|
||||||
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
|
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_fmtdttm.sas
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
@li mp_lockfilecheck.sas
|
@li mp_lockfilecheck.sas
|
||||||
@li mf_getuser.sas
|
@li mf_getuser.sas
|
||||||
@@ -48,7 +50,7 @@ data _null_;
|
|||||||
put name '=' value;
|
put name '=' value;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%mp_abort(iftrue= (&ds=0 and &action ne MAKETABLE)
|
%mp_abort(iftrue= ("&ds"="0" and &action ne MAKETABLE)
|
||||||
,mac=&sysmacroname
|
,mac=&sysmacroname
|
||||||
,msg=%str(dataset was not provided)
|
,msg=%str(dataset was not provided)
|
||||||
)
|
)
|
||||||
@@ -110,7 +112,7 @@ run;
|
|||||||
LOCK_LIB ="&lib";
|
LOCK_LIB ="&lib";
|
||||||
LOCK_DS="&ds";
|
LOCK_DS="&ds";
|
||||||
LOCK_STATUS_CD='LOCKED';
|
LOCK_STATUS_CD='LOCKED';
|
||||||
LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
|
LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt;
|
||||||
LOCK_USER_NM="&user";
|
LOCK_USER_NM="&user";
|
||||||
LOCK_PID="&sysjobid";
|
LOCK_PID="&sysjobid";
|
||||||
LOCK_REF="&ref";
|
LOCK_REF="&ref";
|
||||||
@@ -130,7 +132,7 @@ run;
|
|||||||
proc sql;
|
proc sql;
|
||||||
update &ctl_ds
|
update &ctl_ds
|
||||||
set LOCK_STATUS_CD='LOCKED'
|
set LOCK_STATUS_CD='LOCKED'
|
||||||
, LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
|
, LOCK_START_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
||||||
, LOCK_USER_NM="&user"
|
, LOCK_USER_NM="&user"
|
||||||
, LOCK_PID="&sysjobid"
|
, LOCK_PID="&sysjobid"
|
||||||
, LOCK_REF="&ref"
|
, LOCK_REF="&ref"
|
||||||
@@ -205,7 +207,7 @@ run;
|
|||||||
proc sql;
|
proc sql;
|
||||||
update &ctl_ds
|
update &ctl_ds
|
||||||
set LOCK_STATUS_CD='UNLOCKED'
|
set LOCK_STATUS_CD='UNLOCKED'
|
||||||
, LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
|
, LOCK_END_DTTM="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
||||||
, LOCK_USER_NM="&user"
|
, LOCK_USER_NM="&user"
|
||||||
, LOCK_PID="&sysjobid"
|
, LOCK_PID="&sysjobid"
|
||||||
, LOCK_REF="&ref"
|
, LOCK_REF="&ref"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ run;
|
|||||||
,mac=checklock.sas
|
,mac=checklock.sas
|
||||||
,msg=Aborting with syscc=&syscc on entry.
|
,msg=Aborting with syscc=&syscc on entry.
|
||||||
)
|
)
|
||||||
%mp_abort(iftrue= (&libds=0)
|
%mp_abort(iftrue= ("&libds"="0")
|
||||||
,mac=&sysmacroname
|
,mac=&sysmacroname
|
||||||
,msg=%str(libds not provided)
|
,msg=%str(libds not provided)
|
||||||
)
|
)
|
||||||
@@ -46,6 +46,12 @@ run;
|
|||||||
%let lib=%upcase(%scan(&libds,1,.));
|
%let lib=%upcase(%scan(&libds,1,.));
|
||||||
%let ds=%upcase(%scan(&libds,2,.));
|
%let ds=%upcase(%scan(&libds,2,.));
|
||||||
|
|
||||||
|
/* in DC, format catalogs are passed with a -FC suffix. No saslock here! */
|
||||||
|
%if %scan(&libds,2,-)=FC %then %do;
|
||||||
|
%put &sysmacroname: Format Catalog detected, no lockfile applied to &libds;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
/* do not proceed if no observations can be processed */
|
/* do not proceed if no observations can be processed */
|
||||||
%let msg=options obs = 0. syserrortext=%superq(syserrortext);
|
%let msg=options obs = 0. syserrortext=%superq(syserrortext);
|
||||||
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
|
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
|
||||||
|
|||||||
58
base/mp_md5.sas
Normal file
58
base/mp_md5.sas
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Generates an md5 expression for hashing a set of variables
|
||||||
|
@details This is the same algorithm used to hash records in
|
||||||
|
[Data Controller for SAS](https://datacontroller.io) (free for up
|
||||||
|
to 5 users).
|
||||||
|
|
||||||
|
It is not designed to be efficient - it is designed to be effective,
|
||||||
|
given the range of edge cases (large floating points, special missing
|
||||||
|
numerics, thousands of columns, very wide columns).
|
||||||
|
|
||||||
|
It can be used only in data step, eg as follows:
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
set sashelp.class;
|
||||||
|
hashvar=%mp_md5(cvars=name sex, nvars=age height weight);
|
||||||
|
put hashvar=;
|
||||||
|
run;
|
||||||
|
|
||||||
|
Unfortunately it will not run in SQL - it fails with the following message:
|
||||||
|
|
||||||
|
> The width value for HEX is out of bounds. It should be between 1 and 16
|
||||||
|
|
||||||
|
The macro will also cause errors if the data contains (non-special) missings
|
||||||
|
and the (undocumented) `options dsoptions=nonote2err;` is in effect.
|
||||||
|
|
||||||
|
This can be avoided in two ways:
|
||||||
|
|
||||||
|
@li Global option: `options dsoptions=nonote2err;`
|
||||||
|
@li Data step option: `data YOURLIB.YOURDATASET /nonote2err;`
|
||||||
|
|
||||||
|
@param cvars= Space seperated list of character variables
|
||||||
|
@param nvars= Space seperated list of numeric variables
|
||||||
|
|
||||||
|
<h4> Related Programs </h4>
|
||||||
|
@li mp_init.sas
|
||||||
|
|
||||||
|
@version 9.2
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_md5(cvars=,nvars=);
|
||||||
|
%local i var sep;
|
||||||
|
put(md5(
|
||||||
|
%do i=1 %to %sysfunc(countw(&cvars));
|
||||||
|
%let var=%scan(&cvars,&i,%str( ));
|
||||||
|
&sep put(md5(trim(&var)),$hex32.)
|
||||||
|
%let sep=!!;
|
||||||
|
%end;
|
||||||
|
%do i=1 %to %sysfunc(countw(&nvars));
|
||||||
|
%let var=%scan(&nvars,&i,%str( ));
|
||||||
|
/* multiply by 1 to strip precision errors (eg 0 != 0) */
|
||||||
|
/* but ONLY if not missing, else will lose any special missing values */
|
||||||
|
&sep put(md5(trim(put(ifn(missing(&var),&var,&var*1),binary64.))),$hex32.)
|
||||||
|
%let sep=!!;
|
||||||
|
%end;
|
||||||
|
),$hex32.)
|
||||||
|
%mend mp_md5;
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Logs the time the macro was executed in a control dataset.
|
@brief Logs a message in a dataset every time it is invoked
|
||||||
@details If the dataset does not exist, it is created. Usage:
|
@details If the dataset does not exist, it is created.
|
||||||
|
Usage:
|
||||||
|
|
||||||
%mp_perflog(started)
|
%mp_perflog(started)
|
||||||
%mp_perflog()
|
%mp_perflog()
|
||||||
|
|||||||
151
base/mp_replace.sas
Normal file
151
base/mp_replace.sas
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Performs a text substitution on a file
|
||||||
|
@details Performs a find and replace on a file, either in place or to a new
|
||||||
|
file. Can be used on files where lines are longer than 32767.
|
||||||
|
|
||||||
|
Works by reading in the file byte by byte, then marking the beginning and end
|
||||||
|
of each matched string, before finally doing the replace.
|
||||||
|
|
||||||
|
Full credit for this highly efficient and syntactically satisfying SAS logic
|
||||||
|
goes to [Bartosz Jabłoński](https://www.linkedin.com/in/yabwon), founder of
|
||||||
|
the [SAS Packages](https://github.com/yabwon/SAS_PACKAGES) framework.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%let file="%sysfunc(pathname(work))/file.txt";
|
||||||
|
%let str=replace/me;
|
||||||
|
%let rep=with/this;
|
||||||
|
data _null_;
|
||||||
|
file &file;
|
||||||
|
put 'blahblah';
|
||||||
|
put "blahblah&str.blah";
|
||||||
|
put 'blahblahblah';
|
||||||
|
run;
|
||||||
|
%mp_replace(&file, findvar=str, replacevar=rep)
|
||||||
|
data _null_;
|
||||||
|
infile &file;
|
||||||
|
input;
|
||||||
|
list;
|
||||||
|
run;
|
||||||
|
|
||||||
|
Note - if you are running a version of SAS that will allow the io package in
|
||||||
|
LUA, you can also use this macro: mp_gsubfile.sas
|
||||||
|
|
||||||
|
@param infile The QUOTED path to the file on which to perform the substitution
|
||||||
|
@param findvar= Macro variable NAME containing the string to search for
|
||||||
|
@param replacevar= Macro variable NAME containing the replacement string
|
||||||
|
@param outfile= (0) Optional QUOTED path to the adjusted output file (to
|
||||||
|
avoid overwriting the first file).
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquefileref.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li mp_chop.sas
|
||||||
|
@li mp_gsubfile.sas
|
||||||
|
@li mp_replace.test.sas
|
||||||
|
|
||||||
|
@version 9.4
|
||||||
|
@author Bartosz Jabłoński
|
||||||
|
@author Allan Bowe
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mp_replace(infile,
|
||||||
|
findvar=,
|
||||||
|
replacevar=,
|
||||||
|
outfile=0
|
||||||
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%local inref dttm ds1;
|
||||||
|
%let inref=%mf_getuniquefileref();
|
||||||
|
%let outref=%mf_getuniquefileref();
|
||||||
|
%if &outfile=0 %then %let outfile=&infile;
|
||||||
|
%let ds1=%mf_getuniquename(prefix=allchars);
|
||||||
|
%let ds2=%mf_getuniquename(prefix=startmark);
|
||||||
|
|
||||||
|
/* START */
|
||||||
|
%let dttm=%sysfunc(datetime());
|
||||||
|
|
||||||
|
filename &inref &infile lrecl=1 recfm=n;
|
||||||
|
|
||||||
|
data &ds1;
|
||||||
|
infile &inref;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
run;
|
||||||
|
|
||||||
|
data &ds2;
|
||||||
|
/* set find string to length in bytes to cover trailing spaces */
|
||||||
|
length string $ %length(%superq(&findvar));
|
||||||
|
string =symget("&findvar");
|
||||||
|
drop string;
|
||||||
|
|
||||||
|
firstchar=char(string,1);
|
||||||
|
findlen=lengthm(string); /* <- for trailing bytes */
|
||||||
|
|
||||||
|
do _N_=1 to nobs;
|
||||||
|
set &ds1 nobs=nobs point=_N_;
|
||||||
|
if sourcechar=firstchar then do;
|
||||||
|
pos=1;
|
||||||
|
s=0;
|
||||||
|
do point=_N_ to min(_N_ + findlen -1,nobs);
|
||||||
|
set &ds1 point=point;
|
||||||
|
if sourcechar=char(string, pos) then s + 1;
|
||||||
|
else goto _leave_;
|
||||||
|
pos+1;
|
||||||
|
end;
|
||||||
|
_leave_:
|
||||||
|
if s=findlen then do;
|
||||||
|
START =_N_;
|
||||||
|
_N_ =_N_+ s - 1;
|
||||||
|
STOP =_N_;
|
||||||
|
output;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
stop;
|
||||||
|
keep START STOP;
|
||||||
|
run;
|
||||||
|
|
||||||
|
data &ds1;
|
||||||
|
declare hash HS(dataset:"&ds2(keep=start)");
|
||||||
|
HS.defineKey("start");
|
||||||
|
HS.defineDone();
|
||||||
|
declare hash HE(dataset:"&ds2(keep=stop)");
|
||||||
|
HE.defineKey("stop");
|
||||||
|
HE.defineDone();
|
||||||
|
do until(eof);
|
||||||
|
set &ds1 end=eof curobs =n;
|
||||||
|
start = ^HS.check(key:n);
|
||||||
|
stop = ^HE.check(key:n);
|
||||||
|
length strt $ 1;
|
||||||
|
strt =put(start,best. -L);
|
||||||
|
retain out 1;
|
||||||
|
if out then output;
|
||||||
|
if start then out=0;
|
||||||
|
if stop then out=1;
|
||||||
|
end;
|
||||||
|
stop;
|
||||||
|
keep sourcechar strt;
|
||||||
|
run;
|
||||||
|
|
||||||
|
filename &outref &outfile recfm=n;
|
||||||
|
|
||||||
|
data _null_;
|
||||||
|
length replace $ %length(%superq(&replacevar));
|
||||||
|
replace=symget("&replacevar");
|
||||||
|
file &outref;
|
||||||
|
do until(eof);
|
||||||
|
set &ds1 end=eof;
|
||||||
|
if strt ="1" then put replace char.;
|
||||||
|
else put sourcechar char1.;
|
||||||
|
end;
|
||||||
|
stop;
|
||||||
|
run;
|
||||||
|
|
||||||
|
/* END */
|
||||||
|
%put &sysmacroname took %sysevalf(%sysfunc(datetime())-&dttm) seconds to run;
|
||||||
|
|
||||||
|
%mend mp_replace;
|
||||||
@@ -21,6 +21,7 @@ https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-s
|
|||||||
%macro mp_resetoption(option /* the option to reset */
|
%macro mp_resetoption(option /* the option to reset */
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
length code $1500;
|
length code $1500;
|
||||||
startup=getoption("&option",'startupvalue');
|
startup=getoption("&option",'startupvalue');
|
||||||
@@ -31,5 +32,8 @@ data _null_;
|
|||||||
call execute(code );
|
call execute(code );
|
||||||
end;
|
end;
|
||||||
run;
|
run;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
%put &sysmacroname: reset option feature unavailable on &sysvlong;
|
||||||
|
%end;
|
||||||
%mend mp_resetoption;
|
%mend mp_resetoption;
|
||||||
@@ -58,6 +58,7 @@
|
|||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_existvar.sas
|
@li mf_existvar.sas
|
||||||
|
@li mf_fmtdttm.sas
|
||||||
@li mf_getquotedstr.sas
|
@li mf_getquotedstr.sas
|
||||||
@li mf_getuniquename.sas
|
@li mf_getuniquename.sas
|
||||||
@li mf_nobs.sas
|
@li mf_nobs.sas
|
||||||
@@ -217,12 +218,12 @@ quit;
|
|||||||
set keytable="&base_libds"
|
set keytable="&base_libds"
|
||||||
,keycolumn="&retained_key"
|
,keycolumn="&retained_key"
|
||||||
,max_key=%eval(&maxkey+&newkey_cnt)
|
,max_key=%eval(&maxkey+&newkey_cnt)
|
||||||
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt;
|
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt;
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
update &maxkeytable
|
update &maxkeytable
|
||||||
set max_key=%eval(&maxkey+&newkey_cnt)
|
set max_key=%eval(&maxkey+&newkey_cnt)
|
||||||
,processed_dttm="%sysfunc(datetime(),E8601DT26.6)"dt
|
,processed_dttm="%sysfunc(datetime(),%mf_fmtdttm())"dt
|
||||||
where keytable="&base_libds";
|
where keytable="&base_libds";
|
||||||
%end;
|
%end;
|
||||||
%mp_lockanytable(UNLOCK
|
%mp_lockanytable(UNLOCK
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
%else %let dbg=*;
|
%else %let dbg=*;
|
||||||
|
|
||||||
/* set up unique and temporary vars */
|
/* set up unique and temporary vars */
|
||||||
%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist;
|
%local ds1 ds2 ds3 ds4 hashkey inds_auto inds_keep dslist vlist;
|
||||||
%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));
|
%let ds1=%upcase(work.%mf_getuniquename(prefix=mpsd_ds1));
|
||||||
%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));
|
%let ds2=%upcase(work.%mf_getuniquename(prefix=mpsd_ds2));
|
||||||
%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));
|
%let ds3=%upcase(work.%mf_getuniquename(prefix=mpsd_ds3));
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
data &ds1;
|
data &ds1;
|
||||||
set &dslist indsname=&inds_auto;
|
set &dslist indsname=&inds_auto;
|
||||||
&hashkey=put(md5(catx('|',%mf_getquotedstr(&key,quote=N))),$hex32.);
|
&hashkey=put(md5(catx('|',%mf_getquotedstr(&key,quote=N))),$hex32.);
|
||||||
&inds_keep=&inds_auto;
|
&inds_keep=upcase(&inds_auto);
|
||||||
proc sort;
|
proc sort;
|
||||||
by &inds_keep &hashkey;
|
by &inds_keep &hashkey;
|
||||||
run;
|
run;
|
||||||
@@ -144,15 +144,26 @@ proc transpose data=&ds1
|
|||||||
by &inds_keep &hashkey;
|
by &inds_keep &hashkey;
|
||||||
var _character_;
|
var _character_;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
|
%if %index(&libds,-)>0 and %scan(&libds,2,-)=FC %then %do;
|
||||||
|
/* this is a format catalog - cannot query cols directly */
|
||||||
|
%let vlist="FMTNAME","START","END","LABEL","MIN","MAX","DEFAULT","LENGTH"
|
||||||
|
,"FUZZ","PREFIX","MULT","FILL","NOEDIT","TYPE","SEXCL","EEXCL","HLO"
|
||||||
|
,"DECSEP","DIG3SEP","DATATYPE","LANGUAGE";
|
||||||
|
%end;
|
||||||
|
%else %let vlist=%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE);
|
||||||
|
|
||||||
data &ds4;
|
data &ds4;
|
||||||
length &inds_keep $41 tgtvar_nm $32;
|
length &inds_keep $41 tgtvar_nm $32 _label_ $256;
|
||||||
|
if _n_=1 then call missing(_label_);
|
||||||
|
drop _label_;
|
||||||
set &ds2 &ds3 indsname=&inds_auto;
|
set &ds2 &ds3 indsname=&inds_auto;
|
||||||
|
|
||||||
tgtvar_nm=upcase(tgtvar_nm);
|
tgtvar_nm=upcase(tgtvar_nm);
|
||||||
if tgtvar_nm in (%upcase(%mf_getvarlist(&libds,dlm=%str(,),quote=DOUBLE)));
|
if tgtvar_nm in (%upcase(&vlist));
|
||||||
|
|
||||||
if &inds_auto="&ds2" then tgtvar_type='N';
|
if upcase(&inds_auto)="&ds2" then tgtvar_type='N';
|
||||||
else if &inds_auto="&ds3" then tgtvar_type='C';
|
else if upcase(&inds_auto)="&ds3" then tgtvar_type='C';
|
||||||
else do;
|
else do;
|
||||||
putlog "%str(ERR)OR: unidentified vartype input!" &inds_auto;
|
putlog "%str(ERR)OR: unidentified vartype input!" &inds_auto;
|
||||||
call symputx('syscc',98);
|
call symputx('syscc',98);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Streams a file to _webout according to content type
|
@brief Streams a file to _webout according to content type
|
||||||
@details Will set headers using appropriate functions (SAS 9 vs Viya) and send
|
@details Will set headers using appropriate functions per the server type
|
||||||
content as a binary stream.
|
(Viya, EBI, [SASjs Server](https://github.com/sasjs/server)) and stream
|
||||||
|
content using mp_binarycopy().
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
@@ -12,17 +13,27 @@
|
|||||||
|
|
||||||
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
|
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
|
||||||
|
|
||||||
|
@param [in] contenttype= (TEXT) Supported:
|
||||||
|
@li CSV
|
||||||
|
@li EXCEL
|
||||||
|
@li MARKDOWN
|
||||||
|
@li TEXT
|
||||||
|
@li ZIP
|
||||||
|
Feel free to submit PRs to support more mime types! The official list is
|
||||||
|
here: https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||||
|
@param [in] inloc= /path/to/file.ext to be sent
|
||||||
|
@param [in] inref= fileref of file to be sent (if provided, overrides `inloc`)
|
||||||
|
@param [in] iftrue= (1=1) Provide a condition under which to execute.
|
||||||
|
@param [out] outname= the name of the file, as downloaded by the browser
|
||||||
|
@param [out] outref= (_webout) The destination where the file should be
|
||||||
|
streamed.
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getplatform.sas
|
@li mf_getplatform.sas
|
||||||
|
@li mfs_httpheader.sas
|
||||||
@li mp_binarycopy.sas
|
@li mp_binarycopy.sas
|
||||||
|
|
||||||
@param contenttype= Either TEXT, ZIP, CSV, EXCEL (default TEXT)
|
|
||||||
@param inloc= /path/to/file.ext to be sent
|
|
||||||
@param inref= fileref of file to be sent (if provided, overrides `inloc`)
|
|
||||||
@param outname= the name of the file, as downloaded by the browser
|
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@source https://github.com/sasjs/core
|
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@@ -30,12 +41,16 @@
|
|||||||
contenttype=TEXT
|
contenttype=TEXT
|
||||||
,inloc=
|
,inloc=
|
||||||
,inref=0
|
,inref=0
|
||||||
|
,iftrue=%str(1=1)
|
||||||
,outname=
|
,outname=
|
||||||
|
,outref=_webout
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
%let contentype=%upcase(&contenttype);
|
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||||
%local platform; %let platform=%mf_getplatform();
|
|
||||||
|
|
||||||
|
%let contentype=%upcase(&contenttype);
|
||||||
|
%let outref=%upcase(&outref);
|
||||||
|
%local platform; %let platform=%mf_getplatform();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check engine type to avoid the below err message:
|
* check engine type to avoid the below err message:
|
||||||
@@ -44,94 +59,154 @@
|
|||||||
%local streamweb;
|
%local streamweb;
|
||||||
%let streamweb=0;
|
%let streamweb=0;
|
||||||
data _null_;
|
data _null_;
|
||||||
set sashelp.vextfl(where=(upcase(fileref)="_WEBOUT"));
|
set sashelp.vextfl(where=(upcase(fileref)="&outref"));
|
||||||
if xengine='STREAM' then call symputx('streamweb',1,'l');
|
if xengine='STREAM' then call symputx('streamweb',1,'l');
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &contentype=ZIP %then %do;
|
%if &contentype=CSV %then %do;
|
||||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
rc=stpsrv_header('Content-type','application/zip');
|
rc=stpsrv_header('Content-Type','application/csv');
|
||||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if &platform=SASVIYA %then %do;
|
%else %if &platform=SASVIYA %then %do;
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.zip'
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
||||||
contenttype='application/zip'
|
contenttype='application/csv'
|
||||||
contentdisp="attachment; filename=&outname";
|
contentdisp="attachment; filename=&outname";
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,application/csv)
|
||||||
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%else %if &contentype=EXCEL %then %do;
|
%else %if &contentype=EXCEL %then %do;
|
||||||
/* suitable for XLS format */
|
/* suitable for XLS format */
|
||||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
rc=stpsrv_header('Content-type','application/vnd.ms-excel');
|
rc=stpsrv_header('Content-Type','application/vnd.ms-excel');
|
||||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if &platform=SASVIYA %then %do;
|
%else %if &platform=SASVIYA %then %do;
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
||||||
contenttype='application/vnd.ms-excel'
|
contenttype='application/vnd.ms-excel'
|
||||||
contentdisp="attachment; filename=&outname";
|
contentdisp="attachment; filename=&outname";
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,application/vnd.ms-excel)
|
||||||
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%else %if &contentype=GIF or &contentype=JPEG or &contentype=PNG %then %do;
|
||||||
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
|
data _null_;
|
||||||
|
rc=stpsrv_header('Content-Type',"image/%lowcase(&contenttype)");
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASVIYA %then %do;
|
||||||
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
|
||||||
|
contenttype="image/%lowcase(&contenttype)";
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,image/%lowcase(&contenttype))
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%else %if &contentype=HTML or &contenttype=MARKDOWN %then %do;
|
||||||
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
|
data _null_;
|
||||||
|
rc=stpsrv_header('Content-Type',"text/%lowcase(&contenttype)");
|
||||||
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASVIYA %then %do;
|
||||||
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
|
||||||
|
contenttype="text/%lowcase(&contenttype)"
|
||||||
|
contentdisp="attachment; filename=&outname";
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,text/%lowcase(&contenttype))
|
||||||
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%else %if &contentype=TEXT %then %do;
|
||||||
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
|
data _null_;
|
||||||
|
rc=stpsrv_header('Content-Type','application/text');
|
||||||
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASVIYA %then %do;
|
||||||
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
||||||
|
contenttype='application/text'
|
||||||
|
contentdisp="attachment; filename=&outname";
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,application/text)
|
||||||
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
|
%end;
|
||||||
|
%end;
|
||||||
|
%else %if &contentype=WOFF or &contentype=WOFF2 or &contentype=TTF %then %do;
|
||||||
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
|
data _null_;
|
||||||
|
rc=stpsrv_header('Content-Type',"font/%lowcase(&contenttype)");
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASVIYA %then %do;
|
||||||
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI"
|
||||||
|
contenttype="font/%lowcase(&contenttype)";
|
||||||
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type,font/%lowcase(&contenttype))
|
||||||
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%else %if &contentype=XLSX %then %do;
|
%else %if &contentype=XLSX %then %do;
|
||||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
rc=stpsrv_header('Content-type',
|
rc=stpsrv_header('Content-Type',
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if &platform=SASVIYA %then %do;
|
%else %if &platform=SASVIYA %then %do;
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
||||||
contenttype=
|
contenttype=
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||||
contentdisp="attachment; filename=&outname";
|
contentdisp="attachment; filename=&outname";
|
||||||
%end;
|
%end;
|
||||||
|
%else %if &platform=SASJS %then %do;
|
||||||
|
%mfs_httpheader(Content-Type
|
||||||
|
,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
)
|
||||||
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
%end;
|
%end;
|
||||||
%else %if &contentype=TEXT %then %do;
|
%end;
|
||||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
%else %if &contentype=ZIP %then %do;
|
||||||
|
%if (&platform=SASMETA and &streamweb=1) %then %do;
|
||||||
data _null_;
|
data _null_;
|
||||||
rc=stpsrv_header('Content-type','application/text');
|
rc=stpsrv_header('Content-Type','application/zip');
|
||||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
%else %if &platform=SASVIYA %then %do;
|
%else %if &platform=SASVIYA %then %do;
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
filename &outref filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.zip'
|
||||||
contenttype='application/text'
|
contenttype='application/zip'
|
||||||
contentdisp="attachment; filename=&outname";
|
contentdisp="attachment; filename=&outname";
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%else %if &platform=SASJS %then %do;
|
||||||
%else %if &contentype=CSV %then %do;
|
%mfs_httpheader(Content-Type,application/zip)
|
||||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
%mfs_httpheader(Content-disposition,%str(attachment; filename=&outname))
|
||||||
data _null_;
|
|
||||||
rc=stpsrv_header('Content-type','application/csv');
|
|
||||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
|
||||||
run;
|
|
||||||
%end;
|
|
||||||
%else %if &platform=SASVIYA %then %do;
|
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.txt'
|
|
||||||
contenttype='application/csv'
|
|
||||||
contentdisp="attachment; filename=&outname";
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
%else %if &contentype=HTML %then %do;
|
|
||||||
%if &platform=SASVIYA %then %do;
|
|
||||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json"
|
|
||||||
contenttype="text/html";
|
|
||||||
%end;
|
%end;
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;
|
%put %str(ERR)OR: Content Type &contenttype NOT SUPPORTED by &sysmacroname!;
|
||||||
%return;
|
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%if &inref ne 0 %then %do;
|
%if &inref ne 0 %then %do;
|
||||||
%mp_binarycopy(inref=&inref,outref=_webout)
|
%mp_binarycopy(inref=&inref,outref=&outref)
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%mp_binarycopy(inloc="&inloc",outref=_webout)
|
%mp_binarycopy(inloc="&inloc",outref=&outref)
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mp_streamfile;
|
%mend mp_streamfile;
|
||||||
|
|||||||
@@ -1,51 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
@file
|
@file
|
||||||
@brief Will execute a SASjs web service on SAS 9 or Viya
|
@brief To be deprecated. Will execute a SASjs web service on SAS 9 or Viya
|
||||||
@details Prepares the input files and retrieves the resulting datasets from
|
@details Use the mx_testservice.sas macro instead (documentation can be
|
||||||
the response JSON.
|
found there)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Note - the _webout fileref should NOT be assigned prior to running this macro.
|
|
||||||
|
|
||||||
@param [in] program The _PROGRAM endpoint to test
|
|
||||||
@param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
|
|
||||||
follows:
|
|
||||||
inputfiles=inref:filename inref2:filename2
|
|
||||||
@param [in] inputdatasets= (0) All datasets in this space seperated list are
|
|
||||||
converted into SASJS-formatted CSVs (see mp_ds2csv.sas) files and added to
|
|
||||||
the list of `inputfiles` for ingestion. The dataset will be sent with the
|
|
||||||
same name (no need for a colon modifier).
|
|
||||||
@param [in] inputparams=(0) A dataset containing name/value pairs in the
|
|
||||||
following format:
|
|
||||||
|name:$32|value:$1000|
|
|
||||||
|---|---|
|
|
||||||
|stpmacname|some value|
|
|
||||||
|mustbevalidname|can be anything, oops, %abort!!|
|
|
||||||
|
|
||||||
@param [in] debug= (log) Provide the _debug value
|
|
||||||
@param [in] mdebug= (0) Set to 1 to provide macro debugging
|
|
||||||
@param [in] viyaresult= (WEBOUT_JSON) The Viya result type to return. For
|
|
||||||
more info, see mv_getjobresult.sas
|
|
||||||
@param [in] viyacontext= (SAS Job Execution compute context) The Viya compute
|
|
||||||
context on which to run the service
|
|
||||||
@param [out] outlib= (0) Output libref to contain the final tables. Set to
|
|
||||||
0 if the service output is not in JSON format.
|
|
||||||
@param [out] outref= (0) Output fileref to create, to contain the full _webout
|
|
||||||
response.
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getplatform.sas
|
@li mx_testservice.sas
|
||||||
@li mf_getuniquefileref.sas
|
|
||||||
@li mf_getuniquename.sas
|
|
||||||
@li mp_abort.sas
|
|
||||||
@li mp_binarycopy.sas
|
|
||||||
@li mp_ds2csv.sas
|
|
||||||
@li mv_getjobresult.sas
|
|
||||||
@li mv_jobflow.sas
|
|
||||||
|
|
||||||
<h4> Related Programs </h4>
|
|
||||||
@li mp_testservice.test.sas
|
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -63,216 +23,17 @@
|
|||||||
viyaresult=WEBOUT_JSON,
|
viyaresult=WEBOUT_JSON,
|
||||||
viyacontext=SAS Job Execution compute context
|
viyacontext=SAS Job Execution compute context
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
%local dbg pcnt fref1 webref i webcount var platform;
|
|
||||||
%if &mdebug=1 %then %do;
|
|
||||||
%put &sysmacroname entry vars:;
|
|
||||||
%put _local_;
|
|
||||||
%end;
|
|
||||||
%else %let dbg=*;
|
|
||||||
|
|
||||||
/* sanitise inputparams */
|
%mx_testservice(&program,
|
||||||
%let pcnt=0;
|
inputfiles=&inputfiles,
|
||||||
%if &inputparams ne 0 %then %do;
|
inputdatasets=&inputdatasets,
|
||||||
data _null_;
|
inputparams=&inputparams,
|
||||||
set &inputparams;
|
debug=&debug,
|
||||||
if not nvalid(name,'v7') then putlog (_all_)(=);
|
mdebug=&mdebug,
|
||||||
else if name in (
|
|
||||||
'program','inputfiles','inputparams','debug','outlib','outref'
|
|
||||||
) then putlog (_all_)(=);
|
|
||||||
else do;
|
|
||||||
x+1;
|
|
||||||
call symputx(name,quote(cats(value)),'l');
|
|
||||||
call symputx(cats('pval',x),name,'l');
|
|
||||||
call symputx('pcnt',x,'l');
|
|
||||||
end;
|
|
||||||
run;
|
|
||||||
%mp_abort(iftrue= (%mf_nobs(&inputparams) ne &pcnt)
|
|
||||||
,mac=&sysmacroname
|
|
||||||
,msg=%str(Invalid values in &inputparams)
|
|
||||||
)
|
|
||||||
%end;
|
|
||||||
|
|
||||||
/* convert inputdatasets to filerefs */
|
|
||||||
%if "&inputdatasets" ne "0" %then %do;
|
|
||||||
%if %quote(&inputfiles)=0 %then %let inputfiles=;
|
|
||||||
%do i=1 %to %sysfunc(countw(&inputdatasets,%str( )));
|
|
||||||
%let var=%scan(&inputdatasets,&i,%str( ));
|
|
||||||
%local dsref&i;
|
|
||||||
%let dsref&i=%mf_getuniquefileref();
|
|
||||||
%mp_ds2csv(&var,outref=&&dsref&i,headerformat=SASJS)
|
|
||||||
%let inputfiles=&inputfiles &&dsref&i:%scan(&var,-1,.);
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
|
|
||||||
%let fref1=%mf_getuniquefileref();
|
|
||||||
%let webref=%mf_getuniquefileref();
|
|
||||||
%let platform=%mf_getplatform();
|
|
||||||
%if &platform=SASMETA %then %do;
|
|
||||||
|
|
||||||
/* parse the input files */
|
|
||||||
%if %quote(&inputfiles) ne 0 %then %do;
|
|
||||||
%let webcount=%sysfunc(countw(&inputfiles));
|
|
||||||
%put &=webcount;
|
|
||||||
%do i=1 %to &webcount;
|
|
||||||
%let var=%scan(&inputfiles,&i,%str( ));
|
|
||||||
%local webfref&i webname&i;
|
|
||||||
%let webref&i=%scan(&var,1,%str(:));
|
|
||||||
%let webname&i=%scan(&var,2,%str(:));
|
|
||||||
%put webref&i=&&webref&i;
|
|
||||||
%put webname&i=&&webname&i;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
%else %let webcount=0;
|
|
||||||
|
|
||||||
proc stp program="&program";
|
|
||||||
inputparam _program="&program"
|
|
||||||
%do i=1 %to &webcount;
|
|
||||||
%if &webcount=1 %then %do;
|
|
||||||
_webin_fileref="&&webref&i"
|
|
||||||
_webin_name="&&webname&i"
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
_webin_fileref&i="&&webref&i"
|
|
||||||
_webin_name&i="&&webname&i"
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
_webin_file_count="&webcount"
|
|
||||||
_debug="&debug"
|
|
||||||
%do i=1 %to &pcnt;
|
|
||||||
/* resolve name only, proc stp fetches value */
|
|
||||||
&&pval&i=&&&&&&pval&i
|
|
||||||
%end;
|
|
||||||
;
|
|
||||||
%do i=1 %to &webcount;
|
|
||||||
inputfile &&webref&i;
|
|
||||||
%end;
|
|
||||||
outputfile _webout=&webref;
|
|
||||||
run;
|
|
||||||
|
|
||||||
data _null_;
|
|
||||||
infile &webref;
|
|
||||||
file &fref1;
|
|
||||||
input;
|
|
||||||
length line $10000;
|
|
||||||
if index(_infile_,'>>weboutBEGIN<<') then do;
|
|
||||||
line=tranwrd(_infile_,'>>weboutBEGIN<<','');
|
|
||||||
put line;
|
|
||||||
end;
|
|
||||||
else if index(_infile_,'>>weboutEND<<') then do;
|
|
||||||
line=tranwrd(_infile_,'>>weboutEND<<','');
|
|
||||||
put line;
|
|
||||||
stop;
|
|
||||||
end;
|
|
||||||
else put _infile_;
|
|
||||||
run;
|
|
||||||
data _null_;
|
|
||||||
infile &fref1;
|
|
||||||
input;
|
|
||||||
put _infile_;
|
|
||||||
run;
|
|
||||||
%if &outlib ne 0 %then %do;
|
|
||||||
libname &outlib json (&fref1);
|
|
||||||
%end;
|
|
||||||
%if &outref ne 0 %then %do;
|
|
||||||
filename &outref temp;
|
|
||||||
%mp_binarycopy(inref=&webref,outref=&outref)
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%end;
|
|
||||||
%else %if &platform=SASVIYA %then %do;
|
|
||||||
|
|
||||||
/* prepare inputparams */
|
|
||||||
%local ds1;
|
|
||||||
%let ds1=%mf_getuniquename();
|
|
||||||
%if "&inputparams" ne "0" %then %do;
|
|
||||||
proc transpose data=&inputparams out=&ds1;
|
|
||||||
id name;
|
|
||||||
var value;
|
|
||||||
run;
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
data &ds1;run;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
/* parse the input files - convert to sasjs params */
|
|
||||||
%local webcount i var sasjs_tables;
|
|
||||||
%if %quote(&inputfiles) ne 0 %then %do;
|
|
||||||
%let webcount=%sysfunc(countw(&inputfiles));
|
|
||||||
%put &=webcount;
|
|
||||||
%do i=1 %to &webcount;
|
|
||||||
%let var=%scan(&inputfiles,&i,%str( ));
|
|
||||||
%local webfref&i webname&i sasjs&i.data;
|
|
||||||
%let webref&i=%scan(&var,1,%str(:));
|
|
||||||
%let webname&i=%scan(&var,2,%str(:));
|
|
||||||
%put webref&i=&&webref&i;
|
|
||||||
%put webname&i=&&webname&i;
|
|
||||||
|
|
||||||
%let sasjs_tables=&sasjs_tables &&webname&i;
|
|
||||||
data _null_;
|
|
||||||
infile &&webref&i lrecl=32767;
|
|
||||||
input;
|
|
||||||
if _n_=1 then call symputx("sasjs&i.data",_infile_);
|
|
||||||
else call symputx(
|
|
||||||
"sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_)
|
|
||||||
);
|
|
||||||
putlog "&sysmacroname infile: " _infile_;
|
|
||||||
run;
|
|
||||||
data &ds1;
|
|
||||||
set &ds1;
|
|
||||||
length sasjs&i.data $32767 sasjs_tables $1000;
|
|
||||||
sasjs&i.data=symget("sasjs&i.data");
|
|
||||||
sasjs_tables=symget("sasjs_tables");
|
|
||||||
run;
|
|
||||||
%end;
|
|
||||||
%end;
|
|
||||||
%else %let webcount=0;
|
|
||||||
|
|
||||||
data &ds1;
|
|
||||||
retain _program "&program";
|
|
||||||
retain _contextname "&viyacontext";
|
|
||||||
set &ds1;
|
|
||||||
putlog "&sysmacroname inputparams:";
|
|
||||||
putlog (_all_)(=);
|
|
||||||
run;
|
|
||||||
|
|
||||||
%mv_jobflow(inds=&ds1
|
|
||||||
,maxconcurrency=1
|
|
||||||
,outds=work.results
|
|
||||||
,outref=&fref1
|
|
||||||
,mdebug=&mdebug
|
|
||||||
)
|
|
||||||
/* show the log */
|
|
||||||
data _null_;
|
|
||||||
infile &fref1;
|
|
||||||
input;
|
|
||||||
putlog _infile_;
|
|
||||||
run;
|
|
||||||
/* get the uri to fetch results */
|
|
||||||
data _null_;
|
|
||||||
set work.results;
|
|
||||||
call symputx('uri',uri);
|
|
||||||
putlog "&sysmacroname: fetching results for " uri;
|
|
||||||
run;
|
|
||||||
/* fetch results from webout.json */
|
|
||||||
%mv_getjobresult(uri=&uri,
|
|
||||||
result=&viyaresult,
|
|
||||||
outref=&outref,
|
|
||||||
outlib=&outlib,
|
outlib=&outlib,
|
||||||
mdebug=&mdebug
|
outref=&outref,
|
||||||
|
viyaresult=&viyaresult,
|
||||||
|
viyacontext=&viyacontext
|
||||||
)
|
)
|
||||||
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
%put %str(ERR)OR: Unrecognised platform: &platform;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%if &mdebug=0 %then %do;
|
|
||||||
filename &webref clear;
|
|
||||||
%end;
|
|
||||||
%else %do;
|
|
||||||
%put &sysmacroname exit vars:;
|
|
||||||
%put _local_;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%mend mp_testservice;
|
%mend mp_testservice;
|
||||||
@@ -4,9 +4,7 @@
|
|||||||
@details Loops with a `sleep()` command until a file arrives or the max wait
|
@details Loops with a `sleep()` command until a file arrives or the max wait
|
||||||
period expires.
|
period expires.
|
||||||
|
|
||||||
@example
|
Example: Wait 3 minutes OR for /tmp/flag.txt to appear
|
||||||
|
|
||||||
Wait 3 minutes OR for /tmp/flag.txt to appear
|
|
||||||
|
|
||||||
%mp_wait4file(/tmp/flag.txt , maxwait=60*3)
|
%mp_wait4file(/tmp/flag.txt , maxwait=60*3)
|
||||||
|
|
||||||
|
|||||||
39
build.py
39
build.py
@@ -22,20 +22,36 @@ 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\" /source2;\n\n")
|
|
||||||
|
ml.write("/* ensure big enough lrecl to avoid lua compilation issues */\n")
|
||||||
|
ml.write("%local optval;\n")
|
||||||
|
ml.write("%let optval=%sysfunc(getoption(lrecl));\n")
|
||||||
|
ml.write("options lrecl=1024;\n\n")
|
||||||
|
ml.write("/* execute the lua code by using a .lua extension */\n")
|
||||||
|
ml.write("%inc \"%sysfunc(pathname(work))/" +
|
||||||
|
name + ".lua\" /source2;\n\n")
|
||||||
|
ml.write("options lrecl=&optval;\n\n")
|
||||||
ml.write("%mend " + name + ";\n")
|
ml.write("%mend " + name + ";\n")
|
||||||
|
|
||||||
ml.close()
|
ml.close()
|
||||||
|
|
||||||
# prepare web files
|
# prepare web files
|
||||||
files=['viya/mv_createwebservice.sas','meta/mm_createwebservice.sas']
|
files = ['viya/mv_createwebservice.sas',
|
||||||
|
'meta/mm_createwebservice.sas', 'server/ms_createwebservice.sas']
|
||||||
for file in files:
|
for file in files:
|
||||||
webout0 = open('base/mp_jsonout.sas', 'r')
|
webout0 = open('base/mp_jsonout.sas', 'r')
|
||||||
|
webout1 = open('base/mf_getuser.sas', 'r')
|
||||||
|
|
||||||
if file == 'viya/mv_createwebservice.sas':
|
if file == 'viya/mv_createwebservice.sas':
|
||||||
webout1=open('viya/mv_webout.sas',"r")
|
webout2 = open('viya/mv_webout.sas', "r")
|
||||||
|
weboutfiles = [webout0, webout1, webout2]
|
||||||
|
elif file == 'server/ms_createwebservice.sas':
|
||||||
|
webout2 = open('server/ms_webout.sas', "r")
|
||||||
|
webout3 = open('server/mfs_httpheader.sas', 'r')
|
||||||
|
weboutfiles = [webout0, webout1, webout2, webout3]
|
||||||
else:
|
else:
|
||||||
webout1=open('meta/mm_webout.sas','r')
|
webout2 = open('meta/mm_webout.sas', 'r')
|
||||||
webout2=open('base/mf_getuser.sas','r')
|
weboutfiles = [webout0, webout1, webout2]
|
||||||
outfile = open(file + 'TEMP', 'w')
|
outfile = open(file + 'TEMP', 'w')
|
||||||
infile = open(file, 'r')
|
infile = open(file, 'r')
|
||||||
delrow = 0
|
delrow = 0
|
||||||
@@ -43,13 +59,14 @@ for file in files:
|
|||||||
if line == '/* WEBOUT BEGIN */\n':
|
if line == '/* WEBOUT BEGIN */\n':
|
||||||
delrow = 1
|
delrow = 1
|
||||||
outfile.write('/* WEBOUT BEGIN */\n')
|
outfile.write('/* WEBOUT BEGIN */\n')
|
||||||
weboutfiles=[webout0,webout1,webout2]
|
|
||||||
for weboutfile in weboutfiles:
|
for weboutfile in weboutfiles:
|
||||||
stripcomment = 1
|
stripcomment = 1
|
||||||
for w in weboutfile:
|
for w in weboutfile:
|
||||||
if w=='**/\n': stripcomment=0
|
if w == '**/\n':
|
||||||
|
stripcomment = 0
|
||||||
elif stripcomment == 0:
|
elif stripcomment == 0:
|
||||||
outfile.write(" put '" + w.rstrip().replace("'","''") + " ';\n")
|
outfile.write(
|
||||||
|
" put '" + w.rstrip().replace("'", "''") + " ';\n")
|
||||||
elif delrow == 1 and line == '/* WEBOUT END */\n':
|
elif delrow == 1 and line == '/* WEBOUT END */\n':
|
||||||
delrow = 0
|
delrow = 0
|
||||||
outfile.write('/* WEBOUT END */\n')
|
outfile.write('/* WEBOUT END */\n')
|
||||||
@@ -57,6 +74,7 @@ for file in files:
|
|||||||
outfile.write(line.rstrip() + "\n")
|
outfile.write(line.rstrip() + "\n")
|
||||||
webout0.close()
|
webout0.close()
|
||||||
webout1.close()
|
webout1.close()
|
||||||
|
webout2.close()
|
||||||
outfile.close()
|
outfile.close()
|
||||||
infile.close()
|
infile.close()
|
||||||
os.remove(file)
|
os.remove(file)
|
||||||
@@ -84,9 +102,10 @@ 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','server','viya','lua','fcmp']
|
folders = ['base', 'ddl', 'meta', 'metax', 'server', 'viya', 'lua', 'fcmp', 'xplatform']
|
||||||
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()
|
||||||
with open('mc_' + folder + '.sas', 'w') as outfile:
|
with open('mc_' + folder + '.sas', 'w') as outfile:
|
||||||
for fname in filenames:
|
for fname in filenames:
|
||||||
|
|||||||
44
ddl/mddl_dc_difftable.sas
Normal file
44
ddl/mddl_dc_difftable.sas
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Difftable DDL
|
||||||
|
@details Used to store changes to tables. Used by mp_storediffs.sas
|
||||||
|
and mp_stackdiffs.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_dc_difftable(libds=WORK.DIFFTABLE);
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
load_ref char(36) label='unique load reference',
|
||||||
|
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
|
||||||
|
libref char(8) label='Library Reference (8 chars)',
|
||||||
|
dsn char(32) label='Dataset Name (32 chars)',
|
||||||
|
key_hash char(32) label=
|
||||||
|
'MD5 Hash of primary key values (pipe seperated)',
|
||||||
|
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
|
||||||
|
is_pk num label='Is Primary Key Field? (1/0)',
|
||||||
|
is_diff num label=
|
||||||
|
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
|
||||||
|
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
|
||||||
|
tgtvar_nm char(32) label='Target variable name (32 chars)',
|
||||||
|
oldval_num num format=best32. label='Old (numeric) value',
|
||||||
|
newval_num num format=best32. label='New (numeric) value',
|
||||||
|
oldval_char char(32765) label='Old (character) value',
|
||||||
|
newval_char char(32765) label='New (character) value'
|
||||||
|
);
|
||||||
|
|
||||||
|
%local lib;
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||||
|
%else %let lib=%scan(&libds,1,.);
|
||||||
|
|
||||||
|
proc datasets lib=&lib noprint;
|
||||||
|
modify %scan(&libds,-1,.);
|
||||||
|
index create
|
||||||
|
pk_mpe_audit=(load_ref libref dsn key_hash tgtvar_nm)
|
||||||
|
/nomiss unique;
|
||||||
|
quit;
|
||||||
|
|
||||||
|
%mend mddl_dc_difftable;
|
||||||
40
ddl/mddl_dc_filterdetail.sas
Normal file
40
ddl/mddl_dc_filterdetail.sas
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Filtertable DDL
|
||||||
|
@details For storing detailed filter values. Used by
|
||||||
|
mp_filterstore.sas.
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_dc_filterdetail(libds=WORK.FILTER_DETAIL);
|
||||||
|
|
||||||
|
%local nn lib;
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
|
%let nn=not null;
|
||||||
|
%end;
|
||||||
|
%else %let nn=;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
filter_hash char(32) &nn,
|
||||||
|
filter_line num &nn,
|
||||||
|
group_logic char(3) &nn,
|
||||||
|
subgroup_logic char(3) &nn,
|
||||||
|
subgroup_id num &nn,
|
||||||
|
variable_nm varchar(32) &nn,
|
||||||
|
operator_nm varchar(12) &nn,
|
||||||
|
raw_value varchar(4000) &nn,
|
||||||
|
processed_dttm num &nn format=E8601DT26.6
|
||||||
|
);
|
||||||
|
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||||
|
%else %let lib=%scan(&libds,1,.);
|
||||||
|
|
||||||
|
proc datasets lib=&lib noprint;
|
||||||
|
modify %scan(&libds,-1,.);
|
||||||
|
index create pk_mpe_filterdetail=(filter_hash filter_line)/nomiss unique;
|
||||||
|
quit;
|
||||||
|
|
||||||
|
%mend mddl_dc_filterdetail;
|
||||||
35
ddl/mddl_dc_filtersummary.sas
Normal file
35
ddl/mddl_dc_filtersummary.sas
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Filtersummary DDL
|
||||||
|
@details For storing summary filter values. Used by
|
||||||
|
mp_filterstore.sas.
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_dc_filtersummary(libds=WORK.FILTER_SUMMARY);
|
||||||
|
|
||||||
|
%local nn lib;
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
|
%let nn=not null;
|
||||||
|
%end;
|
||||||
|
%else %let nn=;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
filter_rk num &nn,
|
||||||
|
filter_hash char(32) &nn,
|
||||||
|
filter_table char(41) &nn,
|
||||||
|
processed_dttm num &nn format=E8601DT26.6
|
||||||
|
);
|
||||||
|
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||||
|
%else %let lib=%scan(&libds,1,.);
|
||||||
|
|
||||||
|
proc datasets lib=&lib noprint;
|
||||||
|
modify %scan(&libds,-1,.);
|
||||||
|
index create filter_rk /nomiss unique;
|
||||||
|
quit;
|
||||||
|
|
||||||
|
%mend mddl_dc_filtersummary;
|
||||||
41
ddl/mddl_dc_locktable.sas
Normal file
41
ddl/mddl_dc_locktable.sas
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Locktable DDL
|
||||||
|
@details For "locking" tables prior to multipass loads. Used by
|
||||||
|
mp_lockanytable.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_dc_locktable(libds=WORK.LOCKTABLE);
|
||||||
|
|
||||||
|
%local nn lib;
|
||||||
|
%if "%substr(&sysver,1,1)" ne "4" and "%substr(&sysver,1,1)" ne "5" %then %do;
|
||||||
|
%let nn=not null;
|
||||||
|
%end;
|
||||||
|
%else %let nn=;
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
lock_lib char(8),
|
||||||
|
lock_ds char(32),
|
||||||
|
lock_status_cd char(10) &nn,
|
||||||
|
lock_user_nm char(100) &nn ,
|
||||||
|
lock_ref char(200),
|
||||||
|
lock_pid char(10),
|
||||||
|
lock_start_dttm num format=E8601DT26.6,
|
||||||
|
lock_end_dttm num format=E8601DT26.6
|
||||||
|
);
|
||||||
|
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||||
|
%else %let lib=%scan(&libds,1,.);
|
||||||
|
|
||||||
|
proc datasets lib=&lib noprint;
|
||||||
|
modify %scan(&libds,-1,.);
|
||||||
|
index create
|
||||||
|
pk_mp_lockanytable=(lock_lib lock_ds)
|
||||||
|
/nomiss unique;
|
||||||
|
quit;
|
||||||
|
|
||||||
|
%mend mddl_dc_locktable;
|
||||||
33
ddl/mddl_dc_maxkeytable.sas
Normal file
33
ddl/mddl_dc_maxkeytable.sas
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Maxkeytable DDL
|
||||||
|
@details For storing the maximum retained key information. Used
|
||||||
|
by mp_retainedkey.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_dc_maxkeytable(libds=WORK.MAXKEYTABLE);
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
keytable varchar(41) label='Base table in libref.dataset format',
|
||||||
|
keycolumn char(32) format=$32.
|
||||||
|
label='The Retained key field containing the key values.',
|
||||||
|
max_key num label=
|
||||||
|
'Integer representing current max RK or SK value in the KEYTABLE',
|
||||||
|
processed_dttm num format=E8601DT26.6
|
||||||
|
label='Datetime this value was last updated'
|
||||||
|
);
|
||||||
|
|
||||||
|
%local lib;
|
||||||
|
%let libds=%upcase(&libds);
|
||||||
|
%if %index(&libds,.)=0 %then %let lib=WORK;
|
||||||
|
%else %let lib=%scan(&libds,1,.);
|
||||||
|
|
||||||
|
proc datasets lib=&lib noprint;
|
||||||
|
modify %scan(&libds,-1,.);
|
||||||
|
index create keytable /nomiss unique;
|
||||||
|
quit;
|
||||||
|
|
||||||
|
%mend mddl_dc_maxkeytable;
|
||||||
43
ddl/mddl_sas_cntlout.sas
Normal file
43
ddl/mddl_sas_cntlout.sas
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief The CNTLOUT table generated by proc format
|
||||||
|
@details This table will actually change format depending on the data values,
|
||||||
|
therefore the max possible lengths are described here to enable consistency
|
||||||
|
when dealing with format data.
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);
|
||||||
|
|
||||||
|
proc sql;
|
||||||
|
create table &libds(
|
||||||
|
FMTNAME char(32) label='Format name'
|
||||||
|
/*
|
||||||
|
to accommodate larger START values, mp_loadformat.sas will need the
|
||||||
|
SQL dependency removed (proc sql needs to accommodate 3 index values in
|
||||||
|
a 32767 ibufsize limit)
|
||||||
|
*/
|
||||||
|
,START char(10000) label='Starting value for format'
|
||||||
|
,END char(32767) label='Ending value for format'
|
||||||
|
,LABEL char(32767) label='Format value label'
|
||||||
|
,MIN num length=3 label='Minimum length'
|
||||||
|
,MAX num length=3 label='Maximum length'
|
||||||
|
,DEFAULT num length=3 label='Default length'
|
||||||
|
,LENGTH num length=3 label='Format length'
|
||||||
|
,FUZZ num label='Fuzz value'
|
||||||
|
,PREFIX char(2) label='Prefix characters'
|
||||||
|
,MULT num label='Multiplier'
|
||||||
|
,FILL char(1) label='Fill character'
|
||||||
|
,NOEDIT num length=3 label='Is picture string noedit?'
|
||||||
|
,TYPE char(1) label='Type of format'
|
||||||
|
,SEXCL char(1) label='Start exclusion'
|
||||||
|
,EEXCL char(1) label='End exclusion'
|
||||||
|
,HLO char(13) label='Additional information'
|
||||||
|
,DECSEP char(1) label='Decimal separator'
|
||||||
|
,DIG3SEP char(1) label='Three-digit separator'
|
||||||
|
,DATATYPE char(8) label='Date/time/datetime?'
|
||||||
|
,LANGUAGE char(8) label='Language for date strings'
|
||||||
|
);
|
||||||
|
|
||||||
|
%mend mddl_sas_cntlout;
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
/**
|
|
||||||
@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] 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
|
|
||||||
@param [out] insert_cmplib= DEPRECATED - The CMPLIB option is checked and
|
|
||||||
values inserted only if needed.
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
|
||||||
@li mcf_init.sas
|
|
||||||
|
|
||||||
<h4> Related Programs </h4>
|
|
||||||
@li mcf_stpsrv_header.test.sas
|
|
||||||
@li mp_init.sas
|
|
||||||
|
|
||||||
**/
|
|
||||||
|
|
||||||
%macro mcf_stpsrv_header(wrap=NO
|
|
||||||
,insert_cmplib=DEPRECATED
|
|
||||||
,lib=WORK
|
|
||||||
,cat=SASJS
|
|
||||||
,pkg=UTILS
|
|
||||||
)/*/STORE SOURCE*/;
|
|
||||||
%local i var cmpval found;
|
|
||||||
%if %mcf_init(stpsrv_header)=1 %then %return;
|
|
||||||
|
|
||||||
%if &wrap=YES %then %do;
|
|
||||||
proc fcmp outlib=&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;
|
|
||||||
|
|
||||||
/* insert the CMPLIB if not already there */
|
|
||||||
%let cmpval=%sysfunc(getoption(cmplib));
|
|
||||||
%let found=0;
|
|
||||||
%do i=1 %to %sysfunc(countw(&cmpval,%str( %(%))));
|
|
||||||
%let var=%scan(&cmpval,&i,%str( %(%)));
|
|
||||||
%if &var=&lib..&cat %then %let found=1;
|
|
||||||
%end;
|
|
||||||
%if &found=0 %then %do;
|
|
||||||
options insert=(CMPLIB=(&lib..&cat));
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%mend mcf_stpsrv_header;
|
|
||||||
@@ -39,6 +39,14 @@ data _null_;
|
|||||||
put 'io.close(file) ';
|
put 'io.close(file) ';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
|
/* ensure big enough lrecl to avoid lua compilation issues */
|
||||||
|
%local optval;
|
||||||
|
%let optval=%sysfunc(getoption(lrecl));
|
||||||
|
options lrecl=1024;
|
||||||
|
|
||||||
|
/* execute the lua code by using a .lua extension */
|
||||||
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
|
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
|
||||||
|
|
||||||
|
options lrecl=&optval;
|
||||||
|
|
||||||
%mend ml_gsubfile;
|
%mend ml_gsubfile;
|
||||||
|
|||||||
@@ -389,6 +389,14 @@ data _null_;
|
|||||||
put '-- JSON.LUA ENDS HERE ';
|
put '-- JSON.LUA ENDS HERE ';
|
||||||
run;
|
run;
|
||||||
|
|
||||||
|
/* ensure big enough lrecl to avoid lua compilation issues */
|
||||||
|
%local optval;
|
||||||
|
%let optval=%sysfunc(getoption(lrecl));
|
||||||
|
options lrecl=1024;
|
||||||
|
|
||||||
|
/* execute the lua code by using a .lua extension */
|
||||||
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
|
%inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
|
||||||
|
|
||||||
|
options lrecl=&optval;
|
||||||
|
|
||||||
%mend ml_json;
|
%mend ml_json;
|
||||||
|
|||||||
45
main.dox
45
main.dox
@@ -20,6 +20,16 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*! \dir ddl
|
||||||
|
* \brief Data Definition Language files
|
||||||
|
* \details Provides templates for commonly used tables in sasjs/core.
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
* OS independent
|
||||||
|
* No X command
|
||||||
|
* Prefixes: _mddl_
|
||||||
|
*/
|
||||||
|
|
||||||
/*! \dir fcmp
|
/*! \dir fcmp
|
||||||
* \brief Macros for generating FCMP functions
|
* \brief Macros for generating FCMP functions
|
||||||
* \details These macros have the following attributes:
|
* \details These macros have the following attributes:
|
||||||
@@ -93,3 +103,38 @@
|
|||||||
* Prefixes: _ml_
|
* Prefixes: _ml_
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/base
|
||||||
|
* \brief Tests for Base macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/ddlonly
|
||||||
|
* \brief Tests for DDL macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/sas9only
|
||||||
|
* \brief Tests for SAS Metadata macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/serveronly
|
||||||
|
* \brief Tests for SASjs Server macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/viyaonly
|
||||||
|
* \brief Tests for Viya macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir tests/x-platform
|
||||||
|
* \brief Tests for cross-platform macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! \dir xplatform
|
||||||
|
* \brief Cross Platform, works on all SAS servers (Viya, EBI, SASjs)
|
||||||
|
* \details Useful when you need to run a single piece of code against Viya,
|
||||||
|
SAS 9 with metadata, or SASjs on Base SAS.
|
||||||
|
|
||||||
|
* OS independent
|
||||||
|
* No X command
|
||||||
|
* Prefixes: _mx_
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
@details Adds a user to a metadata group. The macro first checks whether the
|
@details Adds a user to a metadata group. The macro first checks whether the
|
||||||
user is in that group, and if not, the user is added.
|
user is in that group, and if not, the user is added.
|
||||||
|
|
||||||
|
Note that the macro does not check inherited group memberships - it looks at
|
||||||
|
direct members only.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
%mm_adduser2group(user=sasdemo
|
%mm_adduser2group(user=sasdemo
|
||||||
@@ -12,10 +15,10 @@
|
|||||||
|
|
||||||
@param user= the user name (not displayname)
|
@param user= the user name (not displayname)
|
||||||
@param group= the group to which to add the user
|
@param group= the group to which to add the user
|
||||||
@param mdebug= set to 1 to show debug info in log
|
@param mdebug= (0) set to 1 to show debug info in log
|
||||||
|
|
||||||
@warning the macro does not check inherited group memberships - it looks at
|
<h4> Related Files </h4>
|
||||||
direct members only
|
@li ms_adduser2group.sas
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
|
|
||||||
@param libref the libref (not name) of the metadata library
|
@param [in] libref The libref (not name) of the metadata library
|
||||||
@param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
|
@param [in] mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
|
||||||
silently return
|
silently return
|
||||||
|
|
||||||
@returns libname statement
|
@returns libname statement
|
||||||
@@ -28,11 +28,11 @@
|
|||||||
libref
|
libref
|
||||||
,mAbort=HARD
|
,mAbort=HARD
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
%local mp_abort msg;
|
||||||
|
%let mp_abort=0;
|
||||||
%if %sysfunc(libref(&libref)) %then %do;
|
%if %sysfunc(libref(&libref)) %then %do;
|
||||||
%local mp_abort msg; %let mp_abort=0;
|
|
||||||
data _null_;
|
data _null_;
|
||||||
length liburi LibName $200;
|
length liburi LibName msg $200;
|
||||||
call missing(of _all_);
|
call missing(of _all_);
|
||||||
nobj=metadata_getnobj("omsobj:SASLibrary?@Libref='&libref'",1,liburi);
|
nobj=metadata_getnobj("omsobj:SASLibrary?@Libref='&libref'",1,liburi);
|
||||||
if nobj=1 then do;
|
if nobj=1 then do;
|
||||||
@@ -40,7 +40,30 @@
|
|||||||
/* now try and assign it */
|
/* now try and assign it */
|
||||||
if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do;
|
if libname("&libref",,'meta',cats('liburi="',liburi,'";')) ne 0 then do;
|
||||||
putlog "&libref could not be assigned";
|
putlog "&libref could not be assigned";
|
||||||
call symputx('msg',sysmsg(),'l');
|
putlog liburi=;
|
||||||
|
/**
|
||||||
|
* Fetch the system message for display in the abort modal. This is
|
||||||
|
* not always helpful though. One example, previously received:
|
||||||
|
* NOTE: Libref XX refers to the same library metadata as libref XX.
|
||||||
|
*/
|
||||||
|
msg=sysmsg();
|
||||||
|
if msg=:'ERROR: Libref SAVE is not assigned.' then do;
|
||||||
|
msg=catx(" ",
|
||||||
|
"Could not assign %upcase(&libref).",
|
||||||
|
"Please check metadata permissions! Libname:",libname,
|
||||||
|
"Liburi:",liburi
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
else if msg="ERROR: User does not have appropriate authorization "!!
|
||||||
|
"level for library SAVE."
|
||||||
|
then do;
|
||||||
|
msg=catx(" ",
|
||||||
|
"ERROR: User does not have appropriate authorization level",
|
||||||
|
"for library %upcase(&libref), libname:",libname,
|
||||||
|
"Liburi:",liburi
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
call symputx('msg',msg,'l');
|
||||||
if "&mabort"='HARD' then call symputx('mp_abort',1,'l');
|
if "&mabort"='HARD' then call symputx('mp_abort',1,'l');
|
||||||
end;
|
end;
|
||||||
else do;
|
else do;
|
||||||
@@ -59,20 +82,16 @@
|
|||||||
end;
|
end;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &mp_abort=1 %then %do;
|
|
||||||
%mp_abort(iftrue= (&mp_abort=1)
|
|
||||||
,mac=&sysmacroname
|
|
||||||
,msg=&msg
|
|
||||||
)
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
%else %if %length(&msg)>2 %then %do;
|
|
||||||
%put NOTE: &msg;
|
%put NOTE: &msg;
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%end;
|
%end;
|
||||||
%else %do;
|
%else %do;
|
||||||
%put NOTE: Library &libref is already assigned;
|
%put NOTE: Library &libref is already assigned;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
|
%mp_abort(iftrue= (&mp_abort=1)
|
||||||
|
,mac=mm_assignlib.sas
|
||||||
|
,msg=%superq(msg)
|
||||||
|
)
|
||||||
|
|
||||||
%mend mm_assignlib;
|
%mend mm_assignlib;
|
||||||
|
|||||||
@@ -59,7 +59,10 @@
|
|||||||
%&mD.put Executing &sysmacroname..sas;
|
%&mD.put Executing &sysmacroname..sas;
|
||||||
%&mD.put _local_;
|
%&mD.put _local_;
|
||||||
|
|
||||||
%mf_verifymacvars(tree name)
|
%mp_abort(iftrue= (%mf_verifymacvars(tree name)=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Empty inputs: tree name)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check tree exists
|
* check tree exists
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
@file mm_createdataset.sas
|
@file
|
||||||
@brief Create a dataset from a metadata definition
|
@brief Create an empty dataset from a metadata definition
|
||||||
@details This macro was built to support viewing empty tables in
|
@details This macro was built to support viewing empty tables in
|
||||||
https://datacontroller.io - a free evaluation copy is available by
|
https://datacontroller.io
|
||||||
contacting the author (Allan Bowe).
|
|
||||||
|
|
||||||
The table can be retrieved using LIBRARY.DATASET reference, or directly
|
The table can be retrieved using LIBRARY.DATASET reference, or directly
|
||||||
using the metadata URI.
|
using the metadata URI.
|
||||||
|
|
||||||
The dataset is written to the WORK library.
|
The dataset is written to the WORK library.
|
||||||
|
|
||||||
usage:
|
Usage:
|
||||||
|
|
||||||
%mm_createdataset(libds=metlib.some_dataset)
|
%mm_createdataset(libds=metlib.some_dataset)
|
||||||
|
|
||||||
@@ -26,9 +25,9 @@
|
|||||||
@param libds= library.dataset metadata source. Note - table names in metadata
|
@param libds= library.dataset metadata source. Note - table names in metadata
|
||||||
can be longer than 32 chars (just fyi, not an issue here)
|
can be longer than 32 chars (just fyi, not an issue here)
|
||||||
@param tableuri= Metadata URI of the table to be created
|
@param tableuri= Metadata URI of the table to be created
|
||||||
@param outds= The dataset to create, default is `work.mm_createdataset`.
|
@param outds= (work.mm_createdataset) The dataset to create. The table name
|
||||||
The table name needs to be 32 chars or less as per SAS naming rules.
|
needs to be 32 chars or less as per SAS naming rules.
|
||||||
@param mdebug= set DBG to 1 to disable DEBUG messages
|
@param mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -54,14 +53,23 @@
|
|||||||
%mm_gettables(uri=&liburi,outds=&tempds2)
|
%mm_gettables(uri=&liburi,outds=&tempds2)
|
||||||
data _null_;
|
data _null_;
|
||||||
set &tempds2;
|
set &tempds2;
|
||||||
if upcase(tablename)="%upcase(%scan(&libds,2,.))";
|
where upcase(tablename)="%upcase(%scan(&libds,2,.))";
|
||||||
|
&dbg putlog tableuri=;
|
||||||
call symputx('tableuri',tableuri);
|
call symputx('tableuri',tableuri);
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
data;run;%let tempds3=&syslast;
|
data;run;
|
||||||
|
%let tempds3=&syslast;
|
||||||
%mm_getcols(tableuri=&tableuri,outds=&tempds3)
|
%mm_getcols(tableuri=&tableuri,outds=&tempds3)
|
||||||
|
|
||||||
|
%if %mf_nobs(&tempds3)=0 %then %do;
|
||||||
|
%put &libds (&tableuri) has no columns defined!!;
|
||||||
|
data &outds;
|
||||||
|
run;
|
||||||
|
%return;
|
||||||
|
%end;
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
set &tempds3 end=last;
|
set &tempds3 end=last;
|
||||||
if _n_=1 then call execute('data &outds;');
|
if _n_=1 then call execute('data &outds;');
|
||||||
|
|||||||
@@ -47,7 +47,10 @@
|
|||||||
%&mD.put Executing &sysmacroname..sas;
|
%&mD.put Executing &sysmacroname..sas;
|
||||||
%&mD.put _local_;
|
%&mD.put _local_;
|
||||||
|
|
||||||
%mf_verifymacvars(tree name)
|
%mp_abort(iftrue= (%mf_verifymacvars(tree name)=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Empty inputs: tree name)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check tree exists
|
* check tree exists
|
||||||
|
|||||||
@@ -12,12 +12,13 @@
|
|||||||
The macro is idempotent - if you run it twice, it will only create a folder
|
The macro is idempotent - if you run it twice, it will only create a folder
|
||||||
once.
|
once.
|
||||||
|
|
||||||
usage:
|
Usage:
|
||||||
|
|
||||||
%mm_createfolder(path=/some/meta/folder)
|
%mm_createfolder(path=/some/meta/folder)
|
||||||
|
|
||||||
@param [in] path= Name of the folder to create.
|
@param [in] path= Name of the folder to create.
|
||||||
@param [in] mdebug= set DBG to 1 to disable DEBUG messages
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|||||||
@@ -133,12 +133,14 @@ run;
|
|||||||
filename &frefin temp;
|
filename &frefin temp;
|
||||||
filename &frefout temp;
|
filename &frefout temp;
|
||||||
|
|
||||||
|
%mp_abort(iftrue= (
|
||||||
|
&engine=BASE & %mf_verifymacvars(libname libref engine servercontext tree)=0
|
||||||
|
)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Empty inputs: libname libref engine servercontext tree)
|
||||||
|
)
|
||||||
|
|
||||||
%if &engine=BASE %then %do;
|
%if &engine=BASE %then %do;
|
||||||
|
|
||||||
%mf_verifymacvars(libname libref engine servercontext tree)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that the ServerContext exists
|
* Check that the ServerContext exists
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
This macro is idempotent - if you run it twice, it will only create an STP
|
This macro is idempotent - if you run it twice, it will only create an STP
|
||||||
once.
|
once.
|
||||||
|
|
||||||
usage (type 1 STP):
|
Usage (type 1 STP):
|
||||||
|
|
||||||
%mm_createstp(stpname=MyNewSTP
|
%mm_createstp(stpname=MyNewSTP
|
||||||
,filename=mySpecialProgram.sas
|
,filename=mySpecialProgram.sas
|
||||||
@@ -31,7 +31,8 @@
|
|||||||
putlog (_all_)(=);
|
putlog (_all_)(=);
|
||||||
run;
|
run;
|
||||||
|
|
||||||
usage (type 2 STP):
|
Usage (type 2 STP):
|
||||||
|
|
||||||
%mm_createstp(stpname=MyNewType2STP
|
%mm_createstp(stpname=MyNewType2STP
|
||||||
,filename=mySpecialProgram.sas
|
,filename=mySpecialProgram.sas
|
||||||
,directory=SASEnvironment/SASCode/STPs
|
,directory=SASEnvironment/SASCode/STPs
|
||||||
@@ -74,8 +75,9 @@
|
|||||||
@li mf_verifymacvars.sas
|
@li mf_verifymacvars.sas
|
||||||
@li mm_getdirectories.sas
|
@li mm_getdirectories.sas
|
||||||
@li mm_updatestpsourcecode.sas
|
@li mm_updatestpsourcecode.sas
|
||||||
@li mp_dropmembers.sas
|
|
||||||
@li mm_getservercontexts.sas
|
@li mm_getservercontexts.sas
|
||||||
|
@li mp_abort.sas
|
||||||
|
@li mp_dropmembers.sas
|
||||||
|
|
||||||
<h4> Related Macros </h4>
|
<h4> Related Macros </h4>
|
||||||
@li mm_createwebservice.sas
|
@li mm_createwebservice.sas
|
||||||
@@ -108,7 +110,12 @@
|
|||||||
%&mD.put Executing mm_CreateSTP.sas;
|
%&mD.put Executing mm_CreateSTP.sas;
|
||||||
%&mD.put _local_;
|
%&mD.put _local_;
|
||||||
|
|
||||||
%mf_verifymacvars(stpname filename directory tree)
|
%mp_abort(
|
||||||
|
iftrue=(%mf_verifymacvars(stpname filename directory tree)=0)
|
||||||
|
,mac=&sysmacroname
|
||||||
|
,msg=%str(Empty inputs: stpname filename directory tree)
|
||||||
|
)
|
||||||
|
|
||||||
%mp_dropmembers(%scan(&outds,2,.))
|
%mp_dropmembers(%scan(&outds,2,.))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
@file mm_createwebservice.sas
|
@file mm_createwebservice.sas
|
||||||
@brief Create a Web Ready Stored Process
|
@brief Create a Web Ready Stored Process
|
||||||
@details This macro creates a Type 2 Stored Process with the mm_webout macro
|
@details This macro creates a Type 2 Stored Process with the mm_webout macro
|
||||||
included as pre-code.
|
(and dependencies) included as pre-code.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
%* compile macros ;
|
%* compile macros ;
|
||||||
@@ -92,25 +93,41 @@ data _null_;
|
|||||||
file sasjs lrecl=3000 ;
|
file sasjs lrecl=3000 ;
|
||||||
put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
|
put "/* Created on %sysfunc(datetime(),datetime19.) by %mf_getuser() */";
|
||||||
/* WEBOUT BEGIN */
|
/* WEBOUT BEGIN */
|
||||||
put ' ';
|
|
||||||
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
|
put '%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y ';
|
||||||
put ' ,engine=DATASTEP ';
|
put ' ,engine=DATASTEP ';
|
||||||
put ' ,missing=NULL ';
|
put ' ,missing=NULL ';
|
||||||
put ' ,showmeta=NO ';
|
put ' ,showmeta=N ';
|
||||||
|
put ' ,maxobs=MAX ';
|
||||||
put ')/*/STORE SOURCE*/; ';
|
put ')/*/STORE SOURCE*/; ';
|
||||||
put '%local tempds colinfo fmtds i numcols; ';
|
put '%local tempds colinfo fmtds i numcols numobs stmt_obs lastobs optval ';
|
||||||
|
put ' tmpds1 tmpds2 tmpds3 tmpds4; ';
|
||||||
put '%let numcols=0; ';
|
put '%let numcols=0; ';
|
||||||
|
put '%if &maxobs ne MAX %then %let stmt_obs=%str(if _n_>&maxobs then stop;); ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put '%if &action=OPEN %then %do; ';
|
put '%if &action=OPEN %then %do; ';
|
||||||
put ' options nobomfile; ';
|
put ' options nobomfile; ';
|
||||||
put ' data _null_;file &jref encoding=''utf-8'' ; ';
|
put ' data _null_;file &jref encoding=''utf-8'' lrecl=200; ';
|
||||||
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
put ' put ''{"PROCESSED_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''"''; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
put '%else %if (&action=ARR or &action=OBJ) %then %do; ';
|
||||||
|
put ' /* force variable names to always be uppercase in the JSON */ ';
|
||||||
put ' options validvarname=upcase; ';
|
put ' options validvarname=upcase; ';
|
||||||
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
|
put ' /* To avoid issues with _webout on EBI - such as encoding diffs and truncation ';
|
||||||
|
put ' (https://support.sas.com/kb/49/325.html) we use temporary files */ ';
|
||||||
|
put ' filename _sjs1 temp lrecl=200 ; ';
|
||||||
|
put ' data _null_; file _sjs1 encoding=''utf-8''; ';
|
||||||
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
|
put ' put ", ""%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":"; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' /* now write to _webout 1 char at a time */ ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' infile _sjs1 lrecl=1 recfm=n; ';
|
||||||
|
put ' file &jref mod lrecl=1 recfm=n; ';
|
||||||
|
put ' input sourcechar $char1. @@; ';
|
||||||
|
put ' format sourcechar hex2.; ';
|
||||||
|
put ' put sourcechar char1. @@; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' filename _sjs1 clear; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' /* grab col defs */ ';
|
put ' /* grab col defs */ ';
|
||||||
put ' proc contents noprint data=&ds ';
|
put ' proc contents noprint data=&ds ';
|
||||||
@@ -121,7 +138,7 @@ data _null_;
|
|||||||
put ' by varnum; ';
|
put ' by varnum; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' /* move meta to mac vars */ ';
|
put ' /* move meta to mac vars */ ';
|
||||||
put ' data _null_; ';
|
put ' data &colinfo; ';
|
||||||
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
|
put ' if _n_=1 then call symputx(''numcols'',nobs,''l''); ';
|
||||||
put ' set &colinfo end=last nobs=nobs; ';
|
put ' set &colinfo end=last nobs=nobs; ';
|
||||||
put ' name=upcase(name); ';
|
put ' name=upcase(name); ';
|
||||||
@@ -132,7 +149,6 @@ data _null_;
|
|||||||
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
|
put ' if format='''' then fmt=cats(''$'',length,''.''); ';
|
||||||
put ' else if formatl=0 then fmt=cats(format,''.''); ';
|
put ' else if formatl=0 then fmt=cats(format,''.''); ';
|
||||||
put ' else fmt=cats(format,formatl,''.''); ';
|
put ' else fmt=cats(format,formatl,''.''); ';
|
||||||
put ' newlen=max(formatl,length); ';
|
|
||||||
put ' end; ';
|
put ' end; ';
|
||||||
put ' else do; ';
|
put ' else do; ';
|
||||||
put ' typelong=''num''; ';
|
put ' typelong=''num''; ';
|
||||||
@@ -140,23 +156,26 @@ data _null_;
|
|||||||
put ' else if formatl=0 then fmt=cats(format,''.''); ';
|
put ' else if formatl=0 then fmt=cats(format,''.''); ';
|
||||||
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
|
put ' else if formatd=0 then fmt=cats(format,formatl,''.''); ';
|
||||||
put ' else fmt=cats(format,formatl,''.'',formatd); ';
|
put ' else fmt=cats(format,formatl,''.'',formatd); ';
|
||||||
put ' /* needs to be wide, for datetimes etc */ ';
|
|
||||||
put ' newlen=max(length,formatl,24); ';
|
|
||||||
put ' end; ';
|
put ' end; ';
|
||||||
put ' /* 32 char unique name */ ';
|
put ' /* 32 char unique name */ ';
|
||||||
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
|
put ' newname=''sasjs''!!substr(cats(put(md5(name),$hex32.)),1,27); ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' call symputx(cats(''name'',_n_),name,''l''); ';
|
put ' call symputx(cats(''name'',_n_),name,''l''); ';
|
||||||
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
|
put ' call symputx(cats(''newname'',_n_),newname,''l''); ';
|
||||||
put ' call symputx(cats(''len'',_n_),newlen,''l''); ';
|
|
||||||
put ' call symputx(cats(''length'',_n_),length,''l''); ';
|
put ' call symputx(cats(''length'',_n_),length,''l''); ';
|
||||||
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
|
put ' call symputx(cats(''fmt'',_n_),fmt,''l''); ';
|
||||||
put ' call symputx(cats(''type'',_n_),type,''l''); ';
|
put ' call symputx(cats(''type'',_n_),type,''l''); ';
|
||||||
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
|
put ' call symputx(cats(''typelong'',_n_),typelong,''l''); ';
|
||||||
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
|
put ' call symputx(cats(''label'',_n_),coalescec(label,name),''l''); ';
|
||||||
|
put ' /* overwritten when fmt=Y and a custom format exists in catalog */ ';
|
||||||
|
put ' if typelong=''num'' then call symputx(cats(''fmtlen'',_n_),200,''l''); ';
|
||||||
|
put ' else call symputx(cats(''fmtlen'',_n_),min(32767,ceil((length+10)*1.5)),''l''); ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
put ' %let tempds=%substr(_%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
||||||
|
put ' proc sql; ';
|
||||||
|
put ' select count(*) into: lastobs from &ds; ';
|
||||||
|
put ' %if &maxobs ne MAX %then %let lastobs=%sysfunc(min(&lastobs,&maxobs)); ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %if &engine=PROCJSON %then %do; ';
|
put ' %if &engine=PROCJSON %then %do; ';
|
||||||
put ' %if &missing=STRING %then %do; ';
|
put ' %if &missing=STRING %then %do; ';
|
||||||
@@ -164,13 +183,25 @@ data _null_;
|
|||||||
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
|
put ' %put &sysmacroname: Switching to DATASTEP engine; ';
|
||||||
put ' %goto datastep; ';
|
put ' %goto datastep; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' data &tempds;set &ds; ';
|
put ' data &tempds; ';
|
||||||
|
put ' set &ds; ';
|
||||||
|
put ' &stmt_obs; ';
|
||||||
put ' %if &fmt=N %then format _numeric_ best32.;; ';
|
put ' %if &fmt=N %then format _numeric_ best32.;; ';
|
||||||
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
|
put ' /* PRETTY is necessary to avoid line truncation in large files */ ';
|
||||||
put ' proc json out=&jref pretty ';
|
put ' filename _sjs2 temp lrecl=131068 encoding=''utf-8''; ';
|
||||||
|
put ' proc json out=_sjs2 pretty ';
|
||||||
put ' %if &action=ARR %then nokeys ; ';
|
put ' %if &action=ARR %then nokeys ; ';
|
||||||
put ' ;export &tempds / nosastags fmtnumeric; ';
|
put ' ;export &tempds / nosastags fmtnumeric; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
|
put ' /* send back to webout */ ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' infile _sjs2 lrecl=1 recfm=n; ';
|
||||||
|
put ' file &jref mod lrecl=1 recfm=n; ';
|
||||||
|
put ' input sourcechar $char1. @@; ';
|
||||||
|
put ' format sourcechar hex2.; ';
|
||||||
|
put ' put sourcechar char1. @@; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' filename _sjs2 clear; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %else %if &engine=DATASTEP %then %do; ';
|
put ' %else %if &engine=DATASTEP %then %do; ';
|
||||||
put ' %datastep: ';
|
put ' %datastep: ';
|
||||||
@@ -181,26 +212,99 @@ data _null_;
|
|||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %if &fmt=Y %then %do; ';
|
put ' %if &fmt=Y %then %do; ';
|
||||||
put ' data _data_; ';
|
put ' /** ';
|
||||||
|
put ' * Extract format definitions ';
|
||||||
|
put ' * First, by getting library locations from dictionary.formats ';
|
||||||
|
put ' * Then, by exporting the width using proc format ';
|
||||||
|
put ' * Cannot use maxw from sashelp.vformat as not always populated ';
|
||||||
|
put ' * Cannot use fmtinfo() as not supported in all flavours ';
|
||||||
|
put ' */ ';
|
||||||
|
put ' %let tmpds1=%substr(fmtsum%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
||||||
|
put ' %let tmpds2=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
||||||
|
put ' %let tmpds3=%substr(cntl%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
||||||
|
put ' %let tmpds4=%substr(col%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32); ';
|
||||||
|
put ' proc sql noprint; ';
|
||||||
|
put ' create table &tmpds1 as ';
|
||||||
|
put ' select cats(libname,''.'',memname) as FMTCAT, ';
|
||||||
|
put ' FMTNAME ';
|
||||||
|
put ' from dictionary.formats ';
|
||||||
|
put ' where fmttype=''F'' and libname is not null ';
|
||||||
|
put ' and fmtname in (select format from &colinfo where format is not null) ';
|
||||||
|
put ' order by 1; ';
|
||||||
|
put ' create table &tmpds2( ';
|
||||||
|
put ' FMTNAME char(32), ';
|
||||||
|
put ' LENGTH num ';
|
||||||
|
put ' ); ';
|
||||||
|
put ' %local catlist cat fmtlist i; ';
|
||||||
|
put ' select distinct fmtcat into: catlist separated by '' '' from &tmpds1; ';
|
||||||
|
put ' %do i=1 %to %sysfunc(countw(&catlist,%str( ))); ';
|
||||||
|
put ' %let cat=%scan(&catlist,&i,%str( )); ';
|
||||||
|
put ' proc sql; ';
|
||||||
|
put ' select distinct fmtname into: fmtlist separated by '' '' ';
|
||||||
|
put ' from &tmpds1 where fmtcat="&cat"; ';
|
||||||
|
put ' proc format lib=&cat cntlout=&tmpds3(keep=fmtname length); ';
|
||||||
|
put ' select &fmtlist; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' proc sql; ';
|
||||||
|
put ' insert into &tmpds2 select distinct fmtname,length from &tmpds3; ';
|
||||||
|
put ' %end; ';
|
||||||
|
put ' ';
|
||||||
|
put ' proc sql; ';
|
||||||
|
put ' create table &tmpds4 as ';
|
||||||
|
put ' select a.*, b.length as MAXW ';
|
||||||
|
put ' from &colinfo a ';
|
||||||
|
put ' left join &tmpds2 b ';
|
||||||
|
put ' on cats(a.format)=cats(upcase(b.fmtname)) ';
|
||||||
|
put ' order by a.varnum; ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' set &tmpds4; ';
|
||||||
|
put ' if not missing(maxw); ';
|
||||||
|
put ' call symputx( ';
|
||||||
|
put ' cats(''fmtlen'',_n_), ';
|
||||||
|
put ' /* vars need extra padding due to JSON escaping of special chars */ ';
|
||||||
|
put ' min(32767,ceil((max(length,maxw)+10)*1.5)) ';
|
||||||
|
put ' ,''l'' ';
|
||||||
|
put ' ); ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' ';
|
||||||
|
put ' /* configure varlenchk - as we are explicitly shortening the variables */ ';
|
||||||
|
put ' %let optval=%sysfunc(getoption(varlenchk)); ';
|
||||||
|
put ' options varlenchk=NOWARN; ';
|
||||||
|
put ' data _data_(compress=char); ';
|
||||||
|
put ' /* shorten the new vars */ ';
|
||||||
|
put ' length ';
|
||||||
|
put ' %do i=1 %to &numcols; ';
|
||||||
|
put ' &&name&i $&&fmtlen&i ';
|
||||||
|
put ' %end; ';
|
||||||
|
put ' ; ';
|
||||||
put ' /* rename on entry */ ';
|
put ' /* rename on entry */ ';
|
||||||
put ' set &ds(rename=( ';
|
put ' set &ds(rename=( ';
|
||||||
put ' %do i=1 %to &numcols; ';
|
put ' %do i=1 %to &numcols; ';
|
||||||
put ' &&name&i=&&newname&i ';
|
put ' &&name&i=&&newname&i ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' )); ';
|
put ' )); ';
|
||||||
|
put ' &stmt_obs; ';
|
||||||
|
put ' ';
|
||||||
|
put ' drop ';
|
||||||
|
put ' %do i=1 %to &numcols; ';
|
||||||
|
put ' &&newname&i ';
|
||||||
|
put ' %end; ';
|
||||||
|
put ' ; ';
|
||||||
put ' %do i=1 %to &numcols; ';
|
put ' %do i=1 %to &numcols; ';
|
||||||
put ' length &&name&i $&&len&i; ';
|
|
||||||
put ' %if &&typelong&i=num %then %do; ';
|
put ' %if &&typelong&i=num %then %do; ';
|
||||||
put ' &&name&i=left(put(&&newname&i,&&fmt&i)); ';
|
put ' &&name&i=cats(put(&&newname&i,&&fmt&i)); ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %else %do; ';
|
put ' %else %do; ';
|
||||||
put ' &&name&i=put(&&newname&i,&&fmt&i); ';
|
put ' &&name&i=put(&&newname&i,&&fmt&i); ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' drop &&newname&i; ';
|
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' if _error_ then call symputx(''syscc'',1012); ';
|
put ' if _error_ then do; ';
|
||||||
|
put ' call symputx(''syscc'',1012); ';
|
||||||
|
put ' stop; ';
|
||||||
|
put ' end; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %let fmtds=&syslast; ';
|
put ' %let fmtds=&syslast; ';
|
||||||
|
put ' options varlenchk=&optval; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' proc format; /* credit yabwon for special null removal */ ';
|
put ' proc format; /* credit yabwon for special null removal */ ';
|
||||||
@@ -219,8 +323,8 @@ data _null_;
|
|||||||
put ' attrib _all_ label=''''; ';
|
put ' attrib _all_ label=''''; ';
|
||||||
put ' %do i=1 %to &numcols; ';
|
put ' %do i=1 %to &numcols; ';
|
||||||
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
|
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
|
||||||
put ' length &&name&i $32767; ';
|
put ' length &&name&i $&&fmtlen&i...; ';
|
||||||
put ' format &&name&i $32767.; ';
|
put ' format &&name&i $&&fmtlen&i...; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %if &fmt=Y %then %do; ';
|
put ' %if &fmt=Y %then %do; ';
|
||||||
@@ -229,23 +333,35 @@ data _null_;
|
|||||||
put ' %else %do; ';
|
put ' %else %do; ';
|
||||||
put ' set &ds; ';
|
put ' set &ds; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
|
put ' &stmt_obs; ';
|
||||||
put ' format _numeric_ bart.; ';
|
put ' format _numeric_ bart.; ';
|
||||||
put ' %do i=1 %to &numcols; ';
|
put ' %do i=1 %to &numcols; ';
|
||||||
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
|
put ' %if &&typelong&i=char or &fmt=Y %then %do; ';
|
||||||
put ' &&name&i=''"''!!trim(prxchange(''s/"/\"/'',-1, ';
|
put ' if findc(&&name&i,''"\''!!''0A0D09000E0F010210111A''x) then do; ';
|
||||||
put ' prxchange(''s/''!!''0A''x!!''/\n/'',-1, ';
|
put ' &&name&i=''"''!!trim( ';
|
||||||
put ' prxchange(''s/''!!''0D''x!!''/\r/'',-1, ';
|
put ' prxchange(''s/"/\\"/'',-1, /* double quote */ ';
|
||||||
put ' prxchange(''s/''!!''09''x!!''/\t/'',-1, ';
|
put ' prxchange(''s/\x0A/\n/'',-1, /* new line */ ';
|
||||||
|
put ' prxchange(''s/\x0D/\r/'',-1, /* carriage return */ ';
|
||||||
|
put ' prxchange(''s/\x09/\\t/'',-1, /* tab */ ';
|
||||||
|
put ' prxchange(''s/\x00/\\u0000/'',-1, /* NUL */ ';
|
||||||
|
put ' prxchange(''s/\x0E/\\u000E/'',-1, /* SS */ ';
|
||||||
|
put ' prxchange(''s/\x0F/\\u000F/'',-1, /* SF */ ';
|
||||||
|
put ' prxchange(''s/\x01/\\u0001/'',-1, /* SOH */ ';
|
||||||
|
put ' prxchange(''s/\x02/\\u0002/'',-1, /* STX */ ';
|
||||||
|
put ' prxchange(''s/\x10/\\u0010/'',-1, /* DLE */ ';
|
||||||
|
put ' prxchange(''s/\x11/\\u0011/'',-1, /* DC1 */ ';
|
||||||
|
put ' prxchange(''s/\x1A/\\u001A/'',-1, /* SUB */ ';
|
||||||
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
|
put ' prxchange(''s/\\/\\\\/'',-1,&&name&i) ';
|
||||||
put ' )))))!!''"''; ';
|
put ' )))))))))))))!!''"''; ';
|
||||||
|
put ' end; ';
|
||||||
|
put ' else &&name&i=quote(cats(&&name&i)); ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' /* write to temp loc to avoid _webout truncation ';
|
put ' filename _sjs3 temp lrecl=131068 ; ';
|
||||||
put ' - https://support.sas.com/kb/49/325.html */ ';
|
put ' data _null_; ';
|
||||||
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
|
put ' file _sjs3 encoding=''utf-8''; ';
|
||||||
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod ; ';
|
|
||||||
put ' if _n_=1 then put "["; ';
|
put ' if _n_=1 then put "["; ';
|
||||||
put ' set &tempds; ';
|
put ' set &tempds; ';
|
||||||
put ' if _n_>1 then put "," @; put ';
|
put ' if _n_>1 then put "," @; put ';
|
||||||
@@ -253,39 +369,38 @@ data _null_;
|
|||||||
put ' %do i=1 %to &numcols; ';
|
put ' %do i=1 %to &numcols; ';
|
||||||
put ' %if &i>1 %then "," ; ';
|
put ' %if &i>1 %then "," ; ';
|
||||||
put ' %if &action=OBJ %then """&&name&i"":" ; ';
|
put ' %if &action=OBJ %then """&&name&i"":" ; ';
|
||||||
put ' &&name&i ';
|
put ' "&&name&i"n /* name literal for reserved variable names */ ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
|
put ' %if &action=ARR %then "]" ; %else "}" ; ; ';
|
||||||
put ' /* now write the long strings to _webout 1 byte at a time */ ';
|
put ' ';
|
||||||
put ' data _null_; ';
|
|
||||||
put ' length filein 8 fileid 8; ';
|
|
||||||
put ' filein=fopen("_sjs",''I'',1,''B''); ';
|
|
||||||
put ' fileid=fopen("&jref",''A'',1,''B''); ';
|
|
||||||
put ' rec=''20''x; ';
|
|
||||||
put ' do while(fread(filein)=0); ';
|
|
||||||
put ' rc=fget(filein,rec,1); ';
|
|
||||||
put ' rc=fput(fileid, rec); ';
|
|
||||||
put ' rc=fwrite(fileid); ';
|
|
||||||
put ' end; ';
|
|
||||||
put ' /* close out the table */ ';
|
put ' /* close out the table */ ';
|
||||||
put ' rc=fput(fileid, "]"); ';
|
put ' data _null_; ';
|
||||||
put ' rc=fwrite(fileid); ';
|
put ' file _sjs3 mod encoding=''utf-8''; ';
|
||||||
put ' rc=fclose(filein); ';
|
put ' put '']''; ';
|
||||||
put ' rc=fclose(fileid); ';
|
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' filename _sjs clear; ';
|
put ' data _null_; ';
|
||||||
|
put ' infile _sjs3 lrecl=1 recfm=n; ';
|
||||||
|
put ' file &jref mod lrecl=1 recfm=n; ';
|
||||||
|
put ' input sourcechar $char1. @@; ';
|
||||||
|
put ' format sourcechar hex2.; ';
|
||||||
|
put ' put sourcechar char1. @@; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' filename _sjs3 clear; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' proc sql; ';
|
put ' proc sql; ';
|
||||||
put ' drop table &colinfo, &tempds; ';
|
put ' drop table &colinfo, &tempds; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put ' %if &showmeta=YES %then %do; ';
|
put ' %if %substr(&showmeta,1,1)=Y %then %do; ';
|
||||||
put ' data _null_; file &jref encoding=''utf-8'' mod; ';
|
put ' filename _sjs4 temp lrecl=131068 encoding=''utf-8''; ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' file _sjs4; ';
|
||||||
|
put ' length label $350; ';
|
||||||
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
|
put ' put ", ""$%lowcase(%sysfunc(coalescec(&dslabel,&ds)))"":{""vars"":{"; ';
|
||||||
put ' do i=1 to &numcols; ';
|
put ' do i=1 to &numcols; ';
|
||||||
put ' name=quote(trim(symget(cats(''name'',i)))); ';
|
put ' name=quote(trim(symget(cats(''name'',i)))); ';
|
||||||
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
|
put ' format=quote(trim(symget(cats(''fmt'',i)))); ';
|
||||||
put ' label=quote(trim(symget(cats(''label'',i)))); ';
|
put ' label=quote(prxchange(''s/\\/\\\\/'',-1,trim(symget(cats(''label'',i))))); ';
|
||||||
put ' length=quote(trim(symget(cats(''length'',i)))); ';
|
put ' length=quote(trim(symget(cats(''length'',i)))); ';
|
||||||
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
|
put ' type=quote(trim(symget(cats(''typelong'',i)))); ';
|
||||||
put ' if i>1 then put "," @@; ';
|
put ' if i>1 then put "," @@; ';
|
||||||
@@ -294,6 +409,15 @@ data _null_;
|
|||||||
put ' end; ';
|
put ' end; ';
|
||||||
put ' put ''}}''; ';
|
put ' put ''}}''; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
|
put ' /* send back to webout */ ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' infile _sjs4 lrecl=1 recfm=n; ';
|
||||||
|
put ' file &jref mod lrecl=1 recfm=n; ';
|
||||||
|
put ' input sourcechar $char1. @@; ';
|
||||||
|
put ' format sourcechar hex2.; ';
|
||||||
|
put ' put sourcechar char1. @@; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' filename _sjs4 clear; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
@@ -303,8 +427,28 @@ data _null_;
|
|||||||
put ' run; ';
|
put ' run; ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put '%mend mp_jsonout; ';
|
put '%mend mp_jsonout; ';
|
||||||
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL ';
|
put ' ';
|
||||||
put ' ,showmeta=NO ';
|
put '%macro mf_getuser( ';
|
||||||
|
put ')/*/STORE SOURCE*/; ';
|
||||||
|
put ' %local user; ';
|
||||||
|
put ' ';
|
||||||
|
put ' %if %symexist(_sasjs_username) %then %let user=&_sasjs_username; ';
|
||||||
|
put ' %else %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %do; ';
|
||||||
|
put ' %let user=&SYS_COMPUTE_SESSION_OWNER; ';
|
||||||
|
put ' %end; ';
|
||||||
|
put ' %else %if %symexist(_metaperson) %then %do; ';
|
||||||
|
put ' %if %length(&_metaperson)=0 %then %let user=&sysuserid; ';
|
||||||
|
put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
|
||||||
|
put ' /* but be sure to quote in case of usernames with commas */ ';
|
||||||
|
put ' %else %let user=%unquote(%scan(%quote(&_metaperson),1,@)); ';
|
||||||
|
put ' %end; ';
|
||||||
|
put ' %else %let user=&sysuserid; ';
|
||||||
|
put ' ';
|
||||||
|
put ' %quote(&user) ';
|
||||||
|
put ' ';
|
||||||
|
put '%mend mf_getuser; ';
|
||||||
|
put '%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL ';
|
||||||
|
put ' ,showmeta=N,maxobs=MAX,workobs=0 ';
|
||||||
put '); ';
|
put '); ';
|
||||||
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
|
put '%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug ';
|
||||||
put ' sasjs_tables; ';
|
put ' sasjs_tables; ';
|
||||||
@@ -371,13 +515,14 @@ data _null_;
|
|||||||
put ' ';
|
put ' ';
|
||||||
put '%else %if &action=ARR or &action=OBJ %then %do; ';
|
put '%else %if &action=ARR or &action=OBJ %then %do; ';
|
||||||
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
|
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref ';
|
||||||
put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta ';
|
put ' ,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs ';
|
||||||
put ' ) ';
|
put ' ) ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put '%else %if &action=CLOSE %then %do; ';
|
put '%else %if &action=CLOSE %then %do; ';
|
||||||
put ' %if %str(&_debug) ge 131 %then %do; ';
|
put ' /* To avoid issues with _webout on EBI we use a temporary file */ ';
|
||||||
put ' /* if debug mode, send back first 10 records of each work table also */ ';
|
put ' filename _sjsref temp lrecl=131068; ';
|
||||||
put ' options obs=10; ';
|
put ' %if %str(&workobs) > 0 %then %do; ';
|
||||||
|
put ' /* if debug mode, send back first XX records of each work table also */ ';
|
||||||
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
|
put ' data;run;%let tempds=%scan(&syslast,2,.); ';
|
||||||
put ' ods output Members=&tempds; ';
|
put ' ods output Members=&tempds; ';
|
||||||
put ' proc datasets library=WORK memtype=data; ';
|
put ' proc datasets library=WORK memtype=data; ';
|
||||||
@@ -388,11 +533,11 @@ data _null_;
|
|||||||
put ' i+1; ';
|
put ' i+1; ';
|
||||||
put ' call symputx(cats(''wt'',i),name,''l''); ';
|
put ' call symputx(cats(''wt'',i),name,''l''); ';
|
||||||
put ' call symputx(''wtcnt'',i,''l''); ';
|
put ' call symputx(''wtcnt'',i,''l''); ';
|
||||||
put ' data _null_; file &fref mod encoding=''utf-8''; ';
|
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
|
||||||
put ' put ",""WORK"":{"; ';
|
put ' put ",""WORK"":{"; ';
|
||||||
put ' %do i=1 %to &wtcnt; ';
|
put ' %do i=1 %to &wtcnt; ';
|
||||||
put ' %let wt=&&wt&i; ';
|
put ' %let wt=&&wt&i; ';
|
||||||
put ' data _null_; file &fref mod encoding=''utf-8''; ';
|
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
|
||||||
put ' dsid=open("WORK.&wt",''is''); ';
|
put ' dsid=open("WORK.&wt",''is''); ';
|
||||||
put ' nlobs=attrn(dsid,''NLOBS''); ';
|
put ' nlobs=attrn(dsid,''NLOBS''); ';
|
||||||
put ' nvars=attrn(dsid,''NVARS''); ';
|
put ' nvars=attrn(dsid,''NVARS''); ';
|
||||||
@@ -401,34 +546,46 @@ data _null_;
|
|||||||
put ' put " ""&wt"" : {"; ';
|
put ' put " ""&wt"" : {"; ';
|
||||||
put ' put ''"nlobs":'' nlobs; ';
|
put ' put ''"nlobs":'' nlobs; ';
|
||||||
put ' put '',"nvars":'' nvars; ';
|
put ' put '',"nvars":'' nvars; ';
|
||||||
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES) ';
|
put ' %mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10 ';
|
||||||
put ' data _null_; file &fref mod encoding=''utf-8''; ';
|
put ' ,maxobs=&workobs ';
|
||||||
|
put ' ) ';
|
||||||
|
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
|
||||||
put ' put "}"; ';
|
put ' put "}"; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' data _null_; file &fref mod encoding=''utf-8''; ';
|
put ' data _null_; file _sjsref mod encoding=''utf-8''; ';
|
||||||
put ' put "}"; ';
|
put ' put "}"; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' /* close off json */ ';
|
put ' /* close off json */ ';
|
||||||
put ' data _null_;file &fref mod encoding=''utf-8''; ';
|
put ' data _null_;file _sjsref mod encoding=''utf-8''; ';
|
||||||
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
|
put ' length SYSPROCESSNAME syserrortext syswarningtext autoexec $512; ';
|
||||||
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
|
|
||||||
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
|
|
||||||
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
|
put ' put ",""_DEBUG"" : ""&_debug"" "; ';
|
||||||
put ' _METAUSER=quote(trim(symget(''_METAUSER''))); ';
|
put ' _METAUSER=quote(trim(symget(''_METAUSER''))); ';
|
||||||
put ' put ",""_METAUSER"": " _METAUSER; ';
|
put ' put ",""_METAUSER"": " _METAUSER; ';
|
||||||
put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); ';
|
put ' _METAPERSON=quote(trim(symget(''_METAPERSON''))); ';
|
||||||
put ' put '',"_METAPERSON": '' _METAPERSON; ';
|
put ' put '',"_METAPERSON": '' _METAPERSON; ';
|
||||||
|
put ' _PROGRAM=quote(trim(resolve(symget(''_PROGRAM'')))); ';
|
||||||
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
|
put ' put '',"_PROGRAM" : '' _PROGRAM ; ';
|
||||||
|
put ' autoexec=quote(urlencode(trim(getoption(''autoexec'')))); ';
|
||||||
|
put ' put '',"AUTOEXEC" : '' autoexec; ';
|
||||||
|
put ' put ",""MF_GETUSER"" : ""%mf_getuser()"" "; ';
|
||||||
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
put ' put ",""SYSCC"" : ""&syscc"" "; ';
|
||||||
put ' put ",""SYSERRORTEXT"" : ""&syserrortext"" "; ';
|
put ' put ",""SYSENCODING"" : ""&sysencoding"" "; ';
|
||||||
|
put ' syserrortext=cats(''"'',tranwrd(symget(''syserrortext''),''"'',''\"''),''"''); ';
|
||||||
|
put ' put '',"SYSERRORTEXT" : '' syserrortext; ';
|
||||||
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
put ' put ",""SYSHOSTNAME"" : ""&syshostname"" "; ';
|
||||||
|
put ' put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" "; ';
|
||||||
|
put ' put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" "; ';
|
||||||
|
put ' SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME))); ';
|
||||||
|
put ' put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME; ';
|
||||||
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
put ' put ",""SYSJOBID"" : ""&sysjobid"" "; ';
|
||||||
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
put ' put ",""SYSSCPL"" : ""&sysscpl"" "; ';
|
||||||
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
put ' put ",""SYSSITE"" : ""&syssite"" "; ';
|
||||||
|
put ' put ",""SYSUSERID"" : ""&sysuserid"" "; ';
|
||||||
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
put ' sysvlong=quote(trim(symget(''sysvlong''))); ';
|
||||||
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
put ' put '',"SYSVLONG" : '' sysvlong; ';
|
||||||
put ' put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; ';
|
put ' syswarningtext=cats(''"'',tranwrd(symget(''syswarningtext''),''"'',''\"''),''"''); ';
|
||||||
|
put ' put '',"SYSWARNINGTEXT" : '' syswarningtext; ';
|
||||||
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
|
put ' put '',"END_DTTM" : "'' "%sysfunc(datetime(),E8601DT26.6)" ''" ''; ';
|
||||||
put ' length memsize $32; ';
|
put ' length memsize $32; ';
|
||||||
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
|
put ' memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)"; ';
|
||||||
@@ -439,27 +596,19 @@ data _null_;
|
|||||||
put ' put ''>>weboutEND<<''; ';
|
put ' put ''>>weboutEND<<''; ';
|
||||||
put ' %end; ';
|
put ' %end; ';
|
||||||
put ' run; ';
|
put ' run; ';
|
||||||
|
put ' /* now write to _webout 1 char at a time */ ';
|
||||||
|
put ' data _null_; ';
|
||||||
|
put ' infile _sjsref lrecl=1 recfm=n; ';
|
||||||
|
put ' file &fref mod lrecl=1 recfm=n; ';
|
||||||
|
put ' input sourcechar $char1. @@; ';
|
||||||
|
put ' format sourcechar hex2.; ';
|
||||||
|
put ' put sourcechar char1. @@; ';
|
||||||
|
put ' run; ';
|
||||||
|
put ' filename _sjsref clear; ';
|
||||||
|
put ' ';
|
||||||
put '%end; ';
|
put '%end; ';
|
||||||
put ' ';
|
put ' ';
|
||||||
put '%mend mm_webout; ';
|
put '%mend mm_webout; ';
|
||||||
put ' ';
|
|
||||||
put '%macro mf_getuser(type=META ';
|
|
||||||
put ')/*/STORE SOURCE*/; ';
|
|
||||||
put ' %local user metavar; ';
|
|
||||||
put ' %if &type=OS %then %let metavar=_secureusername; ';
|
|
||||||
put ' %else %let metavar=_metaperson; ';
|
|
||||||
put ' ';
|
|
||||||
put ' %if %symexist(SYS_COMPUTE_SESSION_OWNER) %then %let user=&SYS_COMPUTE_SESSION_OWNER; ';
|
|
||||||
put ' %else %if %symexist(&metavar) %then %do; ';
|
|
||||||
put ' %if %length(&&&metavar)=0 %then %let user=&sysuserid; ';
|
|
||||||
put ' /* sometimes SAS will add @domain extension - remove for consistency */ ';
|
|
||||||
put ' %else %let user=%scan(&&&metavar,1,@); ';
|
|
||||||
put ' %end; ';
|
|
||||||
put ' %else %let user=&sysuserid; ';
|
|
||||||
put ' ';
|
|
||||||
put ' %quote(&user) ';
|
|
||||||
put ' ';
|
|
||||||
put '%mend mf_getuser; ';
|
|
||||||
/* WEBOUT END */
|
/* WEBOUT END */
|
||||||
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
|
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);';
|
||||||
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
|
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
|
||||||
@@ -478,7 +627,7 @@ run;
|
|||||||
%if &x>1 %then %let mod=mod;
|
%if &x>1 %then %let mod=mod;
|
||||||
|
|
||||||
%let fref=%scan(&freflist,&x);
|
%let fref=%scan(&freflist,&x);
|
||||||
%put &sysmacroname: adding &fref;
|
%&mD.put &sysmacroname: adding &fref;
|
||||||
data _null_;
|
data _null_;
|
||||||
file "&work/&tmpfile" lrecl=3000 &mod;
|
file "&work/&tmpfile" lrecl=3000 &mod;
|
||||||
infile &fref;
|
infile &fref;
|
||||||
@@ -514,12 +663,10 @@ data _null_;
|
|||||||
if rc=0 then call symputx('url',url,'l');
|
if rc=0 then call symputx('url',url,'l');
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%put ;%put ;%put ;%put ;%put ;%put ;
|
|
||||||
%put &sysmacroname: STP &name successfully created in &path;
|
%put &sysmacroname: STP &name successfully created in &path;
|
||||||
%put ;%put ;%put ;
|
|
||||||
%put Check it out here:;
|
%put Check it out here:;
|
||||||
%put ;%put ;%put ;
|
%put ;%put ;%put ;
|
||||||
%put &url?_PROGRAM=&path/&name;
|
%put &url?_PROGRAM=&path/&name;
|
||||||
%put ;%put ;%put ;%put ;%put ;%put ;
|
%put ;%put ;%put ;
|
||||||
|
|
||||||
%mend mm_createwebservice;
|
%mend mm_createwebservice;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ filename &fname2 clear;
|
|||||||
%local isgone;
|
%local isgone;
|
||||||
data _null_;
|
data _null_;
|
||||||
length type uri $256;
|
length type uri $256;
|
||||||
|
call missing (of _all_);
|
||||||
rc=metadata_resolve("omsobj:SASLibrary?@Id='&liburi'",type,uri);
|
rc=metadata_resolve("omsobj:SASLibrary?@Id='&liburi'",type,uri);
|
||||||
call symputx('isgone',type,'l');
|
call symputx('isgone',type,'l');
|
||||||
run;
|
run;
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
/**
|
/**
|
||||||
@file mm_getauthinfo.sas
|
@file mm_getauthinfo.sas
|
||||||
@brief extracts authentication info
|
@brief Extracts authentication info for each user in metadata
|
||||||
@details usage:
|
@details
|
||||||
|
Usage:
|
||||||
|
|
||||||
%mm_getauthinfo(outds=auths)
|
%mm_getauthinfo(outds=auths)
|
||||||
|
|
||||||
@param outds= the ONE LEVEL work dataset to create
|
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages and preserve outputs
|
||||||
|
@param [out] outds= (mm_getauthinfo) The output dataset to create
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mm_getobjects.sas
|
|
||||||
@li mf_getuniquefileref.sas
|
@li mf_getuniquefileref.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
@li mm_getdetails.sas
|
@li mm_getdetails.sas
|
||||||
|
@li mm_getobjects.sas
|
||||||
|
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -18,67 +23,69 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mm_getauthinfo(outds=mm_getauthinfo
|
%macro mm_getauthinfo(outds=mm_getauthinfo
|
||||||
|
,mdebug=0
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
%local prefix fileref;
|
||||||
|
%let prefix=%substr(%mf_getuniquename(),1,25);
|
||||||
|
|
||||||
%if %length(&outds)>30 %then %do;
|
%mm_getobjects(type=Login,outds=&prefix.0)
|
||||||
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which
|
|
||||||
therefore needs to be 30 characters or less;
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
%if %index(&outds,'.')>0 %then %do;
|
|
||||||
%put %str(ERR)OR: Table &outds should be ONE LEVEL (no library);
|
|
||||||
%return;
|
|
||||||
%end;
|
|
||||||
|
|
||||||
%mm_getobjects(type=Login,outds=&outds.0)
|
|
||||||
|
|
||||||
%local fileref;
|
%local fileref;
|
||||||
%let fileref=%mf_getuniquefileref();
|
%let fileref=%mf_getuniquefileref();
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
file &fileref;
|
file &fileref;
|
||||||
set &outds.0 end=last;
|
set &prefix.0 end=last;
|
||||||
/* run macro */
|
/* run macro */
|
||||||
str=cats('%mm_getdetails(uri=',id,",outattrs=&outds.d",_n_
|
str=cats('%mm_getdetails(uri=',id,",outattrs=&prefix.d",_n_
|
||||||
,",outassocs=&outds.a",_n_,")");
|
,",outassocs=&prefix.a",_n_,")");
|
||||||
put str;
|
put str;
|
||||||
/* transpose attributes */
|
/* transpose attributes */
|
||||||
str=cats("proc transpose data=&outds.d",_n_,"(drop=type) out=&outds.da"
|
str=cats("proc transpose data=&prefix.d",_n_,"(drop=type) out=&prefix.da"
|
||||||
,_n_,"(drop=_name_);var value;id name;run;");
|
,_n_,"(drop=_name_);var value;id name;run;");
|
||||||
put str;
|
put str;
|
||||||
/* add extra info to attributes */
|
/* add extra info to attributes */
|
||||||
str=cats("data &outds.da",_n_,";length login_id login_name $256; login_id="
|
str=cats("data &prefix.da",_n_,";length login_id login_name $256; login_id="
|
||||||
,quote(trim(id)),";set &outds.da",_n_
|
,quote(trim(id)),";set &prefix.da",_n_
|
||||||
,";login_name=trim(subpad(name,1,256));drop name;run;");
|
,";login_name=trim(subpad(name,1,256));drop name;run;");
|
||||||
put str;
|
put str;
|
||||||
/* add extra info to associations */
|
/* add extra info to associations */
|
||||||
str=cats("data &outds.a",_n_,";length login_id login_name $256; login_id="
|
str=cats("data &prefix.a",_n_,";length login_id login_name $256; login_id="
|
||||||
,quote(trim(id)),";login_name=",quote(trim(name))
|
,quote(trim(id)),";login_name=",quote(trim(name))
|
||||||
,";set &outds.a",_n_,";run;");
|
,";set &prefix.a",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
if last then do;
|
if last then do;
|
||||||
/* collate attributes */
|
/* collate attributes */
|
||||||
str=cats("data &outds._logat; set &outds.da1-&outds.da",_n_,";run;");
|
str=cats("data &prefix._logat; set &prefix.da1-&prefix.da",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
/* collate associations */
|
/* collate associations */
|
||||||
str=cats("data &outds._logas; set &outds.a1-&outds.a",_n_,";run;");
|
str=cats("data &prefix._logas; set &prefix.a1-&prefix.a",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
/* tidy up */
|
/* tidy up */
|
||||||
str=cats("proc delete data=&outds.da1-&outds.da",_n_,";run;");
|
str=cats("proc delete data=&prefix.da1-&prefix.da",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
str=cats("proc delete data=&outds.d1-&outds.d",_n_,";run;");
|
str=cats("proc delete data=&prefix.d1-&prefix.d",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
str=cats("proc delete data=&outds.a1-&outds.a",_n_,";run;");
|
str=cats("proc delete data=&prefix.a1-&prefix.a",_n_,";run;");
|
||||||
put str;
|
put str;
|
||||||
end;
|
end;
|
||||||
run;
|
run;
|
||||||
|
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
|
data _null_;
|
||||||
|
infile &fileref;
|
||||||
|
if _n_=1 then putlog // "Now executing the following code:" //;
|
||||||
|
input; putlog _infile_;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
%inc &fileref;
|
%inc &fileref;
|
||||||
|
filename &fileref clear;
|
||||||
|
|
||||||
/* get libraries */
|
/* get libraries */
|
||||||
proc sort data=&outds._logas(where=(assoc='Libraries')) out=&outds._temp;
|
proc sort data=&prefix._logas(where=(assoc='Libraries')) out=&prefix._temp;
|
||||||
by login_id;
|
by login_id;
|
||||||
data &outds._temp;
|
data &prefix._temp;
|
||||||
set &outds._temp;
|
set &prefix._temp;
|
||||||
by login_id;
|
by login_id;
|
||||||
length library_list $32767;
|
length library_list $32767;
|
||||||
retain library_list;
|
retain library_list;
|
||||||
@@ -86,31 +93,27 @@ data &outds._temp;
|
|||||||
else library_list=catx(' !! ',library_list,name);
|
else library_list=catx(' !! ',library_list,name);
|
||||||
proc sql;
|
proc sql;
|
||||||
/* get auth domain */
|
/* get auth domain */
|
||||||
create table &outds._dom as
|
create table &prefix._dom as
|
||||||
select login_id,name as domain
|
select login_id,name as domain
|
||||||
from &outds._logas
|
from &prefix._logas
|
||||||
where assoc='Domain';
|
where assoc='Domain';
|
||||||
create unique index login_id on &outds._dom(login_id);
|
create unique index login_id on &prefix._dom(login_id);
|
||||||
/* join it all together */
|
/* join it all together */
|
||||||
create table &outds._logins as
|
create table &outds as
|
||||||
select a.*
|
select a.*
|
||||||
,c.domain
|
,c.domain
|
||||||
,b.library_list
|
,b.library_list
|
||||||
from &outds._logat (drop=ishidden lockedby usageversion publictype) a
|
from &prefix._logat (drop=ishidden lockedby usageversion publictype) a
|
||||||
left join &outds._temp b
|
left join &prefix._temp b
|
||||||
on a.login_id=b.login_id
|
on a.login_id=b.login_id
|
||||||
left join &outds._dom c
|
left join &prefix._dom c
|
||||||
on a.login_id=c.login_id;
|
on a.login_id=c.login_id;
|
||||||
drop table &outds._temp;
|
|
||||||
drop table &outds._logat;
|
|
||||||
drop table &outds._logas;
|
|
||||||
|
|
||||||
data _null_;
|
%if &mdebug=0 %then %do;
|
||||||
infile &fileref;
|
proc datasets lib=work;
|
||||||
if _n_=1 then putlog // "Now executing the following code:" //;
|
delete &prefix:;
|
||||||
input; putlog _infile_;
|
|
||||||
run;
|
run;
|
||||||
|
%end;
|
||||||
|
|
||||||
filename &fileref clear;
|
|
||||||
|
|
||||||
%mend mm_getauthinfo;
|
%mend mm_getauthinfo;
|
||||||
@@ -17,8 +17,6 @@
|
|||||||
directories
|
directories
|
||||||
@param [in] mDebug= set to 1 to show debug messages in the log
|
@param [in] mDebug= set to 1 to show debug messages in the log
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
@li mf_getuniquefileref.sas
|
@li mf_getuniquefileref.sas
|
||||||
|
@li mp_abort.sas
|
||||||
|
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ data _null_;
|
|||||||
cnt=1;
|
cnt=1;
|
||||||
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
|
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
|
||||||
rc=metadata_getattr(tsuri,"Name",value);
|
rc=metadata_getattr(tsuri,"Name",value);
|
||||||
put tsuri= value=;
|
&mD.put tsuri= value=;
|
||||||
if value="SourceCode" then do;
|
if value="SourceCode" then do;
|
||||||
/* found it! */
|
/* found it! */
|
||||||
rc=metadata_getattr(tsuri,"Id",value);
|
rc=metadata_getattr(tsuri,"Id",value);
|
||||||
@@ -70,11 +71,10 @@ data _null_;
|
|||||||
else put (_all_)(=);
|
else put (_all_)(=);
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%if &tsuri=stopifempty %then %do;
|
%mp_abort(iftrue= (&tsuri=stopifempty)
|
||||||
%put %str(WARN)ING: &tree&name.(StoredProcess) not found!;
|
,mac=mm_getstpcode
|
||||||
%return;
|
,msg=%str(&tree&name.(StoredProcess) not found!)
|
||||||
%end;
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Now we can extract the textstore
|
* Now we can extract the textstore
|
||||||
|
|||||||
67
meta/mm_getstpinfo.sas
Normal file
67
meta/mm_getstpinfo.sas
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Get the properties of a Stored Process
|
||||||
|
@details Extracts various properties and creates an output table in the
|
||||||
|
structure below:
|
||||||
|
|
||||||
|
|STP_URI:$200.|SERVERCONTEXT:$200.|STOREDPROCESSCONFIGURATION:$1000.|SOURCECODE_FIRST32K:$32767.|PATH:$76.|
|
||||||
|
|---|---|---|---|---|
|
||||||
|
|`A5DN9TDQ.BH0000C8 `|`SASApp `|`<?xml version="1.0" encoding="UTF-8"?><StoredProcess><ServerContext LogicalServerType="Sps" OtherAllowed="false"/><ResultCapabilities Package="false" Streaming="true"/><OutputParameters/></StoredProcess> `|`%put first 32767 bytes of code; `|`/path/to/my/stp`|
|
||||||
|
|
||||||
|
@param [in] pgm The metadata path of the Stored Process
|
||||||
|
@param [out] outds= (work.mm_getstpinfo) The output table to create
|
||||||
|
@param [in] mdebug= (0) Set to 1 to enable DEBUG messages
|
||||||
|
|
||||||
|
<h4> Related Files </h4>
|
||||||
|
@li mm_getstpcode.sas
|
||||||
|
@li mm_getstps.sas
|
||||||
|
@li mm_createstp.sas
|
||||||
|
@li mm_deletestp.sas
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mm_getstpinfo(pgm
|
||||||
|
,outds=work.mm_getstpinfo
|
||||||
|
,mDebug=0
|
||||||
|
);
|
||||||
|
|
||||||
|
%local mD;
|
||||||
|
%if &mDebug=1 %then %let mD=;
|
||||||
|
%else %let mD=%str(*);
|
||||||
|
%&mD.put Executing &sysmacroname..sas;
|
||||||
|
%&mD.put _local_;
|
||||||
|
|
||||||
|
data &outds;
|
||||||
|
length type stp_uri tsuri servercontext value $200
|
||||||
|
StoredProcessConfiguration $1000 sourcecode_first32k $32767;
|
||||||
|
keep path stp_uri sourcecode_first32k StoredProcessConfiguration
|
||||||
|
servercontext;
|
||||||
|
call missing (of _all_);
|
||||||
|
path="&pgm(StoredProcess)";
|
||||||
|
/* first, find the STP ID */
|
||||||
|
if metadata_pathobj("",path,"StoredProcess",type,stp_uri)>0 then do;
|
||||||
|
/* get attributes */
|
||||||
|
cnt=1;
|
||||||
|
do while (metadata_getnasn(stp_uri,"Notes",cnt,tsuri)>0);
|
||||||
|
rc1=metadata_getattr(tsuri,"Name",value);
|
||||||
|
&mD.put tsuri= value=;
|
||||||
|
if value="SourceCode" then do;
|
||||||
|
rc2=metadata_getattr(tsuri,"StoredText",sourcecode_first32k);
|
||||||
|
end;
|
||||||
|
else if value="Stored Process" then do;
|
||||||
|
rc3=metadata_getattr(tsuri,"StoredText",StoredProcessConfiguration);
|
||||||
|
end;
|
||||||
|
cnt+1;
|
||||||
|
end;
|
||||||
|
/* get context (should only be one) */
|
||||||
|
rc4=metadata_getnasn(stp_uri,"ComputeLocations",1,tsuri);
|
||||||
|
rc5=metadata_getattr(tsuri,"Name",servercontext);
|
||||||
|
end;
|
||||||
|
else do;
|
||||||
|
put "%str(ERR)OR: could not find " path;
|
||||||
|
put (_all_)(=);
|
||||||
|
end;
|
||||||
|
&md.put (_all_)(=);
|
||||||
|
run;
|
||||||
|
|
||||||
|
%mend mm_getstpinfo ;
|
||||||
@@ -8,17 +8,18 @@
|
|||||||
|
|
||||||
%mm_getusers()
|
%mm_getusers()
|
||||||
|
|
||||||
|
Optionally, filter for a user (useful to get the uri):
|
||||||
|
|
||||||
|
%mm_getusers(user=&_metaperson)
|
||||||
|
|
||||||
@param outds the dataset to create that contains the list of libraries
|
@param outds the dataset to create that contains the list of libraries
|
||||||
|
|
||||||
@returns outds dataset containing all users, with the following columns:
|
@returns outds dataset containing all users, with the following columns:
|
||||||
- uri
|
- uri
|
||||||
- name
|
- name
|
||||||
|
|
||||||
@warning The following filenames are created and then de-assigned:
|
@param user= (0) Set to a metadata user to filter on that user
|
||||||
|
@param outds= (work.mm_getusers) The output table to create
|
||||||
filename sxlemap clear;
|
|
||||||
filename response clear;
|
|
||||||
libname _XML_ clear;
|
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -26,10 +27,12 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
%macro mm_getusers(
|
%macro mm_getusers(
|
||||||
outds=work.mm_getusers
|
outds=work.mm_getusers,
|
||||||
|
user=0
|
||||||
)/*/STORE SOURCE*/;
|
)/*/STORE SOURCE*/;
|
||||||
|
|
||||||
filename response temp;
|
filename response temp;
|
||||||
|
%if %superq(user)=0 %then %do;
|
||||||
proc metadata in= '<GetMetadataObjects>
|
proc metadata in= '<GetMetadataObjects>
|
||||||
<Reposid>$METAREPOSITORY</Reposid>
|
<Reposid>$METAREPOSITORY</Reposid>
|
||||||
<Type>Person</Type>
|
<Type>Person</Type>
|
||||||
@@ -43,6 +46,30 @@ proc metadata in= '<GetMetadataObjects>
|
|||||||
</GetMetadataObjects>'
|
</GetMetadataObjects>'
|
||||||
out=response;
|
out=response;
|
||||||
run;
|
run;
|
||||||
|
%end;
|
||||||
|
%else %do;
|
||||||
|
filename inref temp;
|
||||||
|
data _null_;
|
||||||
|
file inref;
|
||||||
|
put "<GetMetadataObjects>";
|
||||||
|
put "<Reposid>$METAREPOSITORY</Reposid>";
|
||||||
|
put "<Type>Person</Type>";
|
||||||
|
put "<NS>SAS</NS>";
|
||||||
|
put "<!-- Specify the OMI_XMLSELECT (128) flag -->";
|
||||||
|
put "<Flags>128</Flags>";
|
||||||
|
put "<Options>";
|
||||||
|
put "<Templates>";
|
||||||
|
put '<Person Name=""/>';
|
||||||
|
put "</Templates>";
|
||||||
|
length string $10000;
|
||||||
|
string=cats('<XMLSELECT search="Person[@Name=',"'&user'",']"/>');
|
||||||
|
put string;
|
||||||
|
put "</Options>";
|
||||||
|
put "</GetMetadataObjects>";
|
||||||
|
run;
|
||||||
|
proc metadata in=inref out=response;
|
||||||
|
run;
|
||||||
|
%end;
|
||||||
|
|
||||||
filename sxlemap temp;
|
filename sxlemap temp;
|
||||||
data _null_;
|
data _null_;
|
||||||
|
|||||||
@@ -49,20 +49,25 @@
|
|||||||
|
|
||||||
|
|
||||||
<h4> SAS Macros </h4>
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_getuniquefileref.sas
|
||||||
|
@li mf_getuniquename.sas
|
||||||
|
@li mf_isblank.sas
|
||||||
@li mf_loc.sas
|
@li mf_loc.sas
|
||||||
@li mm_tree.sas
|
@li mm_tree.sas
|
||||||
@li mf_getuniquefileref.sas
|
|
||||||
@li mf_isblank.sas
|
|
||||||
@li mp_abort.sas
|
@li mp_abort.sas
|
||||||
|
|
||||||
@param metaloc= the metadata folder to export
|
|
||||||
@param secureref= fileref containing the username / password (should point to
|
@param [in] metaloc= the metadata folder to export
|
||||||
a file in a secure location). Leave blank to substitute $bash type vars.
|
@param [in] secureref= fileref containing the username / password (should
|
||||||
@param outref= fileref to which to write the command
|
point to a file in a secure location). Leave blank to substitute $bash vars.
|
||||||
@param cmdoutloc= the directory to which the command will write the SPK
|
@param [in] excludevars= (0) A space seperated list of macro variable names,
|
||||||
(default=WORK)
|
each of which contains a value that should be used to filter the output
|
||||||
@param cmdoutname= the name of the spk / log files to create (will be
|
objects.
|
||||||
identical just with .spk or .log extension)
|
@param [out] outref= fileref to which to write the command
|
||||||
|
@param [out] cmdoutloc= (%sysfunc(pathname(work))) The directory to which the
|
||||||
|
command will write the SPK
|
||||||
|
@param [out] cmdoutname= (mmxport) The name of the spk / log files to create
|
||||||
|
(will be identical just with .spk or .log extension)
|
||||||
|
|
||||||
@version 9.4
|
@version 9.4
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
@@ -71,6 +76,7 @@
|
|||||||
|
|
||||||
%macro mm_spkexport(metaloc=
|
%macro mm_spkexport(metaloc=
|
||||||
,secureref=
|
,secureref=
|
||||||
|
,excludevars=0
|
||||||
,outref=
|
,outref=
|
||||||
,cmdoutloc=%sysfunc(pathname(work))
|
,cmdoutloc=%sysfunc(pathname(work))
|
||||||
,cmdoutname=mmxport
|
,cmdoutname=mmxport
|
||||||
@@ -82,7 +88,7 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
/* set creds */
|
/* set creds */
|
||||||
%local mmxuser mmxpath;
|
%local mmxuser mmxpath i var;
|
||||||
%let mmxuser=$1;
|
%let mmxuser=$1;
|
||||||
%let mmxpass=$2;
|
%let mmxpass=$2;
|
||||||
%if %mf_isblank(&secureref)=0 %then %do;
|
%if %mf_isblank(&secureref)=0 %then %do;
|
||||||
@@ -90,34 +96,50 @@
|
|||||||
%end;
|
%end;
|
||||||
|
|
||||||
/* setup metadata connection options */
|
/* setup metadata connection options */
|
||||||
%local host port platform_object_path connx_string;
|
%local host port platform_object_path ds;
|
||||||
%let host=%sysfunc(getoption(metaserver));
|
%let host=%sysfunc(getoption(metaserver));
|
||||||
%let port=%sysfunc(getoption(metaport));
|
%let port=%sysfunc(getoption(metaport));
|
||||||
%let platform_object_path=%mf_loc(POF);
|
%let platform_object_path=%mf_loc(POF);
|
||||||
|
%let ds=%mf_getuniquename(prefix=spkexportable);
|
||||||
|
|
||||||
%let connx_string=%str(-host &host -port &port -user &mmxuser %trim(
|
%mm_tree(root=%str(&metaloc),types=EXPORTABLE ,outds=&ds)
|
||||||
)-password &mmxpass);
|
|
||||||
|
|
||||||
%mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable)
|
|
||||||
|
|
||||||
%if %mf_isblank(&outref)=1 %then %let outref=%mf_getuniquefileref();
|
%if %mf_isblank(&outref)=1 %then %let outref=%mf_getuniquefileref();
|
||||||
|
|
||||||
data _null_;
|
data _null_;
|
||||||
set exportable end=last;
|
set &ds end=last;
|
||||||
file &outref lrecl=32767;
|
file &outref lrecl=32767;
|
||||||
length str $32767;
|
length str $32767;
|
||||||
if _n_=1 then do;
|
if _n_=1 then do;
|
||||||
|
put "# Script generated by &sysuserid on %sysfunc(datetime(),datetime19.)";
|
||||||
put "cd ""&platform_object_path"" \";
|
put "cd ""&platform_object_path"" \";
|
||||||
put "; ./ExportPackage &connx_string -disableX11 \";
|
put "; ./ExportPackage -host &host -port &port -user &mmxuser \";
|
||||||
|
put " -disableX11 -password &mmxpass \";
|
||||||
put " -package ""&cmdoutloc/&cmdoutname..spk"" \";
|
put " -package ""&cmdoutloc/&cmdoutname..spk"" \";
|
||||||
end;
|
end;
|
||||||
|
/* exclude particular patterns from the exported SPK */
|
||||||
|
%if "&excludevars" ne "0" %then %do;
|
||||||
|
%do i=1 %to %sysfunc(countw(&excludevars));
|
||||||
|
%let var=%scan(&excludevars,&i);
|
||||||
|
if _n_=1 then do;
|
||||||
|
length excludestr&i $1000;
|
||||||
|
retain excludestr&i;
|
||||||
|
excludestr&i=symget("&var");
|
||||||
|
putlog excludestr&i=;
|
||||||
|
putlog path=;
|
||||||
|
end;
|
||||||
|
if index(path,cats(excludestr&i))=0 and index(name,cats(excludestr&i))=0;
|
||||||
|
%end;
|
||||||
|
/* ignore top level folder else all subcontent will be exported regardless */
|
||||||
|
if _n_>1;
|
||||||
|
%end;
|
||||||
str=' -objects '!!cats('"',path,'/',name,"(",publictype,')" \');
|
str=' -objects '!!cats('"',path,'/',name,"(",publictype,')" \');
|
||||||
put str;
|
put str;
|
||||||
if last then put " -log ""&cmdoutloc/&cmdoutname..log"" 2>&1 ";
|
if last then put " -log ""&cmdoutloc/&cmdoutname..log"" 2>&1 ";
|
||||||
run;
|
run;
|
||||||
|
|
||||||
%mp_abort(iftrue= (&syscc ne 0)
|
%mp_abort(iftrue= (&syscc ne 0)
|
||||||
,mac=&sysmacroname
|
,mac=mm_spkexport
|
||||||
,msg=%str(syscc=&syscc)
|
,msg=%str(syscc=&syscc)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ data _null_;
|
|||||||
cnt=1;
|
cnt=1;
|
||||||
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
|
do while (metadata_getnasn(uri,"Notes",cnt,tsuri)>0);
|
||||||
rc=metadata_getattr(tsuri,"Name",value);
|
rc=metadata_getattr(tsuri,"Name",value);
|
||||||
|
%if &mdebug=1 %then %do;
|
||||||
put tsuri= value=;
|
put tsuri= value=;
|
||||||
|
%end;
|
||||||
if value="SourceCode" then do;
|
if value="SourceCode" then do;
|
||||||
/* found it! */
|
/* found it! */
|
||||||
rc=metadata_getattr(tsuri,"Id",value);
|
rc=metadata_getattr(tsuri,"Id",value);
|
||||||
|
|||||||
@@ -26,21 +26,33 @@
|
|||||||
@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
|
@param [in] action Either FETCH, OPEN, ARR, OBJ or CLOSE
|
||||||
@param [in] ds The dataset to send back to the frontend
|
@param [in] ds The dataset to send back to the frontend
|
||||||
@param [out] dslabel= Value to use instead of table name for sending to JSON
|
@param [out] dslabel= Value to use instead of table name for sending to JSON
|
||||||
@param [in] fmt=(Y) Set to N to send back unformatted values
|
@param [in] fmt= (N) Setting Y converts all vars to their formatted values
|
||||||
@param [out] fref= (_webout) The fileref to which to write the JSON
|
@param [out] fref= (_webout) The fileref to which to write the JSON
|
||||||
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
@param [in] missing= (NULL) Special numeric missing values can be sent as NULL
|
||||||
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
(eg `null`) or as STRING values (eg `".a"` or `".b"`)
|
||||||
@param [in] showmeta= (NO) Set to YES to output metadata alongside each table,
|
@param [in] showmeta= (N) Set to Y to output metadata alongside each table,
|
||||||
such as the column formats and types. The metadata is contained inside an
|
such as the column formats and types. The metadata is contained inside an
|
||||||
object with the same name as the table but prefixed with a dollar sign - ie,
|
object with the same name as the table but prefixed with a dollar sign - ie,
|
||||||
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
`,"$tablename":{"formats":{"col1":"$CHAR1"},"types":{"COL1":"C"}}`
|
||||||
|
@param [in] workobs= (0) When set to a positive integer, will create a new
|
||||||
|
output object (WORK) which contains this number of observations from all
|
||||||
|
tables in the WORK library.
|
||||||
|
@param [in] maxobs= (MAX) Provide an integer to limit the number of input rows
|
||||||
|
that should be converted to output JSON
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mp_jsonout.sas
|
||||||
|
|
||||||
|
<h4> Related Macros </h4>
|
||||||
|
@li ms_webout.sas
|
||||||
|
@li mv_webout.sas
|
||||||
|
|
||||||
@version 9.3
|
@version 9.3
|
||||||
@author Allan Bowe
|
@author Allan Bowe
|
||||||
|
|
||||||
**/
|
**/
|
||||||
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=Y,missing=NULL
|
%macro mm_webout(action,ds,dslabel=,fref=_webout,fmt=N,missing=NULL
|
||||||
,showmeta=NO
|
,showmeta=N,maxobs=MAX,workobs=0
|
||||||
);
|
);
|
||||||
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
|
%global _webin_file_count _webin_fileref1 _webin_name1 _program _debug
|
||||||
sasjs_tables;
|
sasjs_tables;
|
||||||
@@ -107,13 +119,14 @@
|
|||||||
|
|
||||||
%else %if &action=ARR or &action=OBJ %then %do;
|
%else %if &action=ARR or &action=OBJ %then %do;
|
||||||
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
|
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt,jref=&fref
|
||||||
,engine=&jsonengine,missing=&missing,showmeta=&showmeta
|
,engine=&jsonengine,missing=&missing,showmeta=&showmeta,maxobs=&maxobs
|
||||||
)
|
)
|
||||||
%end;
|
%end;
|
||||||
%else %if &action=CLOSE %then %do;
|
%else %if &action=CLOSE %then %do;
|
||||||
%if %str(&_debug) ge 131 %then %do;
|
/* To avoid issues with _webout on EBI we use a temporary file */
|
||||||
/* if debug mode, send back first 10 records of each work table also */
|
filename _sjsref temp lrecl=131068;
|
||||||
options obs=10;
|
%if %str(&workobs) > 0 %then %do;
|
||||||
|
/* if debug mode, send back first XX records of each work table also */
|
||||||
data;run;%let tempds=%scan(&syslast,2,.);
|
data;run;%let tempds=%scan(&syslast,2,.);
|
||||||
ods output Members=&tempds;
|
ods output Members=&tempds;
|
||||||
proc datasets library=WORK memtype=data;
|
proc datasets library=WORK memtype=data;
|
||||||
@@ -124,11 +137,11 @@
|
|||||||
i+1;
|
i+1;
|
||||||
call symputx(cats('wt',i),name,'l');
|
call symputx(cats('wt',i),name,'l');
|
||||||
call symputx('wtcnt',i,'l');
|
call symputx('wtcnt',i,'l');
|
||||||
data _null_; file &fref mod encoding='utf-8';
|
data _null_; file _sjsref mod encoding='utf-8';
|
||||||
put ",""WORK"":{";
|
put ",""WORK"":{";
|
||||||
%do i=1 %to &wtcnt;
|
%do i=1 %to &wtcnt;
|
||||||
%let wt=&&wt&i;
|
%let wt=&&wt&i;
|
||||||
data _null_; file &fref mod encoding='utf-8';
|
data _null_; file _sjsref mod encoding='utf-8';
|
||||||
dsid=open("WORK.&wt",'is');
|
dsid=open("WORK.&wt",'is');
|
||||||
nlobs=attrn(dsid,'NLOBS');
|
nlobs=attrn(dsid,'NLOBS');
|
||||||
nvars=attrn(dsid,'NVARS');
|
nvars=attrn(dsid,'NVARS');
|
||||||
@@ -137,34 +150,46 @@
|
|||||||
put " ""&wt"" : {";
|
put " ""&wt"" : {";
|
||||||
put '"nlobs":' nlobs;
|
put '"nlobs":' nlobs;
|
||||||
put ',"nvars":' nvars;
|
put ',"nvars":' nvars;
|
||||||
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,showmeta=YES)
|
%mp_jsonout(OBJ,&wt,jref=_sjsref,dslabel=first10rows,showmeta=Y,maxobs=10
|
||||||
data _null_; file &fref mod encoding='utf-8';
|
,maxobs=&workobs
|
||||||
|
)
|
||||||
|
data _null_; file _sjsref mod encoding='utf-8';
|
||||||
put "}";
|
put "}";
|
||||||
%end;
|
%end;
|
||||||
data _null_; file &fref mod encoding='utf-8';
|
data _null_; file _sjsref mod encoding='utf-8';
|
||||||
put "}";
|
put "}";
|
||||||
run;
|
run;
|
||||||
%end;
|
%end;
|
||||||
/* close off json */
|
/* close off json */
|
||||||
data _null_;file &fref mod encoding='utf-8';
|
data _null_;file _sjsref mod encoding='utf-8';
|
||||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
length SYSPROCESSNAME syserrortext syswarningtext autoexec $512;
|
||||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
|
||||||
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
|
|
||||||
put ",""_DEBUG"" : ""&_debug"" ";
|
put ",""_DEBUG"" : ""&_debug"" ";
|
||||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||||
put ",""_METAUSER"": " _METAUSER;
|
put ",""_METAUSER"": " _METAUSER;
|
||||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||||
put ',"_METAPERSON": ' _METAPERSON;
|
put ',"_METAPERSON": ' _METAPERSON;
|
||||||
|
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||||
|
autoexec=quote(urlencode(trim(getoption('autoexec'))));
|
||||||
|
put ',"AUTOEXEC" : ' autoexec;
|
||||||
|
put ",""MF_GETUSER"" : ""%mf_getuser()"" ";
|
||||||
put ",""SYSCC"" : ""&syscc"" ";
|
put ",""SYSCC"" : ""&syscc"" ";
|
||||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
put ",""SYSENCODING"" : ""&sysencoding"" ";
|
||||||
|
syserrortext=cats('"',tranwrd(symget('syserrortext'),'"','\"'),'"');
|
||||||
|
put ',"SYSERRORTEXT" : ' syserrortext;
|
||||||
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
|
||||||
|
put ",""SYSPROCESSID"" : ""&SYSPROCESSID"" ";
|
||||||
|
put ",""SYSPROCESSMODE"" : ""&SYSPROCESSMODE"" ";
|
||||||
|
SYSPROCESSNAME=quote(urlencode(cats(SYSPROCESSNAME)));
|
||||||
|
put ",""SYSPROCESSNAME"" : " SYSPROCESSNAME;
|
||||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||||
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
put ",""SYSSCPL"" : ""&sysscpl"" ";
|
||||||
put ",""SYSSITE"" : ""&syssite"" ";
|
put ",""SYSSITE"" : ""&syssite"" ";
|
||||||
|
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||||
sysvlong=quote(trim(symget('sysvlong')));
|
sysvlong=quote(trim(symget('sysvlong')));
|
||||||
put ',"SYSVLONG" : ' sysvlong;
|
put ',"SYSVLONG" : ' sysvlong;
|
||||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
syswarningtext=cats('"',tranwrd(symget('syswarningtext'),'"','\"'),'"');
|
||||||
|
put ',"SYSWARNINGTEXT" : ' syswarningtext;
|
||||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
put ',"END_DTTM" : "' "%sysfunc(datetime(),E8601DT26.6)" '" ';
|
||||||
length memsize $32;
|
length memsize $32;
|
||||||
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
|
memsize="%sysfunc(INPUTN(%sysfunc(getoption(memsize)), best.),sizekmg.)";
|
||||||
@@ -175,6 +200,16 @@
|
|||||||
put '>>weboutEND<<';
|
put '>>weboutEND<<';
|
||||||
%end;
|
%end;
|
||||||
run;
|
run;
|
||||||
|
/* now write to _webout 1 char at a time */
|
||||||
|
data _null_;
|
||||||
|
infile _sjsref lrecl=1 recfm=n;
|
||||||
|
file &fref mod lrecl=1 recfm=n;
|
||||||
|
input sourcechar $char1. @@;
|
||||||
|
format sourcechar hex2.;
|
||||||
|
put sourcechar char1. @@;
|
||||||
|
run;
|
||||||
|
filename _sjsref clear;
|
||||||
|
|
||||||
%end;
|
%end;
|
||||||
|
|
||||||
%mend mm_webout;
|
%mend mm_webout;
|
||||||
|
|||||||
50
metax/mmx_createmetafolder.sas
Normal file
50
metax/mmx_createmetafolder.sas
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@brief Creates a metadata folder
|
||||||
|
@details Creates a metadata folder using the batch tools
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
%mmx_createmetafolder(loc=/some/meta/folder,user=sasdemo,pass=mars345)
|
||||||
|
|
||||||
|
<h4> SAS Macros </h4>
|
||||||
|
@li mf_loc.sas
|
||||||
|
@li mp_abort.sas
|
||||||
|
|
||||||
|
@param loc= the metadata folder to delete
|
||||||
|
@param user= username
|
||||||
|
@param pass= password
|
||||||
|
|
||||||
|
@version 9.4
|
||||||
|
@author Allan Bowe
|
||||||
|
|
||||||
|
**/
|
||||||
|
|
||||||
|
%macro mmx_createmetafolder(loc=,user=,pass=);
|
||||||
|
|
||||||
|
%local host port path connx_string msg;
|
||||||
|
%let host=%sysfunc(getoption(metaserver));
|
||||||
|
%let port=%sysfunc(getoption(metaport));
|
||||||
|
%let path=%mf_loc(POF)/tools;
|
||||||
|
|
||||||
|
%let connx_string= -host &host -port &port -user '&user' -password '&pass';
|
||||||
|
/* remove directory */
|
||||||
|
data _null_;
|
||||||
|
infile " &path/sas-make-folder &connx_string ""&loc"" -makeFullPath 2>&1"
|
||||||
|
pipe lrecl=10000;
|
||||||
|
input;
|
||||||
|
putlog _infile_;
|
||||||
|
run;
|
||||||
|
|
||||||
|
data _null_; /* check tree exists */
|
||||||
|
length type uri $256;
|
||||||
|
rc=metadata_pathobj("","&loc","Folder",type,uri);
|
||||||
|
call symputx('foldertype',type,'l');
|
||||||
|
run;
|
||||||
|
%let msg=Location (&loc) was not created!!;
|
||||||
|
%mp_abort(iftrue= (&foldertype ne Tree)
|
||||||
|
,mac=&_program..sas
|
||||||
|
,msg=%superq(msg)
|
||||||
|
)
|
||||||
|
|
||||||
|
%mend mmx_createmetafolder;
|
||||||
2359
package-lock.json
generated
2359
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user