mirror of
https://github.com/sasjs/core.git
synced 2025-12-11 06:24:35 +00:00
Compare commits
316 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
825c97c49c | ||
|
|
f301899269 | ||
|
|
fc81f62d2f | ||
|
|
93aea5ed02 | ||
|
|
55d4c7238a | ||
|
|
cd75bf263a | ||
|
|
929a1a9974 | ||
|
|
7cafb4fb36 | ||
|
|
a8d222a0f8 | ||
|
|
ac0ddf38b0 | ||
|
|
ecd389c935 | ||
|
|
06a5ea06f8 | ||
|
|
955471ed3c | ||
|
|
c8d3b43b12 | ||
|
|
3e313b06a9 | ||
|
|
d7371a4505 | ||
|
|
32a6d15c2e | ||
|
|
b109e7cead | ||
| d291d3e287 | |||
|
|
5a2968e798 | ||
|
|
120ad9a7da | ||
|
|
67a81b2690 | ||
|
|
506cf1812f | ||
|
|
8cc0eb0dd7 | ||
|
|
4c1f69da3a | ||
|
|
f160ebe705 | ||
|
|
3f49925d01 | ||
|
|
53ed5dc916 | ||
|
|
808b24e31b | ||
|
|
c51c9c2ca9 | ||
|
|
4d6edf5566 | ||
|
|
41a24677f5 | ||
|
|
e7d8d8ffb3 | ||
|
|
60d23dd618 | ||
|
|
d2764c3cd1 | ||
|
|
f9f4355143 | ||
|
|
18bc6c889d | ||
|
|
42a16ef496 | ||
|
|
8178b801fb | ||
|
|
ce331a23c8 | ||
|
|
1cc9213467 | ||
|
|
aabbf4d0f9 | ||
|
|
2bea8be70d | ||
|
|
9b2368443e | ||
|
|
7915ba2c41 | ||
|
|
cc61e48868 | ||
|
|
62db83dcf6 | ||
|
|
7ea9e0f8e9 | ||
|
|
1c852515f5 | ||
|
|
b7e677bd8e | ||
|
|
f47f0d2cee | ||
|
|
3d0f426a98 | ||
|
|
2cb51f6164 | ||
|
|
b73bf998da | ||
|
|
c9ad38ee98 | ||
|
|
76b1b951c0 | ||
|
|
996054b17a | ||
|
|
7fca3d4e3f | ||
|
|
c4e599c861 | ||
|
|
0f6ff2cc1e | ||
|
|
7cac4c71fb | ||
|
|
1322bdab92 | ||
|
|
f201df606a | ||
|
|
a56fce86b1 | ||
|
|
41ccc5fdd9 | ||
|
|
b2877bd493 | ||
|
|
df8f8893e7 | ||
|
|
27fbdf193b | ||
|
|
6ae892989d | ||
|
|
39a7b332da | ||
|
|
c81794b542 | ||
|
|
e456da846a | ||
|
|
5c144be05b | ||
|
|
055669c133 | ||
|
|
4b67e13b24 | ||
|
|
f1ec3eda81 | ||
|
|
f2d5859675 | ||
|
|
ea057d4655 | ||
|
|
26c085b354 | ||
|
|
d13ac52739 | ||
|
|
bbbc28ad6d | ||
|
|
530cd6e95c | ||
|
|
c4e17e43e8 | ||
|
|
fed217eec3 | ||
|
|
1934dc8332 | ||
|
|
9de512cfc7 | ||
|
|
cadafcc86b | ||
|
|
5f805b006f | ||
|
|
c6b65366b7 | ||
|
|
51ddd9c1e5 | ||
|
|
20bf3b86af | ||
|
|
de67cd329b | ||
|
|
779e4942c7 | ||
|
|
a69a1ac7f0 | ||
|
|
2a644d6c2b | ||
|
|
843930c666 | ||
|
|
90d69af7ee | ||
|
|
b7bafb49f4 | ||
|
|
2fa9e48286 | ||
|
|
5cee93c7bd | ||
|
|
1a595c64c6 | ||
|
|
2c901831b7 | ||
|
|
28209950ab | ||
|
|
44069e9867 | ||
|
|
e26af5c09a | ||
|
|
4ee13c9389 | ||
|
|
15f903aa42 | ||
|
|
58a0cce39e | ||
|
|
9a5574ea0e | ||
|
|
e6146dcbcf | ||
|
|
583c7e0c83 | ||
|
|
223bdd5983 | ||
|
|
aef14543f0 | ||
|
|
c88764c1d8 | ||
|
|
2c952c8b01 | ||
|
|
de3610d1aa | ||
|
|
d35d597437 | ||
|
|
3e8deda008 | ||
|
|
a27496c7b3 | ||
|
|
265389befc | ||
|
|
db2531e0b3 | ||
|
|
5e77494aa6 | ||
|
|
6a2ac51925 | ||
|
|
f625b04189 | ||
|
|
68aee776d3 | ||
|
|
38d2195d32 | ||
|
|
4e564b5409 | ||
|
|
298acc4e50 | ||
|
|
af98909753 | ||
|
|
b9d33b38bf | ||
|
|
b61b5f1856 | ||
|
|
805474bb46 | ||
|
|
61701f3c6a | ||
|
|
f20d7476bf | ||
|
|
04a3189a89 | ||
|
|
b1380983ec | ||
|
|
b4834f9b40 | ||
|
|
1b5ad93cad | ||
|
|
f2942f2032 | ||
|
|
4198448b81 | ||
|
|
47a33452e0 | ||
|
|
fb21a0adfd | ||
|
|
e01b06b640 | ||
|
|
24380ddf26 | ||
|
|
1ef42d45af | ||
|
|
6ee13a2779 | ||
|
|
ffd2e135dc | ||
|
|
7f2ad5fc66 | ||
| ff1eb54cc3 | |||
|
|
d6235c6357 | ||
|
|
98118adb9a | ||
|
|
369c4412f3 | ||
|
|
7d7608f06c | ||
|
|
3791cb8a2c | ||
|
|
ff82f7d75c | ||
|
|
fdd566e8ce | ||
|
|
328f8c260b | ||
|
|
029169ac80 | ||
| 66ff1de7a9 | |||
| 053290c7df | |||
| af71a5e53b | |||
| ecdce86287 | |||
| ba1272aaf7 | |||
| d6056b9397 | |||
|
|
00511c72c2 | ||
|
|
1d6f04fd56 | ||
| af4dbb5632 | |||
|
|
f48c291dce | ||
|
|
18be74a1c2 | ||
|
|
456d10a90e | ||
|
|
a7fdb52231 | ||
|
|
066ed00e44 | ||
|
|
49fbc210ad | ||
|
|
951aa474f2 | ||
|
|
961dd54ee0 | ||
|
|
921354dac7 | ||
|
|
48212f8797 | ||
| cb8992dade | |||
| 7dec3120be | |||
| 9568b17f20 | |||
| 0a38056c69 | |||
|
|
096bf4fa11 | ||
|
|
030c4a4fc1 | ||
| 1b70205cab | |||
| 539447ed06 | |||
| e3c333ea39 | |||
| ae72446f85 | |||
| 2b6bf4bd02 | |||
| 6dbb3760e0 | |||
| 200d9a5761 | |||
|
|
01a9a5b823 | ||
|
|
35eadd0e9d | ||
| 5cdca95216 | |||
|
|
81b75a32ed | ||
| b7f5a2ec00 | |||
| db859bbf1d | |||
| 27b56e8efd | |||
| 28ea218d02 | |||
| f356e1f351 | |||
| 4b375e0b8c | |||
| 7db207dd1c | |||
|
|
ffdfc57aa6 | ||
|
|
6fc8408988 | ||
|
|
eeb25fa5bc | ||
|
|
521d128afe | ||
|
|
0135dd6c8f | ||
|
|
1b66c59dc0 | ||
| 96be5c65dc | |||
| 8f6ef569e1 | |||
| ff45c5a8b8 | |||
| fb5f1c820a | |||
| c0e33175cf | |||
| 2bfa72f48f | |||
| fdc2e8ac8a | |||
| 2a894419ab | |||
| 58bfc7b4aa | |||
| 818c0f5eae | |||
| dff9e2f387 | |||
| 6c9256e097 | |||
| 0631a05a78 | |||
| 268bdca4e0 | |||
|
|
e38f331ad5 | ||
| 8d64b30419 | |||
| 4a6c8ffbb3 | |||
| b5c86e7031 | |||
| 9783edd0e3 | |||
| 961728a987 | |||
| 4b34322d94 | |||
| 8bb83deede | |||
| 79c81aa8a4 | |||
| bbbcf7d550 | |||
| 82184bc6be | |||
| efc731cfaa | |||
| da9a74ee14 | |||
| 94762d9381 | |||
| 03d9d805ff | |||
| 94416028b7 | |||
| 6cf5d4ef28 | |||
| e4ceaecfb2 | |||
|
|
2eb246c543 | ||
| d9954ae777 | |||
| 364dc9f07f | |||
|
|
fbd8196230 | ||
|
|
5720caaf86 | ||
| d96125c3cf | |||
| 506695be56 | |||
|
|
45f858db15 | ||
|
|
b4d97a063a | ||
|
|
4df8f3b4c2 | ||
|
|
11aa484996 | ||
|
|
b9fd79bd5e | ||
|
|
1beb30d0ff | ||
|
|
e334ea9b85 | ||
|
|
c090c8d53b | ||
|
|
659339bd98 | ||
|
|
4c333ae7b3 | ||
| b3a8b4323e | |||
| 0592206f2d | |||
| bedc2a443a | |||
| 6f86ed62a2 | |||
| def0cc8476 | |||
| 3a9029557e | |||
| 9dc3bcd513 | |||
| 2bcf6346ac | |||
| 0eccc169f5 | |||
| 493639fe4a | |||
| 4987d2fbbc | |||
| 1a35b357d6 | |||
| a7792d93e4 | |||
| 541dc31ad0 | |||
| abccafab7b | |||
| f6cec012da | |||
| d51be73017 | |||
| cafffbb509 | |||
| a88efacfab | |||
| cc7cc55022 | |||
| 15687be5d6 | |||
| d9a82c0bdf | |||
| 6f06e5540d | |||
| 6b782a4fa2 | |||
| efe4709dde | |||
| 5cb41041d9 | |||
| f50cb03fd3 | |||
| ac46489f11 | |||
|
|
5e45701e74 | ||
|
|
8caaacd9f0 | ||
|
|
91983e0a91 | ||
|
|
7b72f0ac94 | ||
|
|
3eae34d8b7 | ||
|
|
58358c916d | ||
|
|
578ef26cd5 | ||
|
|
33189743cd | ||
|
|
459beff4fa | ||
|
|
1c873afe57 | ||
|
|
2b683509ac | ||
|
|
dcccd1491d | ||
|
|
8d9b84037c | ||
|
|
029c1a29ed | ||
|
|
c4dbd5971f | ||
|
|
40ac3bba9a | ||
|
|
21946e74f1 | ||
|
|
6443e2d2ef | ||
|
|
17f03b7507 | ||
|
|
c6a18a4168 | ||
|
|
dd18d1d5a8 | ||
|
|
fc8a39bbca | ||
|
|
8beec7dc19 | ||
|
|
e3f6cb7b45 | ||
|
|
041aff9bc0 | ||
|
|
1986732573 | ||
|
|
958f509894 | ||
|
|
1373957031 | ||
|
|
8466acc7a7 | ||
|
|
45e646565f | ||
|
|
9d9a72220f | ||
|
|
53865a3909 |
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
|
||||
18
.git-hooks/commit-msg
Executable file
18
.git-hooks/commit-msg
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
RED="\033[1;31m"
|
||||
GREEN="\033[1;32m"
|
||||
|
||||
# Get the commit message (the parameter we're given is just the path to the
|
||||
# temporary file which holds the message).
|
||||
commit_message=$(cat "$1")
|
||||
|
||||
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z \-]+\))?!?: .+$") then
|
||||
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "${RED}❌ Commit message does not meet the Conventional Commit standard!"
|
||||
echo "An example of a valid message is:"
|
||||
echo " feat(login): add the 'remember me' button"
|
||||
echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary"
|
||||
exit 1
|
||||
2
.git-hooks/pre-commit
Executable file
2
.git-hooks/pre-commit
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
sasjs lint
|
||||
30
.github/vpn/config.ovpn
vendored
Normal file
30
.github/vpn/config.ovpn
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
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
|
||||
84
.github/workflows/run-tests.yml
vendored
Normal file
84
.github/workflows/run-tests.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
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-bionic.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
|
||||
run: sudo apt-get install doxygen
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check code style
|
||||
run: npm run lint
|
||||
|
||||
- name: Add client
|
||||
run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya
|
||||
|
||||
- name: Add secret
|
||||
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya
|
||||
|
||||
- name: Add access token
|
||||
run: echo "ACCESS_TOKEN=${{secrets.ACCESS_TOKEN}}" >> .env.viya
|
||||
|
||||
- name: Add refresh token
|
||||
run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build
|
||||
|
||||
- name: Run SASjs tests
|
||||
run: npm run test
|
||||
env:
|
||||
CI: true
|
||||
CLIENT: ${{secrets.CLIENT}}
|
||||
SECRET: ${{secrets.SECRET}}
|
||||
SAS_USERNAME: ${{secrets.SAS_USERNAME}}
|
||||
SAS_PASSWORD: ${{secrets.SAS_PASSWORD}}
|
||||
SERVER_URL: ${{secrets.SERVER_URL}}
|
||||
SERVER_TYPE: ${{secrets.SERVER_TYPE}}
|
||||
ACCESS_TOKEN: ${{secrets.ACCESS_TOKEN}}
|
||||
REFRESH_TOKEN: ${{secrets.REFRESH_TOKEN}}
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1 +1,13 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
sasjsbuild/
|
||||
sasjsresults/
|
||||
|
||||
# avoid filenames with spaces being committed to source control
|
||||
**\ **
|
||||
|
||||
# ignore the mc_* files - containing macros for individual libraries
|
||||
mc_*
|
||||
|
||||
# ignore .env files as they can contain sasjs access tokens
|
||||
*.env*
|
||||
6
.gitpod.dockerfile
Normal file
6
.gitpod.dockerfile
Normal file
@@ -0,0 +1,6 @@
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
RUN sudo apt-get update \
|
||||
&& sudo apt-get install -y \
|
||||
doxygen \
|
||||
&& sudo rm -rf /var/lib/apt/lists/*
|
||||
8
.gitpod.yml
Normal file
8
.gitpod.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
tasks:
|
||||
- init: nvm install --latest-npm && npm i -g @sasjs/cli
|
||||
|
||||
image:
|
||||
file: .gitpod.dockerfile
|
||||
vscode:
|
||||
extensions:
|
||||
- sasjs.sasjs-for-vscode
|
||||
11
.npmignore
Normal file
11
.npmignore
Normal file
@@ -0,0 +1,11 @@
|
||||
all.sas
|
||||
build.py
|
||||
.gitpod*
|
||||
tests/
|
||||
sasjs/
|
||||
.github/
|
||||
.git-hooks/
|
||||
.vscode/
|
||||
main.dox
|
||||
make_singlefile.sh
|
||||
*.md
|
||||
13
.sasjslint
Normal file
13
.sasjslint
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"noTrailingSpaces": true,
|
||||
"noEncodedPasswords": true,
|
||||
"hasDoxygenHeader": true,
|
||||
"hasMacroNameInMend": true,
|
||||
"hasMacroParentheses": true,
|
||||
"noNestedMacros": false,
|
||||
"noSpacesInFileNames": true,
|
||||
"maxLineLength": 230,
|
||||
"lowerCaseFileNames": true,
|
||||
"noTabIndentation": true,
|
||||
"indentationMultiple": 2
|
||||
}
|
||||
9
.vscode/.editorconfig
vendored
Normal file
9
.vscode/.editorconfig
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/sasjsbuild/**": true,
|
||||
"**/dist/**":true
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"trim_trailing_whitespace": true
|
||||
}
|
||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"sasjs.sasjs-for-vscode"
|
||||
]
|
||||
}
|
||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.detectIndentation": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [
|
||||
80
|
||||
],
|
||||
"files.trimTrailingWhitespace": true
|
||||
}
|
||||
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# CHANGELOG
|
||||
|
||||
As the changes are managed automatically in github, we don't generate an additional changelog. To view the fixes/features in each release, check out the releases page below:
|
||||
|
||||
[https://github.com/sasjs/core/releases](https://github.com/sasjs/core/releases)
|
||||
128
CODE_OF_CONDUCT.md
Normal file
128
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
https://sasapps.io/contact-us.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
@@ -1,83 +1,31 @@
|
||||
# Contributing
|
||||
|
||||
Contributions to the macrocore library are warmly welcomed! To avoid any
|
||||
misunderstandings, do please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before submitting
|
||||
a PR.
|
||||
Contributions are warmly welcomed! To avoid any misunderstandings, do please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before submitting a PR.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions
|
||||
with the project.
|
||||
Please note we have a [code of conduct](https://www.contributor-covenant.org/version/2/0/code_of_conduct/), please follow it in all your interactions with the project.
|
||||
|
||||
# Environment Setup
|
||||
|
||||
This repository makes use of the [SASjs](https://sasjs.io) framework for code organisation, compilation, documentation, and deployment. The following tools are highly recommended:
|
||||
|
||||
* [NPM](https://sasjs.io/windows/#npm) - the runtime and dependency manager for [SASjs CLI](https://cli.sasjs.io) (batteries included)
|
||||
* [VSCode](https://sasjs.io/windows/#vscode) - feature packed IDE for code editing (warning - highly effective!)
|
||||
* [GIT](https://sasjs.io/windows/#git) - a safety net you cannot (and should not) do without.
|
||||
|
||||
For generating the documentation (`sasjs doc`) it is also necessary to install [doxygen](https://www.doxygen.nl/manual/install.html).
|
||||
|
||||
|
||||
## Code of Conduct
|
||||
To get configured:
|
||||
|
||||
### Our Pledge
|
||||
1. Clone the repository
|
||||
2. Install local dependencies (`npm install`)
|
||||
3. Install the SASjs CLI globally (`npm install -g @sasjs/cli`)
|
||||
4. Add a target, and authentication (`npm add`). See [docs](https://cli.sasjs.io/add/).
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
To contribute:
|
||||
|
||||
### Our Standards
|
||||
1. Create your feature branch (`git checkout -b myfeature`)
|
||||
2. Make your change
|
||||
3. Update the `all.sas` file (`python3 build.py`)
|
||||
4. Push and make a PR
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@macropeople.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2018 (Allan Bowe)
|
||||
Copyright 2020 (Allan Bowe)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
85
README.md
85
README.md
@@ -1,6 +1,27 @@
|
||||
# Macro Core
|
||||
[![npm package][npm-image]][npm-url]
|
||||
[![Github Workflow][githubworkflow-image]][githubworkflow-url]
|
||||
[![Dependency Status][dependency-image]][dependency-url]
|
||||
[]()
|
||||

|
||||
[](/LICENSE)
|
||||

|
||||
[](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
|
||||
[](https://github.com/sasjs/core/issues)
|
||||

|
||||
[](https://gitpod.io/#https://github.com/sasjs/core)
|
||||
|
||||
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 on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
|
||||
|
||||
[npm-image]:https://img.shields.io/npm/v/@sasjs/core.svg
|
||||
[npm-url]:http://npmjs.org/package/@sasjs/core
|
||||
[githubworkflow-image]:https://github.com/sasjs/core/actions/workflows/main.yml/badge.svg
|
||||
[githubworkflow-url]:https://github.com/sasjs/core/blob/main/.github/workflows/main.yml
|
||||
[dependency-image]:https://david-dm.org/sasjs/core.svg
|
||||
[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.
|
||||
|
||||
You can download and compile them all in just two lines of SAS code:
|
||||
|
||||
@@ -9,7 +30,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
```
|
||||
|
||||
Documentation: https://sasjs.github.io/core.github.io/files.html
|
||||
Documentation: https://core.sasjs.io
|
||||
|
||||
# Components
|
||||
|
||||
@@ -40,6 +61,27 @@ Documentation: https://sasjs.github.io/core.github.io/files.html
|
||||
- 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.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
/* compile the lua module */
|
||||
%ml_yourmodule()
|
||||
|
||||
/* Execute. Do not use the restart keyword! */
|
||||
proc lua;
|
||||
submit;
|
||||
print(yourStuff);
|
||||
endsubmit;
|
||||
run;
|
||||
```
|
||||
|
||||
- X command enabled
|
||||
- Prefixes: _mmw_,_mmu_,_mmx_
|
||||
|
||||
# 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:
|
||||
@@ -63,7 +105,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
## File Properties
|
||||
|
||||
- filenames much match macro names
|
||||
- filenames must be lowercase
|
||||
- filenames must be lowercase, without spaces
|
||||
- macro names must be lowercase
|
||||
- one macro per file
|
||||
- prefixes:
|
||||
@@ -72,12 +114,13 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
- _mm_ for metadata macros (interface with the metadata server).
|
||||
- _mmx_ for macros that use metadata and are XCMD enabled
|
||||
- _mx_ for macros that are XCMD enabled
|
||||
- _ml_ for macros that are used to compile LUA modules
|
||||
- _mv_ for macros that will only work in Viya
|
||||
- follow verb-noun convention
|
||||
- unix style line endings (lf)
|
||||
- individual lines should be no more than 80 characters long
|
||||
- UTF-8
|
||||
- no trailing white space
|
||||
|
||||
|
||||
## Header Properties
|
||||
|
||||
@@ -91,18 +134,48 @@ The **Macro Core** documentation is created using [doxygen](http://www.doxygen.n
|
||||
- version. The EARLIEST SAS version in which this macro is known to work.
|
||||
- author. Author name, contact details optional
|
||||
|
||||
All macros must be commented in the doxygen format, to enable the [online documentation](https://sasjs.github.io/core.github.io/).
|
||||
All macros must be commented in the doxygen format, to enable the [online documentation](https://core.sasjs.io).
|
||||
|
||||
### Dependencies
|
||||
SAS code can contain one of two types of dependency - SAS Macros, and SAS Includes. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers:
|
||||
|
||||
```sas
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mm_assignlib.sas
|
||||
|
||||
<h4> SAS Includes </h4>
|
||||
@li somefile.ddl SOMEFREF
|
||||
@li someprogram.sas FREFTWO
|
||||
```
|
||||
|
||||
The CLI can then extract all the dependencies and insert as precode (SAS Macros) or in a temp engine fileref (SAS Includes) when creating SAS Jobs and Services.
|
||||
|
||||
When contributing to this library, it is therefore important to ensure that all dependencies are listed in the header in this format.
|
||||
|
||||
|
||||
## Coding Standards
|
||||
|
||||
- Indentation = 2 spaces. No tabs!
|
||||
- no trailing white space
|
||||
- no invisible characters, other than spaces. If invisibles are needed, use hex literals.
|
||||
- Macro variables should not have the trailing dot (`&var` not `&var.`) unless necessary to prevent incorrect resolution
|
||||
- The closing `%mend;` should not contain the macro name.
|
||||
- The closing `%mend;` should **not** contain the macro name.
|
||||
- All macros should be defined with brackets, even if no variables are needed - ie `%macro x();` not `%macro x;`
|
||||
- Mandatory parameters should be positional, all optional parameters should be keyword (var=) style.
|
||||
- All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect.
|
||||
- Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;`
|
||||
- The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics.
|
||||
|
||||
# General Notes
|
||||
|
||||
- All macros should be compatible with SAS versions from support level B and above (so currently 9.2 and later). If an earlier version is not supported, then the macro should say as such in the header documentation, and exit gracefully (eg `%if %sysevalf(&sysver<9.3) %then %return`).
|
||||
|
||||
## Star Gazing
|
||||
|
||||
If you find this library useful, please leave a [star](https://github.com/sasjs/core/stargazers) and help us grow our star graph!
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
Security is an extremely high priority when it comes to the SASjs product suite. We take a number of steps across all repositories to minimise risk, such as:
|
||||
|
||||
* Regular dependabot updates
|
||||
* Snyk reports
|
||||
* Minimising dependencies, especially production dependencies (sasjs/core has NONE)
|
||||
* Testing & Code review process
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We support only the latest version
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We welcome disclosures of all kinds in relation to all the SASjs libraries. You can submit them here: [https://sasapps.io/contact-us](https://sasapps.io/contact-us)
|
||||
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
/**
|
||||
@file
|
||||
@brief abort gracefully according to context
|
||||
@details Do not use directly! See bottom of explanation for details.
|
||||
@brief Abort, ungracefully
|
||||
@details Will abort with a straightforward %abort if the condition is true.
|
||||
|
||||
Configures an abort mechanism according to site specific policies or the
|
||||
particulars of an environment. For instance, can stream custom
|
||||
results back to the client in an STP Web App context, or completely stop
|
||||
in the case of a batch run.
|
||||
|
||||
For the sharp eyed readers - this is no longer a macro function!! It became
|
||||
a macro procedure during a project and now it's kinda stuck that way until
|
||||
that project is updated (if it's ever updated). In the meantime we created
|
||||
`mp_abort` which is just a wrapper for this one, and so we recomend you use
|
||||
that for forwards compatibility reasons.
|
||||
|
||||
@param mac= to contain the name of the calling macro
|
||||
@param type= deprecated. Not used.
|
||||
@param msg= message to be returned
|
||||
@param iftrue= supply a condition under which the macro should be executed.
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_abort.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mf_abort(mac=mf_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||
%macro mf_abort(mac=mf_abort.sas, type=deprecated, msg=, iftrue=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
@@ -32,110 +20,8 @@
|
||||
%if %length(&mac)>0 %then %put NOTE- called by &mac;
|
||||
%put NOTE - &msg;
|
||||
|
||||
/* Stored Process Server web app context */
|
||||
%if %symexist(_metaperson) or "&SYSPROCESSNAME"="Compute Server" %then %do;
|
||||
options obs=max replace nosyntaxcheck mprint;
|
||||
/* extract log err / warn, if exist */
|
||||
%local logloc logline;
|
||||
%global logmsg; /* capture global messages */
|
||||
%if %symexist(SYSPRINTTOLOG) %then %let logloc=&SYSPRINTTOLOG;
|
||||
%else %let logloc=%qsysfunc(getoption(LOG));
|
||||
proc printto log=log;run;
|
||||
%if %length(&logloc)>0 %then %do;
|
||||
%let logline=0;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input; putlog _infile_;
|
||||
i=1;
|
||||
retain logonce 0;
|
||||
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
|
||||
call symputx('logline',_n_);
|
||||
logonce+1;
|
||||
end;
|
||||
run;
|
||||
/* capture log including lines BEFORE the err */
|
||||
%if &logline>0 %then %do;
|
||||
data _null_;
|
||||
infile &logloc lrecl=5000;
|
||||
input;
|
||||
i=1;
|
||||
stoploop=0;
|
||||
if _n_ ge &logline-5 and stoploop=0 then do until (i>12);
|
||||
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
||||
input;
|
||||
i+1;
|
||||
stoploop=1;
|
||||
end;
|
||||
if stoploop=1 then stop;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%abort;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000;
|
||||
length msg $32767;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
/* escape the quotes */
|
||||
msg=tranwrd(msg,'"','\"');
|
||||
/* ditch the CRLFs as chrome complains */
|
||||
msg=compress(msg,,'kw');
|
||||
/* quote without quoting the quotes (which are escaped instead) */
|
||||
msg=cats('"',msg,'"');
|
||||
if symexist('_debug') then debug=symget('_debug');
|
||||
if debug ge 131 then put '>>weboutBEGIN<<';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
put ',"sasjsAbort" : [{';
|
||||
put ' "MSG":' msg ;
|
||||
put ' ,"MAC": "' "&mac" '"}]';
|
||||
put ",""SYSUSERID"" : ""&sysuserid"" ";
|
||||
if symexist('_metauser') then do;
|
||||
_METAUSER=quote(trim(symget('_METAUSER')));
|
||||
put ",""_METAUSER"": " _METAUSER;
|
||||
_METAPERSON=quote(trim(symget('_METAPERSON')));
|
||||
put ',"_METAPERSON": ' _METAPERSON;
|
||||
end;
|
||||
_PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
|
||||
put ',"_PROGRAM" : ' _PROGRAM ;
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||
put "}" @;
|
||||
%if &_debug ge 131 %then %do;
|
||||
put '>>weboutEND<<';
|
||||
%end;
|
||||
run;
|
||||
%let syscc=0;
|
||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||
/* refer web service output to file service in one hit */
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json";
|
||||
%let rc=%sysfunc(fcopy(_web,_webout));
|
||||
%end;
|
||||
%else %do;
|
||||
data _null_;
|
||||
if symexist('sysprocessmode')
|
||||
then if symget("sysprocessmode")="SAS Stored Process Server"
|
||||
then rc=stpsrvset('program error', 0);
|
||||
run;
|
||||
%end;
|
||||
/**
|
||||
* endsas is reliable but kills some deployments.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
*/
|
||||
%put _all_;
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip(); %macro skippy();';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_abort;
|
||||
|
||||
/** @endcond */
|
||||
@@ -23,4 +23,4 @@
|
||||
%if %sysfunc(exist(&libds)) ne 1 & %sysfunc(exist(&libds,VIEW)) ne 1 %then 0;
|
||||
%else 1;
|
||||
|
||||
%mend;
|
||||
%mend mf_existds;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
@file mf_existfeature.sas
|
||||
@file
|
||||
@brief Checks whether a feature exists
|
||||
@details Check to see if a feature is supported in your environment.
|
||||
Run without arguments to see a list of detectable features.
|
||||
@@ -7,19 +7,20 @@
|
||||
actual feature detection, as that is tricky / impossible to do
|
||||
without generating errors in most cases.
|
||||
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
%put %mf_existfeature(PROCLUA);
|
||||
|
||||
@param feature the feature to detect. Leave blank to list all in log.
|
||||
|
||||
|
||||
@return output returns 1 or 0 (or -1 if not found)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getplatform.sas
|
||||
|
||||
|
||||
@version 8
|
||||
@author Allan Bowe
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mf_existfeature(feature
|
||||
)/*/STORE SOURCE*/;
|
||||
@@ -31,12 +32,16 @@
|
||||
%put Supported features: PROCLUA;
|
||||
%end;
|
||||
%else %if &feature=PROCLUA %then %do;
|
||||
/* https://blogs.sas.com/content/sasdummy/2015/08/03/using-lua-within-your-sas-programs */
|
||||
%if &platform=SASVIYA %then 1;
|
||||
%else %if "&sysver"="9.3" or "&sysver"="9.4" %then 1;
|
||||
%else 0;
|
||||
%else %if "&sysver"="9.2" or "&sysver"="9.3" %then 0;
|
||||
%else %if "&SYSVLONG" < "9.04.01M3" %then 0;
|
||||
%else 1;
|
||||
%end;
|
||||
%else %do;
|
||||
-1
|
||||
%put &sysmacroname: &feature not found;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_existfeature;
|
||||
|
||||
/** @endcond */
|
||||
27
base/mf_existfileref.sas
Normal file
27
base/mf_existfileref.sas
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks whether a fileref exists
|
||||
@details You can probably do without this macro as it is just a one liner.
|
||||
Mainly it is here as a convenient way to remember the syntax!
|
||||
|
||||
@param fref the fileref to detect
|
||||
|
||||
@return output Returns 1 if found and 0 if not found. Note - it is possible
|
||||
that the fileref is found, but the file does not (yet) exist. If you need
|
||||
to test for this, you may as well use the fileref function directly.
|
||||
|
||||
@version 8
|
||||
@author [Allan Bowe](https://www.linkedin.com/in/allanbowe/)
|
||||
**/
|
||||
|
||||
%macro mf_existfileref(fref
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if %sysfunc(fileref(&fref))=0 %then %do;
|
||||
1
|
||||
%end;
|
||||
%else %do;
|
||||
0
|
||||
%end;
|
||||
|
||||
%mend mf_existfileref;
|
||||
@@ -12,6 +12,7 @@
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mf_existvar(libds /* 2 part dataset name */
|
||||
, var /* variable name */
|
||||
@@ -29,4 +30,6 @@
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mf_existvar;
|
||||
|
||||
/** @endcond */
|
||||
@@ -2,11 +2,11 @@
|
||||
@file
|
||||
@brief Checks if a set of variables ALL exist in a data set.
|
||||
@details Returns 0 if ANY of the variables do not exist, or 1 if they ALL do.
|
||||
Usage:
|
||||
Usage:
|
||||
|
||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar)
|
||||
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param libds 2 part dataset or view reference
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mf_existvarlist(libds, varlist
|
||||
@@ -28,7 +29,7 @@
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
|
||||
%if &dsid=0 %then %do;
|
||||
%put WARNING: unable to open &libds in mf_existvarlist (&dsid);
|
||||
%put %str(WARN)ING: unable to open &libds in mf_existvarlist (&dsid);
|
||||
%end;
|
||||
|
||||
%if %sysfunc(attrn(&dsid,NVARS))=0 %then %do;
|
||||
@@ -53,4 +54,6 @@
|
||||
0
|
||||
%put Vars not found: &found;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_existvarlist;
|
||||
|
||||
/** @endcond */
|
||||
@@ -17,13 +17,13 @@
|
||||
**/
|
||||
|
||||
%macro mf_getattrc(
|
||||
libds
|
||||
libds
|
||||
,attr
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %str(WARN)ING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
@@ -31,4 +31,4 @@
|
||||
%sysfunc(attrc(&dsid,&attr))
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_getattrc;
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
@param libds library.dataset
|
||||
@param attr Common values are NLOBS and NVARS, full list in [documentation](
|
||||
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
||||
http://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000212040.htm)
|
||||
@return output returns result of the attrn value supplied, or -1 and log
|
||||
message if error.
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
**/
|
||||
|
||||
%macro mf_getattrn(
|
||||
libds
|
||||
libds
|
||||
,attr
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid rc;
|
||||
%let dsid=%sysfunc(open(&libds,is));
|
||||
%if &dsid = 0 %then %do;
|
||||
%put WARNING: Cannot open %trim(&libds), system message below;
|
||||
%put %str(WARN)ING: Cannot open %trim(&libds), system message below;
|
||||
%put %sysfunc(sysmsg());
|
||||
-1
|
||||
%end;
|
||||
@@ -31,4 +31,4 @@
|
||||
%sysfunc(attrn(&dsid,&attr))
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_getattrn;
|
||||
@@ -3,7 +3,7 @@
|
||||
@brief Returns the engine type of a SAS library
|
||||
@details Usage:
|
||||
|
||||
%put %mf_getEngine(SASHELP);
|
||||
%put %mf_getengine(SASHELP);
|
||||
|
||||
returns:
|
||||
> V9
|
||||
@@ -21,16 +21,23 @@
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mf_getEngine(libref
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_getxengine.sas
|
||||
|
||||
**/
|
||||
/** @cond */
|
||||
|
||||
%macro mf_getengine(libref
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid engnum rc engine;
|
||||
|
||||
/* in case the parameter is a libref.tablename, pull off just the libref */
|
||||
%let libref = %upcase(%scan(&libref, 1, %str(.)));
|
||||
|
||||
%let dsid=%sysfunc(open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i));
|
||||
%let dsid=%sysfunc(
|
||||
open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)
|
||||
);
|
||||
%if (&dsid ^= 0) %then %do;
|
||||
%let engnum=%sysfunc(varnum(&dsid,ENGINE));
|
||||
%let rc=%sysfunc(fetch(&dsid));
|
||||
@@ -39,6 +46,8 @@
|
||||
%let rc= %sysfunc(close(&dsid));
|
||||
%end;
|
||||
|
||||
&engine
|
||||
&engine
|
||||
|
||||
%mend;
|
||||
%mend mf_getengine;
|
||||
|
||||
/** @endcond */
|
||||
@@ -38,10 +38,10 @@
|
||||
%let rc=%sysfunc(filename(fref));
|
||||
|
||||
%if &format=NO %then %do;
|
||||
&bytes
|
||||
&bytes
|
||||
%end;
|
||||
%else %do;
|
||||
%sysfunc(INPUTN(&bytes, best.),sizekmg.)
|
||||
%end;
|
||||
|
||||
%mend ;
|
||||
%mend mf_getfilesize ;
|
||||
@@ -3,8 +3,8 @@
|
||||
@brief retrieves a key value pair from a control dataset
|
||||
@details By default, control dataset is work.mp_setkeyvalue. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%put %mf_getkeyvalue(someindex)
|
||||
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
%macro mf_getkeyvalue(key,libds=work.mp_setkeyvalue
|
||||
)/*/STORE SOURCE*/;
|
||||
%local ds dsid key valc valn type rc;
|
||||
%local ds dsid key valc valn type rc;
|
||||
%let dsid=%sysfunc(open(&libds(where=(key="&key"))));
|
||||
%syscall set(dsid);
|
||||
%let rc = %sysfunc(fetch(&dsid));
|
||||
@@ -29,4 +29,4 @@
|
||||
&valc
|
||||
%end;
|
||||
%else %put %str(ERR)OR: Unable to find key &key in ds &libds;
|
||||
%mend;
|
||||
%mend mf_getkeyvalue;
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
@param switch the param for which to return a platform specific variable
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_mval.sas
|
||||
@li mf_trimstr.sas
|
||||
|
||||
@version 9.4 / 3.4
|
||||
@author Allan Bowe
|
||||
@@ -22,25 +23,27 @@
|
||||
%local a b c;
|
||||
%if &switch.NONE=NONE %then %do;
|
||||
%if %symexist(sysprocessmode) %then %do;
|
||||
%if "&sysprocessmode"="SAS Object Server"
|
||||
%if "&sysprocessmode"="SAS Object Server"
|
||||
or "&sysprocessmode"= "SAS Compute Server" %then %do;
|
||||
SASVIYA
|
||||
%end;
|
||||
%else %if "&sysprocessmode"="SAS Stored Process Server" %then %do;
|
||||
%else %if "&sysprocessmode"="SAS Stored Process Server"
|
||||
or "&sysprocessmode"="SAS Workspace Server"
|
||||
%then %do;
|
||||
SASMETA
|
||||
%return;
|
||||
%end;
|
||||
%else %do;
|
||||
SAS
|
||||
BASESAS
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%else %if %symexist(_metaport) %then %do;
|
||||
%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;
|
||||
SASMETA
|
||||
%return;
|
||||
%end;
|
||||
%else %do;
|
||||
SAS
|
||||
BASESAS
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
@@ -57,6 +60,6 @@
|
||||
%else 0;
|
||||
%end;
|
||||
%else %if &switch=VIYARESTAPI %then %do;
|
||||
%sysfunc(getoption(servicesbaseurl))
|
||||
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_getplatform;
|
||||
@@ -3,11 +3,18 @@
|
||||
@brief Adds custom quotes / delimiters to a delimited string
|
||||
@details Can be used in open code, eg as follows:
|
||||
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
%put %mf_getquotedstr(blah blah blah);
|
||||
|
||||
which returns:
|
||||
> 'blah','blah','blah'
|
||||
|
||||
Alternatively:
|
||||
|
||||
%put %mf_getquotedstr(these words are double quoted,quote=D)
|
||||
|
||||
for:
|
||||
> "these","words","are","double","quoted"
|
||||
|
||||
@param in_str the unquoted, spaced delimited string to transform
|
||||
@param dlm= the delimeter to be applied to the output (default comma)
|
||||
@param indlm= the delimeter used for the input (default is space)
|
||||
@@ -43,4 +50,4 @@
|
||||
|
||||
&buffer
|
||||
|
||||
%mend;
|
||||
%mend mf_getquotedstr;
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mf_getschema(libref
|
||||
@@ -37,4 +38,6 @@
|
||||
|
||||
&schema
|
||||
|
||||
%mend;
|
||||
%mend mf_getschema;
|
||||
|
||||
/** @endcond */
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
/**
|
||||
@file
|
||||
@brief Assigns and returns an unused fileref
|
||||
@details Use as follows:
|
||||
@details
|
||||
Use as follows:
|
||||
|
||||
%let fileref1=%mf_getuniquefileref();
|
||||
%let fileref2=%mf_getuniquefileref();
|
||||
%put &fileref1 &fileref2;
|
||||
%let fileref1=%mf_getuniquefileref();
|
||||
%let fileref2=%mf_getuniquefileref();
|
||||
%put &fileref1 &fileref2;
|
||||
|
||||
which returns:
|
||||
|
||||
> mcref0 mcref1
|
||||
|
||||
@prefix= first part of fileref. Remember that filerefs can only be 8
|
||||
@param prefix= first part of fileref. Remember that filerefs can only be 8
|
||||
characters, so a 7 letter prefix would mean that `maxtries` should be 10.
|
||||
@param maxtries= the last part of the libref. Provide an integer value.
|
||||
|
||||
@@ -33,4 +34,4 @@
|
||||
%end;
|
||||
%end;
|
||||
%put unable to find available fileref in range &prefix.0-&maxtries;
|
||||
%mend;
|
||||
%mend mf_getuniquefileref;
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
> mclib3
|
||||
|
||||
@prefix= first part of libref. Remember that librefs can only be 8 characters,
|
||||
@param prefix= first part of libref. Remember that librefs can only be 8 characters,
|
||||
so a 7 letter prefix would mean that maxtries should be 10.
|
||||
@param maxtries= the last part of the libref. Provide an integer value.
|
||||
|
||||
@@ -37,4 +37,4 @@
|
||||
%end;
|
||||
%end;
|
||||
%put unable to find available libref in range &prefix.0-&maxtries;
|
||||
%mend;
|
||||
%mend mf_getuniquelibref;
|
||||
@@ -18,5 +18,5 @@
|
||||
|
||||
|
||||
%macro mf_getuniquename(prefix=MC);
|
||||
&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))
|
||||
%mend;
|
||||
&prefix.%substr(%sysfunc(compress(%sysfunc(uuidgen()),-)),1,32-%length(&prefix))
|
||||
%mend mf_getuniquename;
|
||||
@@ -2,7 +2,7 @@
|
||||
@file
|
||||
@brief Returns a userid according to session context
|
||||
@details In a workspace session, a user is generally represented by <code>
|
||||
&sysuserid</code> or <code>SYS_COMPUTE_SESSION_OWNER</code> if it exists.
|
||||
&sysuserid</code> or <code>SYS_COMPUTE_SESSION_OWNER</code> if it exists.
|
||||
In a Stored Process session, <code>&sysuserid</code>
|
||||
resolves to a system account (default=sassrv) and instead there are several
|
||||
metadata username variables to choose from (_metauser, _metaperson
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
%let user= %mf_getUser();
|
||||
%put &user;
|
||||
|
||||
|
||||
@param type - do not use, may be deprecated in a future release
|
||||
|
||||
@return SYSUSERID (if workspace server)
|
||||
@@ -39,4 +39,4 @@
|
||||
|
||||
%quote(&user)
|
||||
|
||||
%mend;
|
||||
%mend mf_getuser;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
%put %mf_getvalue(sashelp.class,name,filter=%quote(age=15));
|
||||
%put %mf_getvalue(sashelp.class,name);
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getattrn.sas
|
||||
|
||||
@param libds dataset to query
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
%macro mf_getvalue(libds,variable,filter=1
|
||||
)/*/STORE SOURCE*/;
|
||||
%if %mf_getattrn(&libds,NLOBS)>0 %then %do;
|
||||
%if %mf_getattrn(&libds,NLOBS)>0 %then %do;
|
||||
%local dsid rc &variable;
|
||||
%let dsid=%sysfunc(open(&libds(where=(&filter))));
|
||||
%syscall set(dsid);
|
||||
@@ -30,4 +30,4 @@
|
||||
%trim(&&&variable)
|
||||
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_getvalue;
|
||||
@@ -29,4 +29,4 @@
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
&nvars
|
||||
%mend;
|
||||
%mend mf_getvarcount;
|
||||
@@ -5,9 +5,9 @@
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
format str1 $1. num1 datetime19.;
|
||||
str2='hello mum!'; num2=666;
|
||||
stop;
|
||||
format str1 $1. num1 datetime19.;
|
||||
str2='hello mum!'; num2=666;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarFormat(test,str1);
|
||||
%put %mf_getVarFormat(work.test,num1);
|
||||
@@ -23,9 +23,9 @@
|
||||
8.
|
||||
NOTE: Variable renegade does not exist in test
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param var Variable name for which a format should be returned
|
||||
@param force Set to 1 to supply a default if the variable has no format
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] var Variable name for which a format should be returned
|
||||
@param [in] force=(0) Set to 1 to supply a default if the variable has no format
|
||||
@returns outputs format
|
||||
|
||||
@author Allan Bowe
|
||||
@@ -45,9 +45,9 @@
|
||||
/* Get variable format */
|
||||
%if(&vnum > 0) %then %let vformat=%sysfunc(varfmt(&dsid, &vnum));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
%return;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
%return;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
@@ -60,7 +60,7 @@
|
||||
%let vlen = %sysfunc(varlen(&dsid, &vnum));
|
||||
%let vtype = %sysfunc(vartype(&dsid, &vnum.));
|
||||
%if &vtype=C %then %let vformat=$&vlen..;
|
||||
%else %let vformat=8.;
|
||||
%else %let vformat=best.;
|
||||
%end;
|
||||
|
||||
|
||||
@@ -68,4 +68,4 @@
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable format */
|
||||
&vformat
|
||||
%mend;
|
||||
%mend mf_getVarFormat;
|
||||
@@ -5,8 +5,8 @@
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarLen(test,str);
|
||||
%put %mf_getVarLen(work.test,num);
|
||||
@@ -39,8 +39,8 @@
|
||||
/* Get variable format */
|
||||
%if(&vnum > 0) %then %let vlen = %sysfunc(varlen(&dsid, &vnum));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vlen = %str( );
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vlen = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
@@ -49,4 +49,4 @@
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable format */
|
||||
&vlen
|
||||
%mend;
|
||||
%mend mf_getVarLen;
|
||||
@@ -10,14 +10,21 @@
|
||||
returns:
|
||||
> List of Variables=Name Sex Age Height Weight
|
||||
|
||||
For a seperated list of column values:
|
||||
|
||||
%put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
|
||||
|
||||
returns:
|
||||
> "Name","Sex","Age","Height","Weight"
|
||||
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param dlm= provide a delimiter (eg comma or space) to separate the vars
|
||||
@param quote= use either DOUBLE or SINGLE to quote the results
|
||||
@param [in] libds Two part dataset (or view) reference.
|
||||
@param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the
|
||||
variables
|
||||
@param [in] quote= (none) use either DOUBLE or SINGLE to quote the results
|
||||
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
|
||||
@li A Return All columns
|
||||
@li C Return Character columns
|
||||
@li N Return Numeric columns
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -27,9 +34,10 @@
|
||||
%macro mf_getvarlist(libds
|
||||
,dlm=%str( )
|
||||
,quote=no
|
||||
,typefilter=A
|
||||
)/*/STORE SOURCE*/;
|
||||
/* declare local vars */
|
||||
%local outvar dsid nvars x rc dlm q var;
|
||||
%local outvar dsid nvars x rc dlm q var vtype;
|
||||
|
||||
/* credit Rowland Hale - byte34 is double quote, 39 is single quote */
|
||||
%if %upcase("e)=DOUBLE %then %let q=%qsysfunc(byte(34));
|
||||
@@ -37,28 +45,30 @@
|
||||
/* open dataset in macro */
|
||||
%let dsid=%sysfunc(open(&libds));
|
||||
|
||||
|
||||
%if &dsid %then %do;
|
||||
%let nvars=%sysfunc(attrn(&dsid,NVARS));
|
||||
%if &nvars>0 %then %do;
|
||||
/* add first dataset variable to global macro variable */
|
||||
%let outvar=&q.%sysfunc(varname(&dsid,1))&q.;
|
||||
/* add remaining variables with supplied delimeter */
|
||||
/* add variables with supplied delimeter */
|
||||
%do x=1 %to &nvars;
|
||||
%let var=&q.%sysfunc(varname(&dsid,&x))&q.;
|
||||
%if &var=&q&q %then %do;
|
||||
%put &sysmacroname: Empty column found in &libds!;
|
||||
%let var=&q. &q.;
|
||||
/* get variable type */
|
||||
%let vtype=%sysfunc(vartype(&dsid,&x));
|
||||
%if &vtype=&typefilter or &typefilter=A %then %do;
|
||||
%let var=&q.%sysfunc(varname(&dsid,&x))&q.;
|
||||
%if &var=&q&q %then %do;
|
||||
%put &sysmacroname: Empty column found in &libds!;
|
||||
%let var=&q. &q.;
|
||||
%end;
|
||||
%if %quote(&outvar)=%quote() %then %let outvar=&var;
|
||||
%else %let outvar=&outvar.&dlm.&var.;
|
||||
%end;
|
||||
%if &x=1 %then %let outvar=&var;
|
||||
%else %let outvar=&outvar.&dlm.&var.;
|
||||
%end;
|
||||
%end;
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
%else %do;
|
||||
%put unable to open &libds (rc=&dsid);
|
||||
%put &sysmacroname: Unable to open &libds (rc=&dsid);
|
||||
%put &sysmacroname: SYSMSG= %sysfunc(sysmsg());
|
||||
%let rc=%sysfunc(close(&dsid));
|
||||
%end;
|
||||
&outvar
|
||||
%mend;
|
||||
%mend mf_getvarlist;
|
||||
@@ -6,8 +6,8 @@
|
||||
Usage:
|
||||
|
||||
data work.test;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
format str $1. num datetime19.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getVarNum(work.test,str);
|
||||
%put %mf_getVarNum(work.test,num);
|
||||
@@ -39,8 +39,8 @@ returns:
|
||||
/* Get variable number */
|
||||
%let vnum = %sysfunc(varnum(&dsid, &var));
|
||||
%if(&vnum <= 0) %then %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vnum = %str( );
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vnum = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &ds not opened! (rc=&dsid);
|
||||
@@ -51,4 +51,4 @@ returns:
|
||||
/* Return variable number */
|
||||
&vnum.
|
||||
|
||||
%mend;
|
||||
%mend mf_getVarNum;
|
||||
@@ -5,8 +5,8 @@
|
||||
Usage:
|
||||
|
||||
data test;
|
||||
length str $1. num 8.;
|
||||
stop;
|
||||
length str $1. num 8.;
|
||||
stop;
|
||||
run;
|
||||
%put %mf_getvartype(test,str);
|
||||
%put %mf_getvartype(work.test,num);
|
||||
@@ -35,8 +35,8 @@ Usage:
|
||||
/* Get variable type (C/N) */
|
||||
%if(&vnum. > 0) %then %let vtype = %sysfunc(vartype(&dsid, &vnum.));
|
||||
%else %do;
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vtype = %str( );
|
||||
%put NOTE: Variable &var does not exist in &libds;
|
||||
%let vtype = %str( );
|
||||
%end;
|
||||
%end;
|
||||
%else %put dataset &libds not opened! (rc=&dsid);
|
||||
@@ -45,4 +45,4 @@ Usage:
|
||||
%let rc = %sysfunc(close(&dsid));
|
||||
/* Return variable type */
|
||||
&vtype
|
||||
%mend;
|
||||
%mend mf_getvartype;
|
||||
43
base/mf_getxengine.sas
Normal file
43
base/mf_getxengine.sas
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns the engine type of a SAS fileref
|
||||
@details Queries sashelp.vextfl to get the xengine value.
|
||||
Usage:
|
||||
|
||||
filename feng temp;
|
||||
%put %mf_getxengine(feng);
|
||||
|
||||
returns:
|
||||
> TEMP
|
||||
|
||||
@param fref The fileref to check
|
||||
|
||||
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mf_getengine.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_getxengine(fref
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dsid engnum rc engine;
|
||||
|
||||
%let dsid=%sysfunc(
|
||||
open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i)
|
||||
);
|
||||
%if (&dsid ^= 0) %then %do;
|
||||
%let engnum=%sysfunc(varnum(&dsid,XENGINE));
|
||||
%let rc=%sysfunc(fetch(&dsid));
|
||||
%let engine=%sysfunc(getvarc(&dsid,&engnum));
|
||||
%* put &fref. ENGINE is &engine.;
|
||||
%let rc= %sysfunc(close(&dsid));
|
||||
%end;
|
||||
%else %let engine=0;
|
||||
|
||||
&engine
|
||||
|
||||
%mend mf_getxengine;
|
||||
@@ -6,12 +6,13 @@
|
||||
%sysevalf(%superq(param)=,boolean)
|
||||
|
||||
Usage:
|
||||
|
||||
%put mf_isblank(&var);
|
||||
|
||||
inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
||||
%put mf_isblank(&var);
|
||||
|
||||
@param param VALUE to be checked
|
||||
inspiration:
|
||||
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
|
||||
|
||||
@param param VALUE to be checked
|
||||
|
||||
@return output returns 1 (if blank) else 0
|
||||
|
||||
@@ -23,4 +24,4 @@
|
||||
|
||||
%sysevalf(%superq(param)=,boolean)
|
||||
|
||||
%mend;
|
||||
%mend mf_isblank;
|
||||
34
base/mf_isdir.sas
Normal file
34
base/mf_isdir.sas
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks whether a path is a valid directory
|
||||
@details
|
||||
Usage:
|
||||
|
||||
%let isdir=%mf_isdir(/tmp);
|
||||
|
||||
With thanks and full credit to Andrea Defronzo -
|
||||
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
|
||||
|
||||
@param path full path of the file/directory to be checked
|
||||
|
||||
@return output returns 1 if path is a directory, 0 if it is not
|
||||
|
||||
@version 9.2
|
||||
**/
|
||||
|
||||
%macro mf_isdir(path
|
||||
)/*/STORE SOURCE*/;
|
||||
%local rc did is_directory fref_t;
|
||||
|
||||
%let is_directory = 0;
|
||||
%let rc = %sysfunc(filename(fref_t, %superq(path)));
|
||||
%let did = %sysfunc(dopen(&fref_t.));
|
||||
%if &did. ^= 0 %then %do;
|
||||
%let is_directory = 1;
|
||||
%let rc = %sysfunc(dclose(&did.));
|
||||
%end;
|
||||
%let rc = %sysfunc(filename(fref_t));
|
||||
|
||||
&is_directory
|
||||
|
||||
%mend mf_isdir;
|
||||
@@ -26,4 +26,4 @@
|
||||
&root
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mf_loc;
|
||||
|
||||
@@ -46,8 +46,8 @@ Usage:
|
||||
*/
|
||||
|
||||
%if (%length(&dir) gt %length(&child)) %then %do;
|
||||
%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));
|
||||
%mf_mkdir(&parent)
|
||||
%let parent = %substr(&dir, 1, %length(&dir)-%length(&child));
|
||||
%mf_mkdir(&parent)
|
||||
%end;
|
||||
|
||||
/*
|
||||
@@ -56,12 +56,12 @@ Usage:
|
||||
|
||||
%let dname = %sysfunc(dcreate(&child, &parent));
|
||||
%if (%bquote(&dname) eq ) %then %do;
|
||||
%put %str(ERR)OR: could not create &parent + &child;
|
||||
%abort cancel;
|
||||
%put %str(ERR)OR: could not create &parent + &child;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%else %do;
|
||||
%put Directory created: &dir;
|
||||
%put Directory created: &dir;
|
||||
%end;
|
||||
%end;
|
||||
/* exit quietly if directory did exist.*/
|
||||
%mend;
|
||||
%mend mf_mkdir;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
@file mf_mval.sas
|
||||
@brief Returns a macro variable value if the variable exists
|
||||
@details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
|
||||
type logic.
|
||||
@details
|
||||
Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
|
||||
type logic.
|
||||
Usage:
|
||||
|
||||
%if %mf_mval(maynotexist)=itdid %then %do;
|
||||
@@ -15,4 +16,4 @@
|
||||
%if %symexist(&var) %then %do;
|
||||
%superq(&var)
|
||||
%end;
|
||||
%mend;
|
||||
%mend mf_mval;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
%put Number of observations=%mf_nobs(sashelp.class);
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getattrn.sas
|
||||
|
||||
@param libds library.dataset
|
||||
@@ -23,4 +23,4 @@
|
||||
%macro mf_nobs(libds
|
||||
)/*/STORE SOURCE*/;
|
||||
%mf_getattrn(&libds,NLOBS)
|
||||
%mend;
|
||||
%mend mf_nobs;
|
||||
50
base/mf_trimstr.sas
Normal file
50
base/mf_trimstr.sas
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
@file mf_trimstr.sas
|
||||
@brief Removes character(s) from the end, if they exist
|
||||
@details If the designated characters exist at the end of the string, they
|
||||
are removed
|
||||
|
||||
%put %mf_trimstr(/blah/,/); * /blah;
|
||||
%put %mf_trimstr(/blah/,h); * /blah/;
|
||||
%put %mf_trimstr(/blah/,h/);* /bla;
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
|
||||
|
||||
@param basestr The string to be modified
|
||||
@param trimstr The string to be removed from the end of `basestr`, if it
|
||||
exists
|
||||
|
||||
@return output returns result with the value of `trimstr` removed from the end
|
||||
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mf_trimstr(basestr,trimstr);
|
||||
%local baselen trimlen trimval;
|
||||
|
||||
/* return if basestr is shorter than trimstr (or 0) */
|
||||
%let baselen=%length(%superq(basestr));
|
||||
%let trimlen=%length(%superq(trimstr));
|
||||
%if &baselen < &trimlen or &baselen=0 %then %return;
|
||||
|
||||
/* obtain the characters from the end of basestr */
|
||||
%let trimval=%qsubstr(%superq(basestr)
|
||||
,%length(%superq(basestr))-&trimlen+1
|
||||
,&trimlen);
|
||||
|
||||
/* compare and if matching, chop it off! */
|
||||
%if %superq(basestr)=%superq(trimstr) %then %do;
|
||||
%return;
|
||||
%end;
|
||||
%else %if %superq(trimval)=%superq(trimstr) %then %do;
|
||||
%qsubstr(%superq(basestr),1,%length(%superq(basestr))-&trimlen)
|
||||
%end;
|
||||
%else %do;
|
||||
&basestr
|
||||
%end;
|
||||
|
||||
%mend mf_trimstr;
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
@file
|
||||
@brief Creates a Unique ID based on system time in a friendly format
|
||||
@brief Creates a unique ID based on system time in friendly format
|
||||
@details format = YYYYMMDD_HHMMSSmmm_<sysjobid>_<3randomDigits>
|
||||
|
||||
%put %mf_uid();
|
||||
|
||||
@version 9.2
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
@@ -14,8 +14,8 @@
|
||||
)/*/STORE SOURCE*/;
|
||||
%local today now;
|
||||
%let today=%sysfunc(today(),yymmddn8.);
|
||||
%let now=%sysfunc(compress(%sysfunc(time(),time12.3),:.));
|
||||
%let now=%sysfunc(compress(%sysfunc(time(),tod12.3),:.));
|
||||
|
||||
&today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL)
|
||||
|
||||
%mend;
|
||||
%mend mf_uid;
|
||||
@@ -11,7 +11,7 @@
|
||||
Returns:
|
||||
> 1
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_abort.sas
|
||||
|
||||
@param verifyvars space separated list of macro variable names
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
|
||||
%macro mf_verifymacvars(
|
||||
verifyVars /* list of macro variable NAMES */
|
||||
verifyVars /* list of macro variable NAMES */
|
||||
,makeUpcase=NO /* set to YES to make all the variable VALUES uppercase */
|
||||
,mAbort=SOFT
|
||||
)/*/STORE SOURCE*/;
|
||||
@@ -62,4 +62,4 @@
|
||||
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
|
||||
%exit_success:
|
||||
|
||||
%mend;
|
||||
%mend mf_verifymacvars;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Usage:
|
||||
|
||||
%let x= %mf_wordsInStr1ButNotStr2(
|
||||
Str1=blah sss blaaah brah bram boo
|
||||
Str1=blah sss blaaah brah bram boo
|
||||
,Str2= blah blaaah brah ssss
|
||||
);
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
**/
|
||||
|
||||
%macro mf_wordsInStr1ButNotStr2(
|
||||
Str1= /* string containing words to extract */
|
||||
,Str2= /* used to compare with the extract string */
|
||||
Str1= /* string containing words to extract */
|
||||
,Str2= /* used to compare with the extract string */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local count_base count_extr i i2 extr_word base_word match outvar;
|
||||
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
|
||||
%put WARNING: empty string provided!;
|
||||
%put %str(WARN)ING: empty string provided!;
|
||||
%put base string (str1)= &str1;
|
||||
%put compare string (str2) = &str2;
|
||||
%return;
|
||||
@@ -50,5 +50,5 @@
|
||||
|
||||
&outvar
|
||||
|
||||
%mend;
|
||||
%mend mf_wordsInStr1ButNotStr2;
|
||||
|
||||
|
||||
@@ -4,7 +4,24 @@
|
||||
@details Configures an abort mechanism according to site specific policies or
|
||||
the particulars of an environment. For instance, can stream custom
|
||||
results back to the client in an STP Web App context, or completely stop
|
||||
in the case of a batch run.
|
||||
in the case of a batch run. For STP sessions
|
||||
|
||||
The method used varies according to the context. Important points:
|
||||
|
||||
@li should not use endsas or abort cancel in 9.4m3 environments as this can
|
||||
cause hung multibridge sessions and result in a frozen STP server
|
||||
@li should not use endsas in viya 3.5 as this destroys the session and cannot
|
||||
fetch results (although both mv_getjoblog.sas and the @sasjs/adapter will
|
||||
recognise this and fetch the log of the parent session instead)
|
||||
@li STP environments must finish cleanly to avoid the log being sent to
|
||||
_webout. To assist with this, we also run stpsrvset('program error', 0)
|
||||
and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro
|
||||
but don't close it! This provides a graceful abort, EXCEPT when called
|
||||
called within a %include within a macro (and that macro contains additional
|
||||
logic). See mp_abort.test.nofix.sas for the example case.
|
||||
If you know of another way to gracefully abort a 9.4m3 STP session, we'd
|
||||
love to hear about it!
|
||||
|
||||
|
||||
@param mac= to contain the name of the calling macro
|
||||
@param msg= message to be returned
|
||||
@@ -12,11 +29,14 @@
|
||||
|
||||
@version 9.4M3
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%global sysprocessmode sysprocessname;
|
||||
|
||||
%if not(%eval(%unquote(&iftrue))) %then %return;
|
||||
|
||||
%put NOTE: /// mp_abort macro executing //;
|
||||
@@ -24,9 +44,7 @@
|
||||
%put NOTE - &msg;
|
||||
|
||||
/* Stored Process Server web app context */
|
||||
%if %symexist(_metaperson)
|
||||
or (%symexist(SYSPROCESSNAME) and "&SYSPROCESSNAME"="Compute Server" )
|
||||
%then %do;
|
||||
%if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do;
|
||||
options obs=max replace nosyntaxcheck mprint;
|
||||
/* extract log errs / warns, if exist */
|
||||
%local logloc logline;
|
||||
@@ -41,7 +59,10 @@
|
||||
input; putlog _infile_;
|
||||
i=1;
|
||||
retain logonce 0;
|
||||
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
|
||||
if (
|
||||
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
|
||||
) and logonce=0 then
|
||||
do;
|
||||
call symputx('logline',_n_);
|
||||
logonce+1;
|
||||
end;
|
||||
@@ -53,7 +74,7 @@
|
||||
input;
|
||||
i=1;
|
||||
stoploop=0;
|
||||
if _n_ ge &logline-5 and stoploop=0 then do until (i>12);
|
||||
if _n_ ge &logline-15 and stoploop=0 then do until (i>22);
|
||||
call symputx('logmsg',catx('\n',symget('logmsg'),_infile_));
|
||||
input;
|
||||
i+1;
|
||||
@@ -65,13 +86,20 @@
|
||||
%end;
|
||||
|
||||
%if %symexist(SYS_JES_JOB_URI) %then %do;
|
||||
/* refer web service output to file service in one hit */
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json";
|
||||
/* setup webout */
|
||||
OPTIONS NOBOMFILE;
|
||||
%if "X&SYS_JES_JOB_URI.X"="XX" %then %do;
|
||||
filename _webout temp lrecl=999999 mod;
|
||||
%end;
|
||||
%else %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
|
||||
name="_webout.json" lrecl=999999 mod;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
/* send response in SASjs JSON format */
|
||||
data _null_;
|
||||
file _webout mod lrecl=32000;
|
||||
file _webout mod lrecl=32000 encoding='utf-8';
|
||||
length msg $32767 debug $8;
|
||||
sasdatetime=datetime();
|
||||
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
|
||||
@@ -105,36 +133,68 @@
|
||||
put ",""SYSCC"" : ""&syscc"" ";
|
||||
put ",""SYSERRORTEXT"" : ""&syserrortext"" ";
|
||||
put ",""SYSJOBID"" : ""&sysjobid"" ";
|
||||
sysvlong=quote(trim(symget('sysvlong')));
|
||||
put ',"SYSVLONG" : ' sysvlong;
|
||||
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" ";
|
||||
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
|
||||
put "}" @;
|
||||
if debug ge '"131"' then put '>>weboutEND<<';
|
||||
run;
|
||||
|
||||
%let syscc=0;
|
||||
%if %symexist(_metaport) %then %do;
|
||||
%put _all_;
|
||||
|
||||
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
|
||||
data _null_;
|
||||
if symexist('sysprocessmode')
|
||||
then if symget("sysprocessmode")="SAS Stored Process Server"
|
||||
then rc=stpsrvset('program error', 0);
|
||||
putlog 'stpsrvset program error and syscc';
|
||||
rc=stpsrvset('program error', 0);
|
||||
call symputx("syscc",0,"g");
|
||||
run;
|
||||
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do;
|
||||
%put NOTE: Ending SAS session due to:;
|
||||
%put NOTE- &msg;
|
||||
endsas;
|
||||
%end;
|
||||
%end;
|
||||
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
|
||||
/* endsas kills the session making it harder to fetch results */
|
||||
data _null_;
|
||||
syswarningtext=symget('syswarningtext');
|
||||
syserrortext=symget('syserrortext');
|
||||
abort_msg=symget('msg');
|
||||
syscc=symget('syscc');
|
||||
sysuserid=symget('sysuserid');
|
||||
iftrue=symget('iftrue');
|
||||
put (_all_)(/=);
|
||||
call symputx('syscc',0);
|
||||
abort cancel nolist;
|
||||
run;
|
||||
%end;
|
||||
/**
|
||||
* endsas is reliable but kills some deployments.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
*/
|
||||
%put _all_;
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip(); %macro skippy();';
|
||||
run;
|
||||
%inc skip;
|
||||
%else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
|
||||
/**
|
||||
* endsas kills 9.4m3 deployments by orphaning multibridges.
|
||||
* Abort variants are ungraceful (non zero return code)
|
||||
* This approach lets SAS run silently until the end :-)
|
||||
* Caution - fails when called within a %include within a macro
|
||||
* See tests/mp_abort.test.1 for an example case.
|
||||
*/
|
||||
filename skip temp;
|
||||
data _null_;
|
||||
file skip;
|
||||
put '%macro skip();';
|
||||
comment '%mend skip; -> fix lint ';
|
||||
put '%macro skippy();';
|
||||
comment '%mend skippy; -> fix lint ';
|
||||
run;
|
||||
%inc skip;
|
||||
%end;
|
||||
%else %do;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
%put _all_;
|
||||
%abort cancel;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mp_abort;
|
||||
|
||||
/** @endcond */
|
||||
56
base/mp_assert.sas
Normal file
56
base/mp_assert.sas
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
@file
|
||||
@brief Generic assertion
|
||||
@details Useful in the context of writing sasjs tests. The results of the
|
||||
test are _appended_ to the &outds. table.
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_assert(iftrue=(1=1),
|
||||
desc=Obviously true
|
||||
)
|
||||
|
||||
%mp_assert(iftrue=(1=0),
|
||||
desc=Will fail
|
||||
)
|
||||
|
||||
@param [in] iftrue= (1=1) A condition where, if true, the test is a PASS.
|
||||
Else, the test is a fail.
|
||||
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||
results. If it does not exist, it will be created, with the following format:
|
||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|Column &inds contained ALL columns|
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_assert(iftrue=(1=1),
|
||||
desc=0,
|
||||
outds=work.test_results
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
data ;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
test_comments="&sysmacroname: Test result of "!!symget('iftrue');
|
||||
%if %eval(%unquote(&iftrue)) %then %do;
|
||||
test_result='PASS';
|
||||
%end;
|
||||
%else %do;
|
||||
test_result='FAIL';
|
||||
%end;
|
||||
run;
|
||||
|
||||
%local ds ;
|
||||
%let ds=&syslast;
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
|
||||
%mend mp_assert;
|
||||
145
base/mp_assertcols.sas
Normal file
145
base/mp_assertcols.sas
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
@file
|
||||
@brief Asserts the existence (or not) of columns
|
||||
@details Useful in the context of writing sasjs tests. The results of the
|
||||
test are _appended_ to the &outds. table.
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_assertcols(sashelp.class,
|
||||
cols=name age sex,
|
||||
test=ALL,
|
||||
desc=check all columns exist
|
||||
)
|
||||
|
||||
%mp_assertcols(sashelp.class,
|
||||
cols=a b c,
|
||||
test=NONE
|
||||
)
|
||||
|
||||
%mp_assertcols(sashelp.class,
|
||||
cols=age depth,
|
||||
test=ANY
|
||||
)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_existvarlist.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_wordsinstr1butnotstr2.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
|
||||
@param [in] inds The input library.dataset to test for values
|
||||
@param [in] cols= The list of columns to check for
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@param [in] test= (ALL) The test to apply. Valid values are:
|
||||
@li ALL - Test is a PASS if ALL columns exist in &inds
|
||||
@li ANY - Test is a PASS if ANY of the columns exist in &inds
|
||||
@li NONE - Test is a PASS if NONE of the columns exist in &inds
|
||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||
results. If it does not exist, it will be created, with the following format:
|
||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|Column &inds contained ALL columns|
|
||||
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_assertdsobs.sas
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assertdsobs.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_assertcols(inds,
|
||||
cols=0,
|
||||
test=ALL,
|
||||
desc=0,
|
||||
outds=work.test_results
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc - on macro entry)
|
||||
)
|
||||
|
||||
%local lib ds ;
|
||||
%let lib=%scan(&inds,1,%str(.));
|
||||
%let ds=%scan(&inds,2,%str(.));
|
||||
%let cols=%upcase(&cols);
|
||||
|
||||
%mp_abort(iftrue= (%mf_existds(&lib..&ds)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&lib..&ds not found!)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue= (&cols=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(No cols provided)
|
||||
)
|
||||
|
||||
|
||||
%let test=%upcase(&test);
|
||||
|
||||
%if &test ne ANY and &test ne ALL and &test ne NONE %then %do;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Invalid test - &test)
|
||||
)
|
||||
%end;
|
||||
|
||||
/**
|
||||
* now do the actual test!
|
||||
*/
|
||||
%local result;
|
||||
%if %mf_existVarList(&inds,&cols)=1 %then %let result=ALL;
|
||||
%else %do;
|
||||
%local targetcols compare;
|
||||
%let targetcols=%upcase(%mf_getvarlist(&inds));
|
||||
%let compare=%mf_wordsinstr1butnotstr2(
|
||||
Str1=&cols,
|
||||
Str2=&targetcols
|
||||
);
|
||||
%if %cmpres(&compare)=%cmpres(&cols) %then %let result=NONE;
|
||||
%else %let result=SOME;
|
||||
%end;
|
||||
|
||||
data;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
if test_description='0'
|
||||
then test_description="Testing &inds for existence of &test of: &cols";
|
||||
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: &inds has &result columns ";
|
||||
%if &test=ALL %then %do;
|
||||
%if &result=ALL %then %do;
|
||||
test_result='PASS';
|
||||
%end;
|
||||
%end;
|
||||
%else %if &test=ANY %then %do;
|
||||
%if &result=SOME %then %do;
|
||||
test_result='PASS';
|
||||
%end;
|
||||
%end;
|
||||
%else %if &test=NONE %then %do;
|
||||
%if &result=NONE %then %do;
|
||||
test_result='PASS';
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||
%end;
|
||||
run;
|
||||
|
||||
%local ds;
|
||||
%let ds=&syslast;
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
|
||||
%mend mp_assertcols;
|
||||
147
base/mp_assertcolvals.sas
Normal file
147
base/mp_assertcolvals.sas
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
@file
|
||||
@brief Asserts the values in a column
|
||||
@details Useful in the context of writing sasjs tests. The results of the
|
||||
test are _appended_ to the &outds. table.
|
||||
|
||||
Example usage:
|
||||
|
||||
data work.checkds;
|
||||
do checkval='Jane','James','Jill';
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%mp_assertcolvals(sashelp.class.name,
|
||||
checkvals=work.checkds.checkval,
|
||||
desc=At least one value has a match,
|
||||
test=ANYVAL
|
||||
)
|
||||
|
||||
data work.check;
|
||||
do val='M','F';
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%mp_assertcolvals(sashelp.class.sex,
|
||||
checkvals=work.check.val,
|
||||
desc=All values have a match,
|
||||
test=ALLVALS
|
||||
)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
|
||||
@param [in] indscol The input library.dataset.column to test for values
|
||||
@param [in] checkvals= A library.dataset.column value containing a UNIQUE
|
||||
list of values to be compared against the source (indscol).
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@param [in] test= (ALLVALS) The test to apply. Valid values are:
|
||||
@li ALLVALS - Test is a PASS if ALL values have a match in checkvals
|
||||
@li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
|
||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||
results. If it does not exist, it will be created, with the following format:
|
||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|Column &indscol contained ALL target vals|
|
||||
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_assertdsobs.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_assertcolvals(indscol,
|
||||
checkvals=0,
|
||||
test=ALLVALS,
|
||||
desc=mp_assertcolvals - no desc provided,
|
||||
outds=work.test_results
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc - on macro entry)
|
||||
)
|
||||
|
||||
%local lib ds col clib cds ccol nobs;
|
||||
%let lib=%scan(&indscol,1,%str(.));
|
||||
%let ds=%scan(&indscol,2,%str(.));
|
||||
%let col=%scan(&indscol,3,%str(.));
|
||||
%mp_abort(iftrue= (%mf_existds(&lib..&ds)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&lib..&ds not found!)
|
||||
)
|
||||
|
||||
%mp_abort(iftrue= (&checkvals=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Set CHECKVALS to a library.dataset.column containing check vals)
|
||||
)
|
||||
%let clib=%scan(&checkvals,1,%str(.));
|
||||
%let cds=%scan(&checkvals,2,%str(.));
|
||||
%let ccol=%scan(&checkvals,3,%str(.));
|
||||
%mp_abort(iftrue= (%mf_existds(&clib..&cds)=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&clib..&cds not found!)
|
||||
)
|
||||
%let nobs=%mf_nobs(&clib..&cds);
|
||||
%mp_abort(iftrue= (&nobs=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(&clib..&cds is empty!)
|
||||
)
|
||||
|
||||
%let test=%upcase(&test);
|
||||
|
||||
%if &test ne ALLVALS and &test ne ANYVAL %then %do;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Invalid test - &test)
|
||||
)
|
||||
%end;
|
||||
|
||||
%local result orig;
|
||||
%let result=-1;
|
||||
%let orig=-1;
|
||||
proc sql noprint;
|
||||
select count(*) into: result
|
||||
from &lib..&ds
|
||||
where &col not in (
|
||||
select &ccol from &clib..&cds
|
||||
);
|
||||
select count(*) into: orig from &lib..&ds;
|
||||
quit;
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc after macro query)
|
||||
)
|
||||
|
||||
data;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
|
||||
!!"not in &clib..&cds..&ccol ";
|
||||
%if &test=ANYVAL %then %do;
|
||||
if &result < &orig then test_result='PASS';
|
||||
%end;
|
||||
%else %if &test=ALLVALS %then %do;
|
||||
if &result=0 then test_result='PASS';
|
||||
%end;
|
||||
%else %do;
|
||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||
%end;
|
||||
run;
|
||||
|
||||
%local ds;
|
||||
%let ds=&syslast;
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
|
||||
%mend mp_assertcolvals;
|
||||
118
base/mp_assertdsobs.sas
Normal file
118
base/mp_assertdsobs.sas
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
@file
|
||||
@brief Asserts the number of observations in a dataset
|
||||
@details Useful in the context of writing sasjs tests. The results of the
|
||||
test are _appended_ to the &outds. table.
|
||||
|
||||
Example usage:
|
||||
|
||||
%mp_assertdsobs(sashelp.class) %* tests if any observations are present;
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
|
||||
@param [in] inds input dataset to test for presence of observations
|
||||
@param [in] desc= (Testing observations) The user provided test description
|
||||
@param [in] test= (HASOBS) The test to apply. Valid values are:
|
||||
@li HASOBS - Test is a PASS if the input dataset has any observations
|
||||
@li EMPTY - Test is a PASS if input dataset is empty
|
||||
@li EQUALS [integer] - Test passes if row count matches the provided integer
|
||||
@LI ATLEAST [integer] - Test passes if row count is more than or equal to
|
||||
the provided integer
|
||||
@LI ATMOST [integer] - Test passes if row count is less than or equal to
|
||||
the provided integer
|
||||
@param [out] outds= (work.test_results) The output dataset to contain the
|
||||
results. If it does not exist, it will be created, with the following format:
|
||||
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|
||||
|---|---|---|
|
||||
|User Provided description|PASS|Dataset &inds has XX obs|
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_assertcolvals.sas
|
||||
@li mp_assert.sas
|
||||
@li mp_assertcols.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_assertdsobs(inds,
|
||||
test=HASOBS,
|
||||
desc=Testing observations,
|
||||
outds=work.test_results
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local nobs;
|
||||
%let nobs=%mf_nobs(&inds);
|
||||
%let test=%upcase(&test);
|
||||
|
||||
%if %substr(&test.xxxxx,1,6)=EQUALS %then %do;
|
||||
%let val=%scan(&test,2,%str( ));
|
||||
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid test - &test, expected EQUALS [integer])
|
||||
)
|
||||
%let test=EQUALS;
|
||||
%end;
|
||||
%else %if %substr(&test.xxxxxxx,1,7)=ATLEAST %then %do;
|
||||
%let val=%scan(&test,2,%str( ));
|
||||
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid test - &test, expected ATLEAST [integer])
|
||||
)
|
||||
%let test=ATLEAST;
|
||||
%end;
|
||||
%else %if %substr(&test.xxxxxxx,1,7)=ATMOST %then %do;
|
||||
%let val=%scan(&test,2,%str( ));
|
||||
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid test - &test, expected ATMOST [integer])
|
||||
)
|
||||
%let test=ATMOST;
|
||||
%end;
|
||||
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Invalid test - &test)
|
||||
)
|
||||
%end;
|
||||
|
||||
data;
|
||||
length test_description $256 test_result $4 test_comments $256;
|
||||
test_description=symget('desc');
|
||||
test_result='FAIL';
|
||||
test_comments="&sysmacroname: Dataset &inds has &nobs observations.";
|
||||
test_comments=test_comments!!" Test was "!!symget('test');
|
||||
%if &test=HASOBS %then %do;
|
||||
if &nobs>0 then test_result='PASS';
|
||||
%end;
|
||||
%else %if &test=EMPTY %then %do;
|
||||
if &nobs=0 then test_result='PASS';
|
||||
%end;
|
||||
%else %if &test=EQUALS %then %do;
|
||||
if &nobs=&val then test_result='PASS';
|
||||
%end;
|
||||
%else %if &test=ATLEAST %then %do;
|
||||
if &nobs ge &val then test_result='PASS';
|
||||
%end;
|
||||
%else %if &test=ATMOST %then %do;
|
||||
if &nobs le &val then test_result='PASS';
|
||||
%end;
|
||||
%else %do;
|
||||
test_comments="&sysmacroname: Unsatisfied test condition - &test";
|
||||
%end;
|
||||
run;
|
||||
|
||||
%local ds;
|
||||
%let ds=&syslast;
|
||||
|
||||
proc append base=&outds data=&ds;
|
||||
run;
|
||||
|
||||
proc sql;
|
||||
drop table &ds;
|
||||
|
||||
%mend mp_assertdsobs;
|
||||
123
base/mp_base64copy.sas
Normal file
123
base/mp_base64copy.sas
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
@file
|
||||
@brief Convert a file to/from base64 format
|
||||
@details Creates a new version of a file either encoded or decoded using
|
||||
Base64. Inspired by this post by Michael Dixon:
|
||||
https://support.selerity.com.au/hc/en-us/articles/223345708-Tip-SAS-and-Base64
|
||||
|
||||
Usage:
|
||||
|
||||
filename tmp temp;
|
||||
data _null_;
|
||||
file tmp;
|
||||
put 'base ik ally';
|
||||
run;
|
||||
%mp_base64copy(inref=tmp, outref=myref, action=ENCODE)
|
||||
|
||||
data _null_;
|
||||
infile myref;
|
||||
input;
|
||||
put _infile_;
|
||||
run;
|
||||
|
||||
%mp_base64copy(inref=myref, outref=mynewref, action=DECODE)
|
||||
|
||||
data _null_;
|
||||
infile mynewref;
|
||||
input;
|
||||
put _infile_;
|
||||
run;
|
||||
|
||||
@param [in] inref= Fileref of the input file (should exist)
|
||||
@param [out] outref= Output fileref. If it does not exist, it is created.
|
||||
@param [in] action= (ENCODE) The action to take. Valid values:
|
||||
@li ENCODE - Convert the file to base64 format
|
||||
@li DECODE - Decode the file from base64 format
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe, source: https://github.com/sasjs/core
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_base64copy(
|
||||
inref=0,
|
||||
outref=0,
|
||||
action=ENCODE
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%let inref=%upcase(&inref);
|
||||
%let outref=%upcase(&outref);
|
||||
%let action=%upcase(&action);
|
||||
%local infound outfound;
|
||||
%let infound=0;
|
||||
%let outfound=0;
|
||||
data _null_;
|
||||
set sashelp.vextfl(where=(fileref="&inref" or fileref="&outref"));
|
||||
if fileref="&inref" then call symputx('infound',1,'l');
|
||||
if fileref="&outref" then call symputx('outfound',1,'l');
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue= (&infound=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(INREF &inref NOT FOUND!)
|
||||
)
|
||||
%mp_abort(iftrue= (&outref=0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(OUTREF NOT PROVIDED!)
|
||||
)
|
||||
%mp_abort(iftrue= (&action ne ENCODE and &action ne DECODE)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(Invalid action! Should be ENCODE OR DECODE)
|
||||
)
|
||||
|
||||
%if &outfound=0 %then %do;
|
||||
filename &outref temp lrecl=2097088;
|
||||
%%end;
|
||||
|
||||
%if &action=ENCODE %then %do;
|
||||
data _null_;
|
||||
length b64 $ 76 line $ 57;
|
||||
retain line "";
|
||||
infile &inref recfm=F lrecl= 1 end=eof;
|
||||
input @1 stream $char1.;
|
||||
file &outref lrecl=76;
|
||||
substr(line,(_N_-(CEIL(_N_/57)-1)*57),1) = byte(rank(stream));
|
||||
if mod(_N_,57)=0 or EOF then do;
|
||||
if eof then b64=put(trim(line),$base64X76.);
|
||||
else b64=put(line, $base64X76.);
|
||||
put b64;
|
||||
line="";
|
||||
end;
|
||||
run;
|
||||
%end;
|
||||
%else %if &action=DECODE %then %do;
|
||||
data _null_;
|
||||
length filein 8 fileout 8;
|
||||
filein = fopen("&inref",'I',4,'B');
|
||||
fileout = fopen("&outref",'O',3,'B');
|
||||
char= '20'x;
|
||||
do while(fread(filein)=0);
|
||||
raw="1234";
|
||||
do i=1 to 4;
|
||||
rc=fget(filein,char,1);
|
||||
substr(raw,i,1)=char;
|
||||
end;
|
||||
val="123";
|
||||
val=input(raw,$base64X4.);
|
||||
do i=1 to 3;
|
||||
length byte $1;
|
||||
byte=byte(rank(substr(val,i,1)));
|
||||
rc = fput(fileout, byte);
|
||||
end;
|
||||
rc =fwrite(fileout);
|
||||
end;
|
||||
rc = fclose(filein);
|
||||
rc = fclose(fileout);
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend mp_base64copy;
|
||||
@@ -2,9 +2,10 @@
|
||||
@file
|
||||
@brief Copy any file using binary input / output streams
|
||||
@details Reads in a file byte by byte and writes it back out. Is an
|
||||
os-independent method to copy files. In case of naming collision, the
|
||||
default filerefs can be modified.
|
||||
Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
||||
os-independent method to copy files. In case of naming collision, the
|
||||
default filerefs can be modified.
|
||||
Based on:
|
||||
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
|
||||
|
||||
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)
|
||||
|
||||
@@ -19,12 +20,12 @@
|
||||
**/
|
||||
|
||||
%macro mp_binarycopy(
|
||||
inloc= /* full path and filename of the object to be copied */
|
||||
inloc= /* full path and filename of the object to be copied */
|
||||
,outloc= /* full path and filename of object to be created */
|
||||
,inref=____in /* override default to use own filerefs */
|
||||
,outref=____out /* override default to use own filerefs */
|
||||
)/*/STORE SOURCE*/;
|
||||
/* these IN and OUT filerefs can point to anything */
|
||||
/* these IN and OUT filerefs can point to anything */
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref &inloc lrecl=1048576 ;
|
||||
%end;
|
||||
@@ -32,24 +33,24 @@
|
||||
filename &outref &outloc lrecl=1048576 ;
|
||||
%end;
|
||||
|
||||
/* copy the file byte-for-byte */
|
||||
data _null_;
|
||||
length filein 8 fileid 8;
|
||||
filein = fopen("&inref",'I',1,'B');
|
||||
fileid = fopen("&outref",'O',1,'B');
|
||||
rec = '20'x;
|
||||
do while(fread(filein)=0);
|
||||
rc = fget(filein,rec,1);
|
||||
rc = fput(fileid, rec);
|
||||
rc =fwrite(fileid);
|
||||
end;
|
||||
rc = fclose(filein);
|
||||
rc = fclose(fileid);
|
||||
run;
|
||||
/* copy the file byte-for-byte */
|
||||
data _null_;
|
||||
length filein 8 fileid 8;
|
||||
filein = fopen("&inref",'I',1,'B');
|
||||
fileid = fopen("&outref",'O',1,'B');
|
||||
rec = '20'x;
|
||||
do while(fread(filein)=0);
|
||||
rc = fget(filein,rec,1);
|
||||
rc = fput(fileid, rec);
|
||||
rc =fwrite(fileid);
|
||||
end;
|
||||
rc = fclose(filein);
|
||||
rc = fclose(fileid);
|
||||
run;
|
||||
%if &inref = ____in %then %do;
|
||||
filename &inref clear;
|
||||
%end;
|
||||
%if &outref=____out %then %do;
|
||||
filename &outref clear;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mp_binarycopy;
|
||||
@@ -2,12 +2,13 @@
|
||||
@file mp_cleancsv.sas
|
||||
@brief Fixes embedded cr / lf / crlf in CSV
|
||||
@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
|
||||
to process using SAS defaults.
|
||||
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.
|
||||
saved by excel). When the termstr is ALSO lf or crlf that can be tricky
|
||||
to process using SAS defaults.
|
||||
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.
|
||||
|
||||
usage:
|
||||
|
||||
fileref mycsv "/path/your/csv";
|
||||
%mp_cleancsv(in=mycsv,out=/path/new.csv)
|
||||
|
||||
@@ -17,11 +18,12 @@
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@cond
|
||||
**/
|
||||
|
||||
%macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x);
|
||||
%if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do;
|
||||
%put %str(ERR)OR: Please provide valid input (&in) and output (&out) locations;
|
||||
%put %str(ERR)OR: Please provide valid input (&in) & output (&out) locations;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
@@ -30,9 +32,9 @@
|
||||
%if %index(&out,.) %then %let out="&out";
|
||||
|
||||
/**
|
||||
* convert all cr and crlf within quotes to lf
|
||||
* convert all other cr or lf to crlf
|
||||
*/
|
||||
* convert all cr and crlf within quotes to lf
|
||||
* convert all other cr or lf to crlf
|
||||
*/
|
||||
data _null_;
|
||||
infile &in recfm=n ;
|
||||
file &out recfm=n;
|
||||
@@ -65,4 +67,5 @@
|
||||
else put inchar $char1.;
|
||||
end;
|
||||
run;
|
||||
%mend;
|
||||
%mend mp_cleancsv;
|
||||
/** @endcond */
|
||||
@@ -13,7 +13,7 @@
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||
%mp_createconstraints(inds=work.constraints,outds=created,execute=YES)
|
||||
@@ -22,7 +22,7 @@
|
||||
@param outds= a table containing the create statements (create_statement column)
|
||||
@param execute= `YES|NO` - default is NO. To actually create, use YES.
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -48,7 +48,7 @@ data &outds;
|
||||
else type=constraint_type;
|
||||
create_statement=catx(" ","alter table",libref,".",table_name
|
||||
,"add constraint",constraint_name,type,"(");
|
||||
if last.constraint_name then
|
||||
if last.constraint_name then
|
||||
create_statement=cats(create_statement,column_name,");");
|
||||
else create_statement=cats(create_statement,column_name,",");
|
||||
if "&execute"="YES" then call execute(create_statement);
|
||||
@@ -64,4 +64,4 @@ data &outds;
|
||||
output;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
%mend mp_createconstraints;
|
||||
@@ -14,7 +14,7 @@ Usage:
|
||||
filename ft15f001 temp;
|
||||
parmcards4;
|
||||
%* fetch any data from frontend ;
|
||||
%webout(FETCH)
|
||||
%webout(FETCH)
|
||||
data example1 example2;
|
||||
set sashelp.class;
|
||||
run;
|
||||
@@ -26,7 +26,7 @@ Usage:
|
||||
;;;;
|
||||
%mp_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getplatform.sas
|
||||
@li mm_createwebservice.sas
|
||||
@li mv_createwebservice.sas
|
||||
@@ -80,4 +80,4 @@ Usage:
|
||||
)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_createwebservice;
|
||||
|
||||
144
base/mp_csv2ds.sas
Normal file
144
base/mp_csv2ds.sas
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
@file mp_csv2ds.sas
|
||||
@brief Efficient import of arbitrary CSV using a dataset as template
|
||||
@details Used to import relevant columns from a large CSV using
|
||||
a dataset to provide the types and lengths. Assumes that a header
|
||||
row is provided, and datarows start on line 2. Extra columns in
|
||||
both the CSV and base dataset are ignored.
|
||||
|
||||
Usage:
|
||||
|
||||
filename mycsv temp;
|
||||
data _null_;
|
||||
file mycsv;
|
||||
put 'name,age,nickname';
|
||||
put 'John,48,Jonny';
|
||||
put 'Jennifer,23,Jen';
|
||||
run;
|
||||
|
||||
%mp_csv2ds(inref=mycsv,outds=myds,baseds=sashelp.class)
|
||||
|
||||
|
||||
@param inref= fileref to the CSV
|
||||
@param outds= output ds (lib.ds format)
|
||||
@param view= Set to YES or NO to determine whether the output should be
|
||||
a view or not. Default is NO (not a view).
|
||||
@param baseds= Template dataset on which to create the input statement.
|
||||
Is used to determine types, lengths, and any informats.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_existds.sas
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_csv2ds(inref=0,outds=0,baseds=0,view=NO);
|
||||
|
||||
%mp_abort(iftrue=( &inref=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the INREF variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( %superq(outds)=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the OUTDS variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( &baseds=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( &baseds=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS variable must be provided)
|
||||
)
|
||||
%mp_abort(iftrue=( %mf_existds(&baseds)=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(the BASEDS dataset (&baseds) needs to be assigned, and to exist)
|
||||
)
|
||||
|
||||
/* count rows */
|
||||
%local hasheader; %let hasheader=0;
|
||||
data _null_;
|
||||
if _N_ > 1 then do;
|
||||
call symputx('hasheader',1,'l');
|
||||
stop;
|
||||
end;
|
||||
infile &inref;
|
||||
input;
|
||||
run;
|
||||
%mp_abort(iftrue=( &hasheader=0 )
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(No header row in &inref)
|
||||
)
|
||||
|
||||
/* get the variables in the CSV */
|
||||
data _data_;
|
||||
infile &inref;
|
||||
input;
|
||||
length name $32;
|
||||
do i=1 to countc(_infile_,',')+1;
|
||||
name=upcase(scan(_infile_,i,','));
|
||||
output;
|
||||
end;
|
||||
stop;
|
||||
run;
|
||||
%local csv_vars;%let csv_vars=&syslast;
|
||||
|
||||
/* get the variables in the dataset */
|
||||
proc contents noprint data=&baseds
|
||||
out=_data_ (keep=name type length format: informat);
|
||||
run;
|
||||
%local base_vars; %let base_vars=&syslast;
|
||||
|
||||
proc sql undo_policy=none;
|
||||
create table &csv_vars as
|
||||
select a.*
|
||||
,b.type
|
||||
,b.length
|
||||
,b.format
|
||||
,b.formatd
|
||||
,b.formatl
|
||||
,b.informat
|
||||
from &csv_vars a
|
||||
left join &base_vars b
|
||||
on a.name=upcase(b.name)
|
||||
order by i;
|
||||
|
||||
/* prepare the input statement */
|
||||
%local instat dropvars;
|
||||
data _null_;
|
||||
set &syslast end=last;
|
||||
length in dropvars $32767;
|
||||
retain in dropvars;
|
||||
if missing(type) then do;
|
||||
informat='$1.';
|
||||
dropvars=catx(' ',dropvars,name);
|
||||
end;
|
||||
else if missing(informat) then do;
|
||||
if type=1 then informat='best.';
|
||||
else informat=cats('$',length,'.');
|
||||
end;
|
||||
else informat=cats(informat,'.');
|
||||
in=catx(' ',in,name,':',informat);
|
||||
if last then do;
|
||||
call symputx('instat',in,'l');
|
||||
call symputx('dropvars',dropvars,'l');
|
||||
end;
|
||||
run;
|
||||
|
||||
/* import the CSV */
|
||||
data &outds
|
||||
%if %upcase(&view)=YES %then %do;
|
||||
/view=&outds
|
||||
%end;
|
||||
;
|
||||
infile &inref dsd firstobs=2;
|
||||
input &instat;
|
||||
%if %length(&dropvars)>0 %then %do;
|
||||
drop &dropvars;
|
||||
%end;
|
||||
run;
|
||||
|
||||
%mend mp_csv2ds;
|
||||
@@ -13,7 +13,7 @@
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
%mp_deleteconstraints(inds=work.constraints,outds=dropped,execute=YES)
|
||||
|
||||
@@ -49,4 +49,4 @@ data &outds;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
%mend mp_deleteconstraints;
|
||||
@@ -1,17 +1,17 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns all files and subdirectories within a specified parent
|
||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
||||
@details When used with getattrs=NO, is not OS specific (uses dopen / dread).
|
||||
|
||||
If getattrs=YES then the doptname / foptname functions are used to scan all
|
||||
properties - any characters that are not valid in a SAS name (v7) are simply
|
||||
properties - any characters that are not valid in a SAS name (v7) are simply
|
||||
stripped, and the table is transposed so theat each property is a column
|
||||
and there is one file per row. An attempt is made to get all properties
|
||||
and there is one file per row. An attempt is made to get all properties
|
||||
whether a file or folder, but some files/folders cannot be accessed, and so
|
||||
not all properties can / will be populated.
|
||||
|
||||
Credit for the rename approach:
|
||||
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||
https://communities.sas.com/t5/SAS-Programming/SAS-Function-to-convert-string-to-Legal-SAS-Name/m-p/27375/highlight/true#M5003
|
||||
|
||||
|
||||
usage:
|
||||
@@ -20,50 +20,65 @@
|
||||
|
||||
%mp_dirlist(outds=cwdfileprops, getattrs=YES)
|
||||
|
||||
@warning In a Unix environment, the existence of a named pipe will cause this
|
||||
%mp_dirlist(fref=MYFREF)
|
||||
|
||||
@warning In a Unix environment, the existence of a named pipe will cause this
|
||||
macro to hang. Therefore this tool should be used with caution in a SAS 9 web
|
||||
application, as it can use up all available multibridge sessions if requests
|
||||
are resubmitted.
|
||||
If anyone finds a way to positively identify a named pipe using SAS (without
|
||||
If anyone finds a way to positively identify a named pipe using SAS (without
|
||||
X CMD) do please raise an issue!
|
||||
|
||||
|
||||
@param path= for which to return contents
|
||||
@param fref= Provide a DISK engine fileref as an alternative to PATH
|
||||
@param outds= the output dataset to create
|
||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
||||
all attributes for each file / folder.
|
||||
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return
|
||||
all attributes for each file / folder.
|
||||
|
||||
|
||||
@returns outds contains the following variables:
|
||||
- file_or_folder (file / folder)
|
||||
- filepath (path/to/file.name)
|
||||
- filename (just the file name)
|
||||
- ext (.extension)
|
||||
- msg (system message if any issues)
|
||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
||||
- directory (containing folder)
|
||||
- file_or_folder (file / folder)
|
||||
- filepath (path/to/file.name)
|
||||
- filename (just the file name)
|
||||
- ext (.extension)
|
||||
- msg (system message if any issues)
|
||||
- OS SPECIFIC variables, if <code>getattrs=</code> is used.
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_dirlist(path=%sysfunc(pathname(work))
|
||||
, fref=0
|
||||
, outds=work.mp_dirlist
|
||||
, getattrs=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
%let getattrs=%upcase(&getattrs)XX;
|
||||
|
||||
data &outds (compress=no keep=file_or_folder filepath filename ext msg);
|
||||
length filepath $500 fref fref2 $8 file_or_folder $6 filename $80 ext $20 msg $200;
|
||||
rc = filename(fref, "&path");
|
||||
data &outds(compress=no
|
||||
keep=file_or_folder filepath filename ext msg directory
|
||||
);
|
||||
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
|
||||
ext $20 msg $200;
|
||||
%if &fref=0 %then %do;
|
||||
rc = filename(fref, "&path");
|
||||
%end;
|
||||
%else %do;
|
||||
fref="&fref";
|
||||
rc=0;
|
||||
%end;
|
||||
if rc = 0 then do;
|
||||
did = dopen(fref);
|
||||
if did=0 then do;
|
||||
putlog "NOTE: This directory is empty - &path";
|
||||
msg=sysmsg();
|
||||
put _all_;
|
||||
stop;
|
||||
end;
|
||||
rc = filename(fref);
|
||||
did = dopen(fref);
|
||||
directory=dinfo(did,'Directory');
|
||||
if did=0 then do;
|
||||
putlog "NOTE: This directory is empty - " directory;
|
||||
msg=sysmsg();
|
||||
put _all_;
|
||||
stop;
|
||||
end;
|
||||
rc = filename(fref);
|
||||
end;
|
||||
else do;
|
||||
msg=sysmsg();
|
||||
@@ -73,7 +88,8 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg);
|
||||
dnum = dnum(did);
|
||||
do i = 1 to dnum;
|
||||
filename = dread(did, i);
|
||||
rc = filename(fref2, "&path/"!!filename);
|
||||
filepath=cats(directory,'/',filename);
|
||||
rc = filename(fref2,filepath);
|
||||
midd=dopen(fref2);
|
||||
dmsg=sysmsg();
|
||||
if did > 0 then file_or_folder='folder';
|
||||
@@ -82,12 +98,12 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg);
|
||||
fmsg=sysmsg();
|
||||
if midf > 0 then file_or_folder='file';
|
||||
rc=fclose(midf);
|
||||
|
||||
if index(fmsg,'File is in use') or index(dmsg,'is not a directory')
|
||||
|
||||
if index(fmsg,'File is in use') or index(dmsg,'is not a directory')
|
||||
then file_or_folder='file';
|
||||
else if index(fmsg, 'Insufficient authorization') then file_or_folder='file';
|
||||
else if index(fmsg,'Insufficient authorization') then file_or_folder='file';
|
||||
else if file_or_folder='' then file_or_folder='locked';
|
||||
|
||||
|
||||
if file_or_folder='file' then do;
|
||||
ext = prxchange('s/.*\.{1,1}(.*)/$1/', 1, filename);
|
||||
if filename = ext then ext = ' ';
|
||||
@@ -96,7 +112,6 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg);
|
||||
ext='';
|
||||
file_or_folder='folder';
|
||||
end;
|
||||
filepath="&path/"!!filename;
|
||||
output;
|
||||
end;
|
||||
rc = dclose(did);
|
||||
@@ -120,7 +135,7 @@ run;
|
||||
else do i=1 to foptnum(fid);
|
||||
infoname=foptname(fid,i);
|
||||
infoval=finfo(fid,infoname);
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
|
||||
if upcase(sasname) ne 'FILENAME' then output;
|
||||
end;
|
||||
@@ -137,7 +152,7 @@ run;
|
||||
else do i=1 to doptnum(fid);
|
||||
infoname=doptname(fid,i);
|
||||
infoval=dinfo(fid,infoname);
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
sasname=compress(infoname, '_', 'adik');
|
||||
if anydigit(sasname)=1 then sasname=substr(sasname,anyalpha(sasname));
|
||||
if upcase(sasname) ne 'FILENAME' then output;
|
||||
end;
|
||||
@@ -152,4 +167,4 @@ run;
|
||||
by filepath file_or_folder filename ext ;
|
||||
run;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mp_dirlist;
|
||||
@@ -19,7 +19,7 @@
|
||||
**/
|
||||
|
||||
%macro mp_distinctfmtvalues(
|
||||
libds=
|
||||
libds=
|
||||
,var=
|
||||
,outvar=formatted_value
|
||||
,outds=work.mp_distinctfmtvalues
|
||||
@@ -34,7 +34,7 @@
|
||||
create table &outds as
|
||||
select distinct
|
||||
%if &vtype=C & %trim(&fmt)=%str() %then %do;
|
||||
&var
|
||||
&var
|
||||
%end;
|
||||
%else %if &vtype=C %then %do;
|
||||
put(&var,&fmt)
|
||||
@@ -45,6 +45,6 @@
|
||||
%else %do;
|
||||
put(&var,&fmt)
|
||||
%end;
|
||||
as &outvar length=&varlen
|
||||
as &outvar length=&varlen
|
||||
from &libds;
|
||||
%mend;
|
||||
%mend mp_distinctfmtvalues;
|
||||
@@ -1,20 +1,24 @@
|
||||
/**
|
||||
@file
|
||||
@brief Drops tables / views (if they exist) without warnings in the log
|
||||
@details
|
||||
@details Useful for dropping tables when you're not sure they exist, or if
|
||||
you are not sure whether they are a dataset or view. Also efficient for
|
||||
dropping multiple tables / views.
|
||||
|
||||
Example usage:
|
||||
|
||||
proc sql;
|
||||
create table data1 as select * from sashelp.class;
|
||||
create view view2 as select * from sashelp.class;
|
||||
%mp_dropmembers(list=data1 view2)
|
||||
%mp_dropmembers(data1 view2, libref=WORK)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_isblank.sas
|
||||
|
||||
|
||||
@param list space separated list of datasets / views
|
||||
@param libref= can only drop from a single library at a time
|
||||
@param list space separated list of datasets / views, WITHOUT libref
|
||||
@param libref= (WORK) Note - you can only drop from a single library at a time
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -22,7 +26,7 @@
|
||||
**/
|
||||
|
||||
%macro mp_dropmembers(
|
||||
list /* space separated list of datasets / views */
|
||||
list /* space separated list of datasets / views */
|
||||
,libref=WORK /* can only drop from a single library at a time */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
@@ -35,4 +39,4 @@
|
||||
delete &list;
|
||||
delete &list /mtype=view;
|
||||
run;
|
||||
%mend;
|
||||
%mend mp_dropmembers;
|
||||
@@ -3,25 +3,29 @@
|
||||
@brief Create a CARDS file from a SAS dataset.
|
||||
@details Uses dataset attributes to convert all data into datalines.
|
||||
Running the generated file will rebuild the original dataset.
|
||||
usage:
|
||||
Usage:
|
||||
|
||||
%mp_ds2cards(base_ds=sashelp.class
|
||||
, cards_file= "C:\temp\class.sas"
|
||||
, maxobs=5)
|
||||
|
||||
stuff to add
|
||||
- labelling the dataset
|
||||
- explicity setting a unix LF
|
||||
- constraints / indexes etc
|
||||
TODO:
|
||||
- labelling the dataset
|
||||
- explicity setting a unix LF
|
||||
- constraints / indexes etc
|
||||
|
||||
@param base_ds= Should be two level - eg work.blah. This is the table that
|
||||
is converted to a cards file.
|
||||
@param tgt_ds= Table that the generated cards file would create. Optional -
|
||||
if omitted, will be same as BASE_DS.
|
||||
@param cards_file= Location in which to write the (.sas) cards file
|
||||
@param maxobs= to limit output to the first <code>maxobs</code> observations
|
||||
@param showlog= whether to show generated cards file in the SAS log (YES/NO)
|
||||
@param outencoding= provide encoding value for file statement (eg utf-8)
|
||||
@param [in] base_ds= Should be two level - eg work.blah. This is the table
|
||||
that is converted to a cards file.
|
||||
@param [in] tgt_ds= Table that the generated cards file would create.
|
||||
Optional - if omitted, will be same as BASE_DS.
|
||||
@param [out] cards_file= Location in which to write the (.sas) cards file
|
||||
@param [in] maxobs= to limit output to the first <code>maxobs</code>
|
||||
observations
|
||||
@param [in] showlog= whether to show generated cards file in the SAS log
|
||||
(YES/NO)
|
||||
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
|
||||
@param [in] append= If NO then will rebuild the cards file if it already
|
||||
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
|
||||
|
||||
|
||||
@version 9.2
|
||||
@@ -34,18 +38,21 @@
|
||||
,random_sample=NO
|
||||
,showlog=YES
|
||||
,outencoding=
|
||||
,append=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
%local i setds nvars;
|
||||
|
||||
%if not %sysfunc(exist(&base_ds)) %then %do;
|
||||
%put WARNING: &base_ds does not exist;
|
||||
%return;
|
||||
%put %str(WARN)ING: &base_ds does not exist;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if %index(&base_ds,.)=0 %then %let base_ds=WORK.&base_ds;
|
||||
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
|
||||
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
|
||||
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
|
||||
%if ("&append" = "") %then %let append=;
|
||||
%else %let append=mod;
|
||||
|
||||
/* get varcount */
|
||||
%let nvars=0;
|
||||
@@ -54,15 +61,17 @@ select count(*) into: nvars from dictionary.columns
|
||||
where libname="%scan(%upcase(&base_ds),1)"
|
||||
and memname="%scan(%upcase(&base_ds),2)";
|
||||
%if &nvars=0 %then %do;
|
||||
%put WARNING: Dataset &base_ds has no variables! It will not be converted.;
|
||||
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/* get indexes */
|
||||
proc sort data=sashelp.vindex
|
||||
(where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)"))
|
||||
out=_data_;
|
||||
proc sort
|
||||
data=sashelp.vindex(
|
||||
where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
|
||||
and upcase(memname)="%scan(%upcase(&base_ds),2)")
|
||||
)
|
||||
out=_data_;
|
||||
by indxname indxpos;
|
||||
run;
|
||||
|
||||
@@ -77,7 +86,7 @@ data _null_;
|
||||
idxcnt+1;
|
||||
nom='';
|
||||
uni='';
|
||||
vars=name;
|
||||
vars=name;
|
||||
end;
|
||||
else vars=catx(' ',vars,name);
|
||||
if last.indxname then do;
|
||||
@@ -103,11 +112,10 @@ proc sql
|
||||
order by ranuni(42)
|
||||
%end;
|
||||
;
|
||||
|
||||
|
||||
reset outobs=max;
|
||||
create table datalines1 as
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
select name,type,length,varnum,format,label from dictionary.columns
|
||||
where libname="%upcase(%scan(&base_ds,1))"
|
||||
and memname="%upcase(%scan(&base_ds,2))";
|
||||
|
||||
/**
|
||||
@@ -122,7 +130,7 @@ create table datalines1 as
|
||||
|
||||
data datalines_2;
|
||||
format dataline $32000.;
|
||||
set datalines1 (where=(upcase(name) not in
|
||||
set datalines1 (where=(upcase(name) not in
|
||||
('PROCESSED_DTTM','VALID_FROM_DTTM','VALID_TO_DTTM')));
|
||||
if type='num' then dataline=
|
||||
cats('ifc(int(',name,')=',name,'
|
||||
@@ -136,9 +144,9 @@ proc sql noprint;
|
||||
select dataline into: datalines separated by ',' from datalines_2;
|
||||
|
||||
%local
|
||||
process_dttm_flg
|
||||
valid_from_dttm_flg
|
||||
valid_to_dttm_flg
|
||||
process_dttm_flg
|
||||
valid_from_dttm_flg
|
||||
valid_to_dttm_flg
|
||||
;
|
||||
%let process_dttm_flg = N;
|
||||
%let valid_from_dttm_flg = N;
|
||||
@@ -173,7 +181,7 @@ data _null_;
|
||||
run;
|
||||
|
||||
data _null_;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl;
|
||||
file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
|
||||
length __attrib $32767;
|
||||
if _n_=1 then do;
|
||||
put '/*******************************************************************';
|
||||
@@ -208,7 +216,7 @@ data _null_;
|
||||
put "input ";
|
||||
%do i = 1 %to &nvars.;
|
||||
%if(%length(&&input_stmt_&i..)) %then
|
||||
put " &&input_stmt_&i..";
|
||||
put " &&input_stmt_&i..";
|
||||
;
|
||||
%end;
|
||||
put ";";
|
||||
@@ -243,4 +251,4 @@ quit;
|
||||
%put NOTE-;%put NOTE-;
|
||||
%put NOTE- %sysfunc(dequote(&cards_file.));
|
||||
%put NOTE-;%put NOTE-;
|
||||
%mend;
|
||||
%mend mp_ds2cards;
|
||||
58
base/mp_ds2csv.sas
Normal file
58
base/mp_ds2csv.sas
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
@file
|
||||
@brief Export a dataset to a CSV file
|
||||
@details Export to a file or a fileref
|
||||
Usage:
|
||||
|
||||
%mp_ds2csv(sashelp.class,outref="%sysfunc(pathname(work))/file.csv")
|
||||
|
||||
@param ds The dataset to be exported
|
||||
@param outfile= The output filename - should be quoted.
|
||||
@param outref= The output fileref (takes precedence if provided)
|
||||
@param outencoding= The output encoding to use (unquoted)
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe (credit mjsq)
|
||||
**/
|
||||
|
||||
%macro mp_ds2csv(ds, outref=0, outfile=, outencoding=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%if not %sysfunc(exist(&ds)) %then %do;
|
||||
%put %str(WARN)ING: &ds does not exist;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if %index(&ds,.)=0 %then %let ds=WORK.&ds;
|
||||
|
||||
%if &outencoding=0 %then %let outencoding=;
|
||||
%else %let outencoding=encoding="&outencoding";
|
||||
|
||||
%local outloc;
|
||||
%if &outref=0 %then %let outloc=&outfile;
|
||||
%else %let outloc=&outref;
|
||||
|
||||
/* credit to mjsq - https://stackoverflow.com/a/55642267 */
|
||||
|
||||
/* first get headers */
|
||||
data _null_;
|
||||
file &outloc dlm=',' dsd &outencoding lrecl=32767;
|
||||
length header $ 2000;
|
||||
dsid=open("&ds.","i");
|
||||
num=attrn(dsid,"nvars");
|
||||
do i=1 to num;
|
||||
header = trim(left(coalescec(varlabel(dsid,i),varname(dsid,i))));
|
||||
put header @;
|
||||
end;
|
||||
rc=close(dsid);
|
||||
run;
|
||||
|
||||
/* next, export data */
|
||||
data _null_;
|
||||
set &ds.;
|
||||
file &outloc mod dlm=',' dsd &outencoding lrecl=32767;
|
||||
put (_all_) (+0);
|
||||
run;
|
||||
|
||||
|
||||
%mend mp_ds2csv;
|
||||
98
base/mp_ds2fmtds.sas
Normal file
98
base/mp_ds2fmtds.sas
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
@file
|
||||
@brief Converts every value in a dataset to it's formatted value
|
||||
@details Converts every value to it's formatted value. All variables will
|
||||
become character, and will be in the same order.
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_ds2fmtds(sashelp.cars,work.cars)
|
||||
|
||||
@param [in] libds The library.dataset to be converted
|
||||
@param [out] outds The dataset to create.
|
||||
|
||||
<h4> Related Macros <h4>
|
||||
@li mp_jsonout.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_ds2fmtds(libds, outds
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* validations */
|
||||
%if not %sysfunc(exist(&libds)) %then %do;
|
||||
%put %str(WARN)ING: &libds does not exist;
|
||||
%return;
|
||||
%end;
|
||||
%if %index(&libds,.)=0 %then %let libds=WORK.&libds;
|
||||
|
||||
/* grab metadata */
|
||||
proc contents noprint data=&libds
|
||||
out=_data_(keep=name type length format formatl formatd varnum);
|
||||
run;
|
||||
proc sort;
|
||||
by varnum;
|
||||
run;
|
||||
|
||||
/* prepare formats and varnames */
|
||||
data _null_;
|
||||
set &syslast end=last;
|
||||
name=upcase(name);
|
||||
/* fix formats */
|
||||
if type=2 or type=6 then do;
|
||||
length fmt $49.;
|
||||
if format='' then fmt=cats('$',length,'.');
|
||||
else if formatl=0 then fmt=cats(format,'.');
|
||||
else fmt=cats(format,formatl,'.');
|
||||
newlen=max(formatl,length);
|
||||
end;
|
||||
else do;
|
||||
if format='' then fmt='best.';
|
||||
else if formatl=0 then fmt=cats(format,'.');
|
||||
else if formatd=0 then fmt=cats(format,formatl,'.');
|
||||
else fmt=cats(format,formatl,'.',formatd);
|
||||
/* needs to be wide, for datetimes etc */
|
||||
newlen=max(length,formatl,24);
|
||||
end;
|
||||
/* 32 char unique name */
|
||||
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
||||
|
||||
call symputx(cats('name',_n_),name,'l');
|
||||
call symputx(cats('newname',_n_),newname,'l');
|
||||
call symputx(cats('len',_n_),newlen,'l');
|
||||
call symputx(cats('fmt',_n_),fmt,'l');
|
||||
call symputx(cats('type',_n_),type,'l');
|
||||
if last then call symputx('nobs',_n_,'l');
|
||||
run;
|
||||
|
||||
/* clean up */
|
||||
proc sql;
|
||||
drop table &syslast;
|
||||
|
||||
%if &nobs=0 %then %do;
|
||||
%put Dataset &libds has no columns!
|
||||
data &outds;
|
||||
set &libds;
|
||||
run;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
data &outds;
|
||||
/* rename on entry */
|
||||
set &libds(rename=(
|
||||
%local i;
|
||||
%do i=1 %to &nobs;
|
||||
&&name&i=&&newname&i
|
||||
%end;
|
||||
));
|
||||
%do i=1 %to &nobs;
|
||||
length &&name&i $&&len&i;
|
||||
&&name&i=left(put(&&newname&i,&&fmt&i));
|
||||
drop &&newname&i;
|
||||
%end;
|
||||
if _error_ then call symputx('syscc',1012);
|
||||
run;
|
||||
|
||||
%mend mp_ds2fmtds;
|
||||
203
base/mp_filtercheck.sas
Normal file
203
base/mp_filtercheck.sas
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks an input filter table for validity
|
||||
@details Performs checks on the input table to ensure it arrives in the
|
||||
correct format. This is necessary to prevent code injection. Will update
|
||||
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
|
||||
graceful service exit (configurable).
|
||||
|
||||
Used for dynamic filtering in [Data Controller for SAS®](https://datacontroller.io).
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords)
|
||||
|
||||
The input table should have the following format:
|
||||
|
||||
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|
||||
|---|---|---|---|---|---|
|
||||
|AND|AND|1|AGE|=|12|
|
||||
|AND|AND|1|SEX|<=|'M'|
|
||||
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|
||||
|AND|OR|2|Weight|>=|7|
|
||||
|
||||
Rules applied:
|
||||
|
||||
@li GROUP_LOGIC - only AND/OR
|
||||
@li SUBGROUP_LOGIC - only AND/OR
|
||||
@li SUBGROUP_ID - only integers
|
||||
@li VARIABLE_NM - must be in the target table
|
||||
@li OPERATOR_NM - only =/>/</<=/>=/BETWEEN/IN/NOT IN/NE/CONTAINS
|
||||
@li RAW_VALUE - no unquoted values except integers, commas and spaces.
|
||||
|
||||
@returns The &outds table containing any bad rows, plus a REASON_CD column.
|
||||
|
||||
@param [in] inds The table to be checked, with the format above
|
||||
@param [in] targetds= The target dataset against which to verify VARIABLE_NM.
|
||||
This must be available (ie, the library must be assigned).
|
||||
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
||||
@param [out] outds= The output table, which is a copy of the &inds. table
|
||||
plus a REASON_CD column, containing only bad records. If bad records found,
|
||||
the SYSCC value will be set to 1008 (general data problem). Downstream
|
||||
processes should check this table (and return code) before continuing.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mp_filtergenerate.sas
|
||||
@li mp_filtervalidate.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_filtergenerate.sas
|
||||
@li mp_filtervalidate.sas
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
@todo Support date / hex / name literals and exponents in RAW_VALUE field
|
||||
**/
|
||||
|
||||
%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc - on macro entry)
|
||||
)
|
||||
|
||||
/* Validate input column */
|
||||
%local vtype;
|
||||
%let vtype=%mf_getvartype(&inds,RAW_VALUE);
|
||||
%mp_abort(iftrue=(&abort=YES and &vtype ne C),
|
||||
mac=&sysmacroname,
|
||||
msg=%str(%str(ERR)OR: RAW_VALUE must be character)
|
||||
)
|
||||
%if &vtype ne C %then %do;
|
||||
%put &sysmacroname: RAW_VALUE must be character;
|
||||
%let syscc=42;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
|
||||
/**
|
||||
* Sanitise the values based on valid value lists, then strip out
|
||||
* quotes, commas, periods and spaces.
|
||||
* Only numeric values should remain
|
||||
*/
|
||||
%local reason_cd nobs;
|
||||
%let nobs=0;
|
||||
data &outds;
|
||||
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
|
||||
OPERATOR_NM $10 RAW_VALUE $4000;*/
|
||||
set &inds;
|
||||
length reason_cd $4032;
|
||||
|
||||
/* closed list checks */
|
||||
if GROUP_LOGIC not in ('AND','OR') then do;
|
||||
REASON_CD='GROUP_LOGIC should be AND/OR, not:'!!cats(GROUP_LOGIC);
|
||||
putlog REASON_CD= GROUP_LOGIC=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
if SUBGROUP_LOGIC not in ('AND','OR') then do;
|
||||
REASON_CD='SUBGROUP_LOGIC should be AND/OR, not:'!!cats(SUBGROUP_LOGIC);
|
||||
putlog REASON_CD= SUBGROUP_LOGIC=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
if mod(SUBGROUP_ID,1) ne 0 then do;
|
||||
REASON_CD='SUBGROUP_ID should be integer, not '!!left(subgroup_id);
|
||||
putlog REASON_CD= SUBGROUP_ID=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
if upcase(VARIABLE_NM) not in
|
||||
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
|
||||
then do;
|
||||
REASON_CD="Variable "!!cats(variable_nm)!!" not in &targetds";
|
||||
putlog REASON_CD= VARIABLE_NM=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
if OPERATOR_NM not in
|
||||
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
|
||||
then do;
|
||||
REASON_CD='Invalid OPERATOR_NM: '!!left(OPERATOR_NM);
|
||||
putlog REASON_CD= OPERATOR_NM=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
|
||||
/* special logic */
|
||||
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
|
||||
else if OPERATOR_NM in ('IN','NOT IN') then do;
|
||||
if substr(raw_value,1,1) ne '('
|
||||
or substr(cats(reverse(raw_value)),1,1) ne ')'
|
||||
then do;
|
||||
REASON_CD='Missing start/end bracket in RAW_VALUE';
|
||||
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
|
||||
end;
|
||||
else raw_value1=raw_value;
|
||||
|
||||
/* remove nested literals eg '' */
|
||||
raw_value1=tranwrd(raw_value1,"''",'');
|
||||
|
||||
/* now match string literals (always single quotes) */
|
||||
raw_value2=raw_value1;
|
||||
regex = prxparse("s/(\').*?(\')//");
|
||||
call prxchange(regex,-1,raw_value2);
|
||||
|
||||
/* remove commas and periods*/
|
||||
raw_value3=compress(raw_value2,',.');
|
||||
|
||||
/* output records that contain values other than digits and spaces */
|
||||
if notdigit(compress(raw_value3,' '))>0 then do;
|
||||
putlog raw_value3= $hex32.;
|
||||
REASON_CD=cats('Invalid RAW_VALUE:',raw_value);
|
||||
putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
|
||||
call symputx('reason_cd',reason_cd,'l');
|
||||
call symputx('nobs',_n_,'l');
|
||||
output;
|
||||
end;
|
||||
|
||||
run;
|
||||
|
||||
|
||||
data _null_;
|
||||
set &outds end=last;
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue=(&abort=YES and &nobs>0),
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Data issue: %superq(reason_cd))
|
||||
)
|
||||
|
||||
%if &nobs>0 %then %do;
|
||||
%let syscc=1008;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
/**
|
||||
* syntax checking passed but it does not mean the filter is valid
|
||||
* for that we can run a proc sql validate query
|
||||
*/
|
||||
%local fref1;
|
||||
%let fref1=%mf_getuniquefileref();
|
||||
%mp_filtergenerate(&inds,outref=&fref1)
|
||||
|
||||
/* this macro will also set syscc to 1008 if any issues found */
|
||||
%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)
|
||||
|
||||
%mend mp_filtercheck;
|
||||
102
base/mp_filtergenerate.sas
Normal file
102
base/mp_filtergenerate.sas
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
@file
|
||||
@brief Generates a filter clause from an input table, to a fileref
|
||||
@details Uses the input table to generate an output filter clause.
|
||||
This feature is used to create dynamic dropdowns in [Data Controller for SAS®](
|
||||
https://datacontroller.io). The input table should be in the format below:
|
||||
|
||||
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|
||||
|---|---|---|---|---|---|
|
||||
|AND|AND|1|AGE|=|12|
|
||||
|AND|AND|1|SEX|<=|'M'|
|
||||
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|
||||
|AND|OR|2|Weight|>=|7|
|
||||
|
||||
Note - if the above table is received from an external client, the values
|
||||
should first be validated using the mp_filtercheck.sas macro to avoid risk
|
||||
of SQL injection.
|
||||
|
||||
To generate the filter, run the following code:
|
||||
|
||||
data work.filtertable;
|
||||
infile datalines4 dsd;
|
||||
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
|
||||
OPERATOR_NM:$10. RAW_VALUE:$4000.;
|
||||
datalines4;
|
||||
AND,AND,1,AGE,=,12
|
||||
AND,AND,1,SEX,<=,"'M'"
|
||||
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
|
||||
AND,OR,2,Weight,>=,7
|
||||
;;;;
|
||||
run;
|
||||
|
||||
%mp_filtergenerate(work.filtertable,outref=myfilter)
|
||||
|
||||
data _null_;
|
||||
infile myfilter;
|
||||
input;
|
||||
put _infile_;
|
||||
run;
|
||||
|
||||
Will write the following query to the log:
|
||||
|
||||
> (
|
||||
> AGE = 12
|
||||
> AND
|
||||
> SEX <= 'M'
|
||||
> ) AND (
|
||||
> Name NOT IN ('Jane','Alfred')
|
||||
> OR
|
||||
> Weight >= 7
|
||||
> )
|
||||
|
||||
@param [in] inds The input table with query values
|
||||
@param [out] outref= The output fileref to contain the filter clause. Will
|
||||
be created (or replaced).
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_filtercheck.sas
|
||||
@li mp_filtervalidate.sas
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_abort.sas
|
||||
@li mf_nobs.sas
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_filtergenerate(inds,outref=filter);
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc - on macro entry)
|
||||
)
|
||||
|
||||
filename &outref temp;
|
||||
|
||||
%if %mf_nobs(&inds)=0 %then %do;
|
||||
/* ensure we have a default filter */
|
||||
data _null_;
|
||||
file &outref;
|
||||
put '1=1';
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
data _null_;
|
||||
file &outref lrecl=32800;
|
||||
set &inds end=last;
|
||||
by SUBGROUP_ID;
|
||||
if _n_=1 then put '((';
|
||||
else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '(';
|
||||
else put +2 SUBGROUP_LOGIC;
|
||||
|
||||
put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;
|
||||
|
||||
if last.SUBGROUP_ID then put ')'@;
|
||||
if last then put ')';
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend mp_filtergenerate;
|
||||
106
base/mp_filtervalidate.sas
Normal file
106
base/mp_filtervalidate.sas
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
@file
|
||||
@brief Checks a generated filter query for validity
|
||||
@details Runs a generated filter in proc sql with the validate option.
|
||||
Used in mp_filtercheck.sas in an fcmp container.
|
||||
|
||||
Built to support dynamic filtering in
|
||||
[Data Controller for SAS®](https://datacontroller.io).
|
||||
|
||||
Usage:
|
||||
|
||||
data work.filtertable;
|
||||
infile datalines4 dsd;
|
||||
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
|
||||
OPERATOR_NM:$10. RAW_VALUE:$4000.;
|
||||
datalines4;
|
||||
AND,AND,1,AGE,=,12
|
||||
AND,AND,1,SEX,<=,"'M'"
|
||||
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
|
||||
AND,OR,2,Weight,>=,7
|
||||
;;;;
|
||||
run;
|
||||
|
||||
%mp_filtergenerate(work.filtertable,outref=myfilter)
|
||||
|
||||
%mp_filtervalidate(myfilter,sashelp.class)
|
||||
|
||||
|
||||
@returns The SYSCC value will be 1008 if there are validation issues.
|
||||
|
||||
@param [in] inref The input fileref to validate (generated by
|
||||
mp_filtergenerate.sas)
|
||||
@param [in] targetds The target dataset against which to verify the query
|
||||
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
|
||||
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the
|
||||
error / warning message, if one exists. If this table contains any rows,
|
||||
there are problems!
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_nobs.sas
|
||||
@li mp_abort.sas
|
||||
|
||||
<h4> Related Macros </h4>
|
||||
@li mp_filtercheck.sas
|
||||
@li mp_filtergenerate.sas
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);
|
||||
|
||||
%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)
|
||||
,mac=&sysmacroname
|
||||
,msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)
|
||||
)
|
||||
|
||||
%local fref1;
|
||||
%let fref1=%mf_getuniquefileref();
|
||||
|
||||
data _null_;
|
||||
file &fref1;
|
||||
infile &inref end=eof;
|
||||
if _n_=1 then do;
|
||||
put "proc sql;";
|
||||
put "validate select * from &targetds";
|
||||
put "where " ;
|
||||
end;
|
||||
input;
|
||||
put _infile_;
|
||||
putlog _infile_;
|
||||
if eof then put ";quit;";
|
||||
run;
|
||||
|
||||
%inc &fref1;
|
||||
|
||||
data &outds;
|
||||
if &sqlrc or &syscc or &syserr then do;
|
||||
REASON_CD='VALIDATION_ERROR: '!!
|
||||
coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
|
||||
output;
|
||||
end;
|
||||
else stop;
|
||||
run;
|
||||
|
||||
filename &fref1 clear;
|
||||
|
||||
%if %mf_nobs(&outds)>0 %then %do;
|
||||
%if &abort=YES %then %do;
|
||||
data _null_;
|
||||
set &outds;
|
||||
call symputx('REASON_CD',reason_cd,'l');
|
||||
stop;
|
||||
run;
|
||||
%mp_abort(
|
||||
mac=&sysmacroname,
|
||||
msg=%str(Filter validation issues. ERR=%superq(SYSERRORTEXT)
|
||||
, WARN=%superq(SYSWARNINGTEXT) )
|
||||
)
|
||||
%end;
|
||||
%let syscc=1008;
|
||||
%end;
|
||||
|
||||
%mend mp_filtervalidate;
|
||||
@@ -14,14 +14,14 @@
|
||||
constraint unq unique(tx_from, dd_type),
|
||||
constraint nnn not null(DD_SHORTDESC)
|
||||
);
|
||||
|
||||
|
||||
%mp_getconstraints(lib=work,ds=example,outds=work.constraints)
|
||||
|
||||
@param lib= The target library (default=WORK)
|
||||
@param ds= The target dataset. Leave blank (default) for all datasets.
|
||||
@param outds the output dataset
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -49,12 +49,12 @@ create table &outds as
|
||||
on a.TABLE_CATALOG=b.TABLE_CATALOG
|
||||
and a.TABLE_NAME=b.TABLE_NAME
|
||||
and a.constraint_name=b.constraint_name
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
where a.TABLE_CATALOG="&lib"
|
||||
and b.TABLE_CATALOG="&lib"
|
||||
%if "&ds" ne "" %then %do;
|
||||
and a.TABLE_NAME="&ds"
|
||||
and b.TABLE_NAME="&ds"
|
||||
%end;
|
||||
;
|
||||
|
||||
%mend;
|
||||
%mend mp_getconstraints;
|
||||
335
base/mp_getdbml.sas
Normal file
335
base/mp_getdbml.sas
Normal file
@@ -0,0 +1,335 @@
|
||||
/**
|
||||
@file
|
||||
@brief Extract DBML from SAS Libraries
|
||||
@details DBML is an open source markup format to represent databases.
|
||||
More details: https://www.dbml.org/home/
|
||||
|
||||
Usage:
|
||||
|
||||
|
||||
%mp_getdbml(liblist=SASHELP WORK,outref=mydbml,showlog=YES)
|
||||
|
||||
Take the log output and paste it into the renderer at https://dbdiagram.io
|
||||
to view your data model diagram. The code takes a "best guess" at
|
||||
the one to one and one to many relationships (based on constraints
|
||||
and indexes, and assuming that the column names would match).
|
||||
|
||||
You may need to adjust the rendered DBML to suit your needs.
|
||||
|
||||

|
||||
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getquotedstr.sas
|
||||
@li mp_getconstraints.sas
|
||||
|
||||
@param liblist= Space seperated list of librefs to take as
|
||||
input (Default=SASHELP)
|
||||
@param outref= Fileref to contain the DBML (Default=getdbml)
|
||||
@param showlog= set to YES to show the DBML in the log (Default is NO)
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_getdbml(liblist=SASHELP,outref=getdbml,showlog=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* check fileref is assigned */
|
||||
%if %sysfunc(fileref(&outref)) > 0 %then %do;
|
||||
filename &outref temp;
|
||||
%end;
|
||||
|
||||
%let liblist=%upcase(&liblist);
|
||||
|
||||
proc sql noprint;
|
||||
create table _data_ as
|
||||
select * from dictionary.tables
|
||||
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
||||
order by libname,memname;
|
||||
%local tabinfo; %let tabinfo=&syslast;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.columns
|
||||
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
||||
order by libname,memname,varnum;
|
||||
%local colinfo; %let colinfo=&syslast;
|
||||
|
||||
%local dsnlist;
|
||||
select distinct upcase(cats(libname,'.',memname)) into: dsnlist
|
||||
separated by ' '
|
||||
from &syslast
|
||||
;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.indexes
|
||||
where upcase(libname) in (%mf_getquotedstr(&liblist))
|
||||
order by idxusage, indxname, indxpos;
|
||||
%local idxinfo; %let idxinfo=&syslast;
|
||||
|
||||
/* Extract all Primary Key and Unique data constraints */
|
||||
%mp_getconstraints(lib=%scan(&liblist,1),outds=_data_)
|
||||
%local colconst; %let colconst=&syslast;
|
||||
|
||||
%do x=2 %to %sysfunc(countw(&liblist));
|
||||
%mp_getconstraints(lib=%scan(&liblist,&x),outds=_data_)
|
||||
proc append base=&colconst data=&syslast;
|
||||
run;
|
||||
%end;
|
||||
|
||||
|
||||
|
||||
|
||||
/* header info */
|
||||
data _null_;
|
||||
file &outref;
|
||||
put "// DBML generated by &sysuserid on %sysfunc(datetime(),datetime19.) ";
|
||||
put "Project sasdbml {";
|
||||
put " database_type: 'SAS'";
|
||||
put " Note: 'Generated by the mp_getdbml() macro'";
|
||||
put "}";
|
||||
run;
|
||||
|
||||
/* create table groups */
|
||||
data _null_;
|
||||
file &outref mod;
|
||||
set &tabinfo;
|
||||
by libname;
|
||||
if first.libname then put "TableGroup " libname "{";
|
||||
ds=quote(cats(libname,'.',memname));
|
||||
put ' ' ds;
|
||||
if last.libname then put "}";
|
||||
run;
|
||||
|
||||
/* table for pks */
|
||||
data _data_;
|
||||
length curds const col $39;
|
||||
call missing (of _all_);
|
||||
stop;
|
||||
run;
|
||||
%let pkds=&syslast;
|
||||
|
||||
%local x curds constraints_used constcheck;
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist,%str( )));
|
||||
%let curds=%scan(&dsnlist,&x,%str( ));
|
||||
%let constraints_used=;
|
||||
%let constcheck=0;
|
||||
data _null_;
|
||||
file &outref mod;
|
||||
length lab $1024 typ $20;
|
||||
set &colinfo (where=(
|
||||
libname="%scan(&curds,1,.)" and upcase(memname)="%scan(&curds,2,.)"
|
||||
)) end=last;
|
||||
|
||||
if _n_=1 then do;
|
||||
table='Table "'!!"&curds"!!'"{';
|
||||
put table;
|
||||
end;
|
||||
name=upcase(name);
|
||||
lab=" note:"!!quote(trim(tranwrd(label,'"',"'")));
|
||||
if upcase(format)=:'DATETIME' then typ='datetime';
|
||||
else if type='char' then typ=cats('char(',length,')');
|
||||
else typ='num';
|
||||
|
||||
if notnull='yes' then notnul=' not null';
|
||||
if notnull='no' and missing(label) then put ' ' name typ;
|
||||
else if notnull='yes' and missing(label) then do;
|
||||
put ' ' name typ '[' notnul ']';
|
||||
end;
|
||||
else if notnull='no' then put ' ' name typ '[' lab ']';
|
||||
else put ' ' name typ '[' notnul ',' lab ']';
|
||||
|
||||
run;
|
||||
|
||||
data _data_(keep=curds const col);
|
||||
length ctype $11 cols constraints_used $5000;
|
||||
set &colconst (where=(
|
||||
upcase(libref)="%scan(&curds,1,.)"
|
||||
and upcase(table_name)="%scan(&curds,2,.)"
|
||||
and constraint_type in ('PRIMARY','UNIQUE')
|
||||
)) end=last;
|
||||
file &outref mod;
|
||||
by constraint_type constraint_name;
|
||||
retain cols;
|
||||
column_name=upcase(column_name);
|
||||
|
||||
if _n_=1 then put / ' indexes {';
|
||||
|
||||
if upcase(strip(constraint_type)) = 'PRIMARY' then ctype='[pk]';
|
||||
else ctype='[unique]';
|
||||
|
||||
if first.constraint_name then cols = cats('(',column_name);
|
||||
else cols=cats(cols,',',column_name);
|
||||
|
||||
if last.constraint_name then do;
|
||||
cols=cats(cols,')',ctype)!!' //'!!constraint_name;
|
||||
put ' ' cols;
|
||||
constraints_used=catx(' ',constraints_used, constraint_name);
|
||||
call symputx('constcheck',1);
|
||||
end;
|
||||
|
||||
if last then call symput('constraints_used',cats(upcase(constraints_used)));
|
||||
|
||||
length curds const col $39;
|
||||
curds="&curds";
|
||||
const=constraint_name;
|
||||
col=column_name;
|
||||
run;
|
||||
|
||||
proc append base=&pkds data=&syslast;run;
|
||||
|
||||
/* Create Unique Indexes, but only if they were not already defined within
|
||||
the Constraints section. */
|
||||
data _data_(keep=curds const col);
|
||||
set &idxinfo (where=(
|
||||
libname="%scan(&curds,1,.)"
|
||||
and upcase(memname)="%scan(&curds,2,.)"
|
||||
and unique='yes'
|
||||
and upcase(indxname) not in (%mf_getquotedstr(&constraints_used))
|
||||
));
|
||||
file &outref mod;
|
||||
by idxusage indxname;
|
||||
name=upcase(name);
|
||||
if &constcheck=1 then stop; /* we only care about PKs so stop if we have */
|
||||
if _n_=1 and &constcheck=0 then put / ' indexes {';
|
||||
|
||||
length cols $5000;
|
||||
retain cols;
|
||||
if first.indxname then cols = cats('(',name);
|
||||
else cols=cats(cols,',',name);
|
||||
|
||||
if last.indxname then do;
|
||||
cols=cats(cols,')[unique]')!!' //'!!indxname;
|
||||
put ' ' cols;
|
||||
call symputx('constcheck',1);
|
||||
end;
|
||||
|
||||
length curds const col $39;
|
||||
curds="&curds";
|
||||
const=indxname;
|
||||
col=name;
|
||||
run;
|
||||
proc append base=&pkds data=&syslast;run;
|
||||
|
||||
data _null_;
|
||||
file &outref mod;
|
||||
if &constcheck =1 then put ' }';
|
||||
put '}';
|
||||
run;
|
||||
|
||||
%end;
|
||||
|
||||
/**
|
||||
* now we need to figure out the relationships
|
||||
*/
|
||||
|
||||
/* sort alphabetically so we can have one set of unique cols per table */
|
||||
proc sort data=&pkds nodupkey;
|
||||
by curds const col;
|
||||
run;
|
||||
|
||||
data &pkds.1 (keep=curds col)
|
||||
&pkds.2 (keep=curds cols);
|
||||
set &pkds;
|
||||
by curds const;
|
||||
length retconst $39 cols $5000;
|
||||
retain retconst cols;
|
||||
if first.curds then do;
|
||||
retconst=const;
|
||||
cols=upcase(col);
|
||||
end;
|
||||
else cols=catx(' ',cols,upcase(col));
|
||||
if retconst=const then do;
|
||||
output &pkds.1;
|
||||
if last.const then output &pkds.2;
|
||||
end;
|
||||
run;
|
||||
|
||||
%let curdslist="0";
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist,%str( )));
|
||||
%let curds=%scan(&dsnlist,&x,%str( ));
|
||||
|
||||
%let pkcols=0;
|
||||
data _null_;
|
||||
set &pkds.2(where=(curds="&curds"));
|
||||
call symputx('pkcols',cols);
|
||||
run;
|
||||
%if &pkcols ne 0 %then %do;
|
||||
%let curdslist=&curdslist,"&curds";
|
||||
|
||||
/* start with one2one */
|
||||
data &pkds.4;
|
||||
file &outref mod;
|
||||
set &pkds.2(where=(cols="&pkcols" and curds not in (&curdslist)));
|
||||
line='Ref: "'!!"&curds"
|
||||
!!cats('".(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')')
|
||||
!!' - '
|
||||
!!cats(quote(trim(curds))
|
||||
,'.('
|
||||
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
||||
,')'
|
||||
);
|
||||
put line;
|
||||
run;
|
||||
|
||||
/* now many2one */
|
||||
/* get table with one row per col */
|
||||
data &pkds.5;
|
||||
set &pkds.1(where=(curds="&curds"));
|
||||
run;
|
||||
/* get tables which contain the PK columns */
|
||||
proc sql;
|
||||
create table &pkds.5a as
|
||||
select upcase(cats(b.libname,'.',b.memname)) as curds
|
||||
,b.name
|
||||
from &pkds.5 a
|
||||
inner join &colinfo b
|
||||
on a.col=upcase(b.name);
|
||||
/* count to make sure those tables contain ALL the columns */
|
||||
create table &pkds.5b as
|
||||
select curds,count(*) as cnt
|
||||
from &pkds.5a
|
||||
where curds not in (
|
||||
select curds from &pkds.2 where cols="&pkcols"
|
||||
) /* not a one to one match */
|
||||
and curds ne "&curds" /* exclude self */
|
||||
group by 1;
|
||||
create table &pkds.6 as
|
||||
select a.*
|
||||
,b.cols
|
||||
from &pkds.5b a
|
||||
left join &pkds.4 b
|
||||
on a.curds=b.curds;
|
||||
|
||||
data _null_;
|
||||
set &pkds.6;
|
||||
file &outref mod;
|
||||
colcnt=%sysfunc(countw(&pkcols));
|
||||
if cnt=colcnt then do;
|
||||
/* table contains all the PK cols, and was not a direct / 121 match */
|
||||
line='Ref: "'!!"&curds"
|
||||
!!'".('
|
||||
!!"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
||||
!!') > '
|
||||
!!cats(quote(trim(curds))
|
||||
,'.('
|
||||
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
|
||||
,')'
|
||||
);
|
||||
put line;
|
||||
end;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
|
||||
%if %upcase(&showlog)=YES %then %do;
|
||||
options ps=max;
|
||||
data _null_;
|
||||
infile &outref;
|
||||
input;
|
||||
putlog _infile_;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend mp_getdbml;
|
||||
@@ -13,11 +13,13 @@
|
||||
label x='blah';
|
||||
run;
|
||||
proc sql; describe table &syslast;
|
||||
|
||||
%mp_getddl(work,test,flavour=tsql,showlog=YES)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mp_getconstraints.sas
|
||||
|
||||
@param lib libref of the library to create DDL for. Should be assigned.
|
||||
@param ds dataset to create ddl for
|
||||
@param ds dataset to create ddl for (optional)
|
||||
@param fref= the fileref to which to write the DDL. If not preassigned, will
|
||||
be assigned to TEMP.
|
||||
@param flavour= The type of DDL to create (default=SAS). Supported=TSQL
|
||||
@@ -25,12 +27,9 @@
|
||||
@param schema= Choose a preferred schema name (default is to use actual schema
|
||||
,else libref)
|
||||
@param applydttm= for non SAS DDL, choose if columns are created with native
|
||||
datetime2 format or regular decimal type
|
||||
|
||||
datetime2 format or regular decimal type
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
@source https://github.com/sasjs/core
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_getddl(libref,ds,fref=getddl,flavour=SAS,showlog=NO,schema=
|
||||
@@ -54,6 +53,21 @@ create table _data_ as
|
||||
;
|
||||
%local tabinfo; %let tabinfo=&syslast;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.columns
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
%if %length(&ds)>0 %then %do;
|
||||
and upcase(memname)="%upcase(&ds)"
|
||||
%end;
|
||||
;
|
||||
%local colinfo; %let colinfo=&syslast;
|
||||
|
||||
%local dsnlist;
|
||||
select distinct upcase(memname) into: dsnlist
|
||||
separated by ' '
|
||||
from &syslast
|
||||
;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.indexes
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
@@ -64,18 +78,45 @@ create table _data_ as
|
||||
;
|
||||
%local idxinfo; %let idxinfo=&syslast;
|
||||
|
||||
create table _data_ as
|
||||
select * from dictionary.columns
|
||||
where upcase(libname)="%upcase(&libref)"
|
||||
%if %length(&ds)>0 %then %do;
|
||||
and upcase(memname)="%upcase(&ds)"
|
||||
%end;
|
||||
;
|
||||
%local colinfo; %let colinfo=&syslast;
|
||||
%local dsnlist;
|
||||
select distinct upcase(memname) into: dsnlist
|
||||
separated by ' '
|
||||
from &syslast;
|
||||
/* Extract all Primary Key and Unique data constraints */
|
||||
%mp_getconstraints(lib=%upcase(&libref),ds=%upcase(&ds),outds=_data_)
|
||||
%local colconst; %let colconst=&syslast;
|
||||
|
||||
%macro addConst();
|
||||
%global constraints_used;
|
||||
data _null_;
|
||||
length ctype $11 constraint_name_orig $256 constraints_used $5000;
|
||||
set &colconst(
|
||||
where=(table_name="&curds" and constraint_type in ('PRIMARY','UNIQUE'))
|
||||
) end=last;
|
||||
file &fref mod;
|
||||
by constraint_type constraint_name;
|
||||
retain constraints_used;
|
||||
constraint_name_orig=constraint_name;
|
||||
if upcase(strip(constraint_type)) = 'PRIMARY' then ctype='PRIMARY KEY';
|
||||
else ctype=strip(constraint_type);
|
||||
%if &flavour=TSQL %then %do;
|
||||
column_name=catt('[',column_name,']');
|
||||
constraint_name=catt('[',constraint_name,']');
|
||||
%end;
|
||||
%else %if &flavour=PGSQL %then %do;
|
||||
column_name=catt('"',column_name,'"');
|
||||
constraint_name=catt('"',constraint_name,'"');
|
||||
%end;
|
||||
if first.constraint_name then do;
|
||||
constraints_used = catx(' ', constraints_used, constraint_name_orig);
|
||||
put " ,CONSTRAINT " constraint_name ctype "(" ;
|
||||
put ' ' column_name;
|
||||
end;
|
||||
else put ' ,' column_name;
|
||||
if last.constraint_name then do;
|
||||
put " )";
|
||||
call symput('constraints_used',strip(constraints_used));
|
||||
end;
|
||||
run;
|
||||
%put &=constraints_used;
|
||||
%mend addConst;
|
||||
|
||||
data _null_;
|
||||
file &fref;
|
||||
put "/* DDL generated by &sysuserid on %sysfunc(datetime(),datetime19.) */";
|
||||
@@ -84,14 +125,15 @@ run;
|
||||
%local x curds;
|
||||
%if &flavour=SAS %then %do;
|
||||
data _null_;
|
||||
file &fref;
|
||||
file &fref mod;
|
||||
put "/* SAS Flavour DDL for %upcase(&libref).&curds */";
|
||||
put "proc sql;";
|
||||
run;
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
length nm lab $1024;
|
||||
length nm lab $1024 typ $20;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
|
||||
if _n_=1 then do;
|
||||
@@ -105,27 +147,49 @@ run;
|
||||
end;
|
||||
else put " ,"@@;
|
||||
if length(format)>1 then fmt=" format="!!cats(format);
|
||||
len=" length="!!cats(length);
|
||||
lab=" label="!!quote(trim(label));
|
||||
if length(label)>1 then lab=" label="!!quote(trim(label));
|
||||
if notnull='yes' then notnul=' not null';
|
||||
put name type len fmt notnul lab;
|
||||
if last then put ');';
|
||||
if type='char' then typ=cats('char(',length,')');
|
||||
else if length ne 8 then typ='num length='!!left(length);
|
||||
else typ='num';
|
||||
put name typ fmt notnul lab;
|
||||
run;
|
||||
|
||||
/* Extra step for data constraints */
|
||||
%addConst()
|
||||
|
||||
data _null_;
|
||||
length ds $128;
|
||||
set &idxinfo (where=(memname="&curds")) end=last;
|
||||
file &fref mod;
|
||||
put ');';
|
||||
run;
|
||||
|
||||
/* Create Unique Indexes, but only if they were not already defined within
|
||||
the Constraints section. */
|
||||
data _null_;
|
||||
*length ds $128;
|
||||
set &idxinfo(
|
||||
where=(
|
||||
memname="&curds"
|
||||
and unique='yes'
|
||||
and indxname not in (
|
||||
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
|
||||
)
|
||||
)
|
||||
);
|
||||
file &fref mod;
|
||||
by idxusage indxname;
|
||||
if unique='yes' then uniq=' unique';
|
||||
ds=cats(libname,'.',memname);
|
||||
/* ds=cats(libname,'.',memname); */
|
||||
if first.indxname then do;
|
||||
put 'create ' uniq ' index ' indxname;
|
||||
put ' on ' ds '(' name @@;
|
||||
put 'CREATE UNIQUE INDEX ' indxname "ON &libref..&curds (" ;
|
||||
put ' ' name ;
|
||||
end;
|
||||
else put ' ,' name ;
|
||||
*else put ' ,' name ;
|
||||
if last.indxname then do;
|
||||
put ');';
|
||||
end;
|
||||
else put ',' name @@;
|
||||
if last.indxname then put ');';
|
||||
run;
|
||||
|
||||
/*
|
||||
ods output IntegrityConstraints=ic;
|
||||
proc contents data=testali out2=info;
|
||||
@@ -146,7 +210,7 @@ run;
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put "/* DDL for &schema..&curds */";
|
||||
put "/* TSQL Flavour DDL for &schema..&curds */";
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
@@ -169,31 +233,39 @@ run;
|
||||
else if length le 8000 then fmt='[varchar]('!!cats(length)!!')';
|
||||
else fmt=cats('[varchar](max)');
|
||||
if notnull='yes' then notnul=' NOT NULL';
|
||||
put name fmt notnul;
|
||||
put "[" name +(-1) "]" fmt notnul;
|
||||
run;
|
||||
|
||||
/* Extra step for data constraints */
|
||||
%addConst()
|
||||
|
||||
/* Create Unique Indexes, but only if they were not already defined within
|
||||
the Constraints section. */
|
||||
data _null_;
|
||||
length ds $128;
|
||||
set &idxinfo (where=(memname="&curds"));
|
||||
*length ds $128;
|
||||
set &idxinfo(
|
||||
where=(
|
||||
memname="&curds"
|
||||
and unique='yes'
|
||||
and indxname not in (
|
||||
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
|
||||
)
|
||||
)
|
||||
);
|
||||
file &fref mod;
|
||||
by idxusage indxname;
|
||||
if unique='yes' then uniq=' unique';
|
||||
ds=cats(libname,'.',memname);
|
||||
*ds=cats(libname,'.',memname);
|
||||
if first.indxname then do;
|
||||
if unique='yes' and nomiss='yes' then do;
|
||||
put ' ,constraint [' indxname '] PRIMARY KEY';
|
||||
end;
|
||||
else if unique='yes' then do;
|
||||
/* add nonclustered in case of multiple unique indexes */
|
||||
put ' ,index [' indxname '] UNIQUE NONCLUSTERED';
|
||||
end;
|
||||
put ' (';
|
||||
put ' [' name ']';
|
||||
/* add nonclustered in case of multiple unique indexes */
|
||||
put ' ,index [' indxname +(-1) '] UNIQUE NONCLUSTERED (';
|
||||
put ' [' name +(-1) ']';
|
||||
end;
|
||||
else put ' ,[' name ']';
|
||||
else put ' ,[' name +(-1) ']';
|
||||
if last.indxname then do;
|
||||
put ' )';
|
||||
put ' )';
|
||||
end;
|
||||
run;
|
||||
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put ')';
|
||||
@@ -217,8 +289,87 @@ run;
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &flavour=PGSQL %then %do;
|
||||
/* if schema does not exist, set to be same as libref */
|
||||
%local schemaactual;
|
||||
proc sql noprint;
|
||||
select sysvalue into: schemaactual
|
||||
from dictionary.libnames
|
||||
where libname="&libref" and engine='POSTGRES';
|
||||
%let schema=%sysfunc(coalescec(&schemaactual,&schema,&libref));
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put "CREATE SCHEMA &schema;";
|
||||
%do x=1 %to %sysfunc(countw(&dsnlist));
|
||||
%let curds=%scan(&dsnlist,&x);
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put "/* Postgres Flavour DDL for &schema..&curds */";
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
set &colinfo (where=(upcase(memname)="&curds")) end=last;
|
||||
length fmt $32;
|
||||
if _n_=1 then do;
|
||||
if memtype='DATA' then do;
|
||||
put "CREATE TABLE &schema..&curds (";
|
||||
end;
|
||||
else do;
|
||||
put "CREATE VIEW &schema..&curds (";
|
||||
end;
|
||||
put " "@@;
|
||||
end;
|
||||
else put " ,"@@;
|
||||
format=upcase(format);
|
||||
if 1=0 then; /* dummy if */
|
||||
%if &applydttm=YES %then %do;
|
||||
else if format=:'DATETIME' then fmt=' TIMESTAMP ';
|
||||
%end;
|
||||
else if type='num' then fmt=' DOUBLE PRECISION';
|
||||
else fmt='VARCHAR('!!cats(length)!!')';
|
||||
if notnull='yes' then notnul=' NOT NULL';
|
||||
/* quote column names in case they represent reserved words */
|
||||
name2=quote(trim(name));
|
||||
put name2 fmt notnul;
|
||||
run;
|
||||
|
||||
%if &showlog=YES %then %do;
|
||||
/* Extra step for data constraints */
|
||||
%addConst()
|
||||
|
||||
data _null_;
|
||||
file &fref mod;
|
||||
put ');';
|
||||
run;
|
||||
|
||||
/* Create Unique Indexes, but only if they were not already defined within
|
||||
the Constraints section. */
|
||||
data _null_;
|
||||
*length ds $128;
|
||||
set &idxinfo(
|
||||
where=(
|
||||
memname="&curds"
|
||||
and unique='yes'
|
||||
and indxname not in (
|
||||
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
|
||||
)
|
||||
)
|
||||
);
|
||||
file &fref mod;
|
||||
by idxusage indxname;
|
||||
/* ds=cats(libname,'.',memname); */
|
||||
if first.indxname then do;
|
||||
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
|
||||
put ' "' name +(-1) '"' ;
|
||||
end;
|
||||
else put ' ,"' name +(-1) '"';
|
||||
*else put ' ,' name ;
|
||||
if last.indxname then do;
|
||||
put ');';
|
||||
end;
|
||||
run;
|
||||
|
||||
%end;
|
||||
%end;
|
||||
%if %upcase(&showlog)=YES %then %do;
|
||||
options ps=max;
|
||||
data _null_;
|
||||
infile &fref;
|
||||
@@ -227,4 +378,4 @@ run;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_getddl;
|
||||
@@ -1,17 +1,18 @@
|
||||
/**
|
||||
@file mp_getmaxvarlengths.sas
|
||||
@brief Scans a dataset to find the max length of the variable values
|
||||
@details
|
||||
@details
|
||||
This macro will scan a base dataset and produce an output dataset with two
|
||||
columns:
|
||||
|
||||
- NAME Name of the base dataset column
|
||||
- MAXLEN Maximum length of the data contained therein.
|
||||
|
||||
Character fields may be allocated very large widths (eg 32000) of which the maximum
|
||||
value is likely to be much narrower. This macro was designed to enable a HTML
|
||||
table to be appropriately sized however this could be used as part of a data
|
||||
audit to ensure we aren't over-sizing our tables in relation to the data therein.
|
||||
Character fields may be allocated very large widths (eg 32000) of which the
|
||||
maximum value is likely to be much narrower. This macro was designed to
|
||||
enable a HTML table to be appropriately sized however this could be used as
|
||||
part of a data audit to ensure we aren't over-sizing our tables in relation to
|
||||
the data therein.
|
||||
|
||||
Numeric fields are converted using the relevant format to determine the width.
|
||||
Usage:
|
||||
@@ -21,7 +22,7 @@
|
||||
@param libds Two part dataset (or view) reference.
|
||||
@param outds= The output dataset to create
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_getvarformat.sas
|
||||
@@ -33,7 +34,7 @@
|
||||
|
||||
%macro mp_getmaxvarlengths(
|
||||
libds /* libref.dataset to analyse */
|
||||
,outds=work.mp_getmaxvarlengths /* name of output dataset to create */
|
||||
,outds=work.mp_getmaxvarlengths /* name of output dataset to create */
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local vars x var fmt;
|
||||
@@ -69,4 +70,4 @@ create table &outds (rename=(
|
||||
out=&outds(rename=(_name_=NAME COL1=MAXLEN));
|
||||
run;
|
||||
|
||||
%mend;
|
||||
%mend mp_getmaxvarlengths;
|
||||
@@ -3,9 +3,9 @@
|
||||
@brief Guess the primary key of a table
|
||||
@details Tries to guess the primary key of a table based on the following logic:
|
||||
|
||||
* Columns with nulls are ignored
|
||||
* Return only column combinations that provide unique results
|
||||
* Start from one column, then move out to include composite keys of 2 to 6 columns
|
||||
* Columns with nulls are ignored
|
||||
* Return only column combinations that provide unique results
|
||||
* Start from one column, then move out to include composite keys of 2 to 6 columns
|
||||
|
||||
The library of the target should be assigned before using this macro.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
@param min_rows= The minimum number of rows a table should have in order to try
|
||||
and guess the PK. Default=5.
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_nobs.sas
|
||||
@@ -51,7 +51,7 @@
|
||||
/* get null count and row count */
|
||||
%let tmpvar=%mf_getuniquename();
|
||||
proc sql noprint;
|
||||
create table _data_ as select
|
||||
create table _data_ as select
|
||||
count(*) as &tmpvar
|
||||
%do i=1 %to &vcnt;
|
||||
%let var=%scan(&vars,&i);
|
||||
@@ -85,10 +85,10 @@
|
||||
%put &sysmacroname: &baseds has no combination of unique records! Exiting.;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
|
||||
/* now check cardinality */
|
||||
proc sql noprint;
|
||||
create table _data_ as select
|
||||
create table _data_ as select
|
||||
%do i=1 %to &ppkcnt;
|
||||
%let var=%scan(&posspks,&i);
|
||||
count(distinct &var) as &var
|
||||
@@ -212,7 +212,7 @@
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
|
||||
%if &ppkcnt=4 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
@@ -228,7 +228,7 @@
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do l=4 %to &ppkcnt;
|
||||
%let lev4=%scan(&posspks,&l);
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%do m=5 %to &ppkcnt;
|
||||
%let lev5=%scan(&posspks,&m);
|
||||
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then %do;
|
||||
@@ -250,7 +250,7 @@
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
|
||||
%if &ppkcnt=5 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
@@ -266,17 +266,17 @@
|
||||
%let lev3=%scan(&posspks,&k);
|
||||
%if &lev1 ne &lev3 and &lev2 ne &lev3 %then %do l=4 %to &ppkcnt;
|
||||
%let lev4=%scan(&posspks,&l);
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%if &lev1 ne &lev4 and &lev2 ne &lev4 and &lev3 ne &lev4 %then
|
||||
%do m=5 %to &ppkcnt;
|
||||
%let lev5=%scan(&posspks,&m);
|
||||
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then
|
||||
%if &lev1 ne &lev5 & &lev2 ne &lev5 & &lev3 ne &lev5 & &lev4 ne &lev5 %then
|
||||
%do n=6 %to &ppkcnt;
|
||||
%let lev6=%scan(&posspks,&n);
|
||||
%if &lev1 ne &lev6 & &lev2 ne &lev6 & &lev3 ne &lev6
|
||||
& &lev4 ne &lev6 & &lev5 ne &lev6 %then
|
||||
%if &lev1 ne &lev6 & &lev2 ne &lev6 & &lev3 ne &lev6
|
||||
& &lev4 ne &lev6 & &lev5 ne &lev6 %then
|
||||
%do;
|
||||
/* check for four level uniqueness */
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5 &lev6)
|
||||
proc sort data=&pkds(keep=&lev1 &lev2 &lev3 &lev4 &lev5 &lev6)
|
||||
out=&tmpds noduprec;
|
||||
by _all_;
|
||||
run;
|
||||
@@ -295,10 +295,10 @@
|
||||
%end;
|
||||
%end;
|
||||
%end;
|
||||
|
||||
|
||||
%if &ppkcnt=6 %then %do;
|
||||
%put &sysmacroname: No more PK guess possible;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_guesspk;
|
||||
75
base/mp_hashdataset.sas
Normal file
75
base/mp_hashdataset.sas
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
@file
|
||||
@brief Returns a unique hash for a dataset
|
||||
@details Ignores metadata attributes, used only to hash values. Compared
|
||||
datasets must be in the same order.
|
||||
|
||||
%mp_hashdataset(sashelp.class,outds=myhash)
|
||||
|
||||
data _null_;
|
||||
set work.myhash;
|
||||
put hashkey=;
|
||||
run;
|
||||
|
||||

|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getattrn.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
|
||||
@param [in] libds dataset to hash
|
||||
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
|
||||
will contain one column (hashkey) with one observation (a hex32.
|
||||
representation of the input hash)
|
||||
|hashkey:$32.|
|
||||
|---|
|
||||
|28ABC74ABFC45F50794237BA5566E6CA|
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_hashdataset(
|
||||
libds,
|
||||
outds=
|
||||
)/*/STORE SOURCE*/;
|
||||
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
|
||||
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
|
||||
%end;
|
||||
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
|
||||
%put %str(ERR)OR: Dataset &libds is not a dataset;
|
||||
%end;
|
||||
%else %do;
|
||||
%local keyvar /* roll up the md5 */
|
||||
prevkeyvar /* retain prev record md5 */
|
||||
lastvar /* last var in input ds */
|
||||
varlist var i;
|
||||
/* avoid naming conflict for hash key vars */
|
||||
%let keyvar=%mf_getuniquename();
|
||||
%let prevkeyvar=%mf_getuniquename();
|
||||
%let lastvar=%mf_getuniquename();
|
||||
%let varlist=%mf_getvarlist(&libds);
|
||||
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
|
||||
length &prevkeyvar &keyvar $32;
|
||||
retain &prevkeyvar;
|
||||
set &libds end=&lastvar;
|
||||
/* hash should include previous row */
|
||||
&keyvar=put(md5(&prevkeyvar
|
||||
/* loop every column, hashing every individual value */
|
||||
%do i=1 %to %sysfunc(countw(&varlist));
|
||||
%let var=%scan(&varlist,&i,%str( ));
|
||||
%if %mf_getvartype(&libds,&var)=C %then %do;
|
||||
!!put(md5(trim(&var)),$hex32.)
|
||||
%end;
|
||||
%else %do;
|
||||
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
|
||||
%end;
|
||||
%end;
|
||||
),$hex32.);
|
||||
&prevkeyvar=&keyvar;
|
||||
if &lastvar then output;
|
||||
run;
|
||||
%end;
|
||||
%mend mp_hashdataset;
|
||||
@@ -4,8 +4,11 @@
|
||||
@details PROC JSON is faster but will produce errs like the ones below if
|
||||
special chars are encountered.
|
||||
|
||||
>An object or array close is not valid at this point in the JSON text.
|
||||
>Date value out of range
|
||||
> ERROR: Some code points did not transcode.
|
||||
|
||||
> An object or array close is not valid at this point in the JSON text.
|
||||
|
||||
> Date value out of range
|
||||
|
||||
If this happens, try running with ENGINE=DATASTEP.
|
||||
|
||||
@@ -13,8 +16,10 @@
|
||||
|
||||
filename tmp temp;
|
||||
data class; set sashelp.class;run;
|
||||
|
||||
|
||||
%mp_jsonout(OPEN,jref=tmp)
|
||||
%mp_jsonout(OBJ,class,jref=tmp)
|
||||
%mp_jsonout(CLOSE,jref=tmp)
|
||||
|
||||
data _null_;
|
||||
infile tmp;
|
||||
@@ -22,27 +27,29 @@
|
||||
run;
|
||||
|
||||
If you are building web apps with SAS then you are strongly encouraged to use
|
||||
the mX_createwebservice macros in combination with the
|
||||
the mX_createwebservice macros in combination with the
|
||||
[sasjs adapter](https://github.com/sasjs/adapter).
|
||||
For more information see https://sasjs.io
|
||||
|
||||
@param action Valid values:
|
||||
* OPEN - opens the JSON
|
||||
* OBJ - sends a table with each row as an object
|
||||
* ARR - sends a table with each row in an array
|
||||
* CLOSE - closes the JSON
|
||||
@li OPEN - opens the JSON
|
||||
@li OBJ - sends a table with each row as an object
|
||||
@li ARR - sends a table with each row in an array
|
||||
@li CLOSE - closes the JSON
|
||||
|
||||
@param ds the dataset to send. Must be a work table.
|
||||
@param jref= the fileref to which to send the JSON
|
||||
@param dslabel= the name to give the table in the exported JSON
|
||||
@param fmt= Whether to keep or strip formats from the table
|
||||
@param engine= Which engine to use to send the JSON, options are:
|
||||
* PROCJSON (default)
|
||||
* DATASTEP
|
||||
@param engine= Which engine to use to send the JSON, valid options are:
|
||||
@li PROCJSON (default)
|
||||
@li DATASTEP (more reliable when data has non standard characters)
|
||||
|
||||
@param dbg= DEPRECATED - was used to conditionally add PRETTY to
|
||||
proc json but this can cause line truncation in large files.
|
||||
|
||||
|
||||
<h4> Related Macros <h4>
|
||||
@li mp_ds2fmtds.sas
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -50,10 +57,11 @@
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=PROCJSON,dbg=0
|
||||
%macro mp_jsonout(action,ds,jref=_webout,dslabel=,fmt=Y,engine=DATASTEP,dbg=0
|
||||
)/*/STORE SOURCE*/;
|
||||
%put output location=&jref;
|
||||
%if &action=OPEN %then %do;
|
||||
options nobomfile;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
|
||||
run;
|
||||
@@ -66,7 +74,7 @@
|
||||
%if &engine=PROCJSON %then %do;
|
||||
data;run;%let tempds=&syslast;
|
||||
proc sql;drop table &tempds;
|
||||
data &tempds /view=&tempds;set &ds;
|
||||
data &tempds /view=&tempds;set &ds;
|
||||
%if &fmt=N %then format _numeric_ best32.;;
|
||||
proc json out=&jref pretty
|
||||
%if &action=ARR %then nokeys ;
|
||||
@@ -81,13 +89,72 @@
|
||||
%put &sysmacroname: &ds NOT FOUND!!!;
|
||||
%return;
|
||||
%end;
|
||||
data _null_;file &jref mod ;
|
||||
%if &fmt=Y %then %do;
|
||||
%put converting every variable to a formatted variable;
|
||||
/* see mp_ds2fmtds.sas for source */
|
||||
proc contents noprint data=&ds
|
||||
out=_data_(keep=name type length format formatl formatd varnum);
|
||||
run;
|
||||
proc sort;
|
||||
by varnum;
|
||||
run;
|
||||
%local fmtds;
|
||||
%let fmtds=%scan(&syslast,2,.);
|
||||
/* prepare formats and varnames */
|
||||
data _null_;
|
||||
if _n_=1 then call symputx('nobs',nobs,'l');
|
||||
set &fmtds end=last nobs=nobs;
|
||||
name=upcase(name);
|
||||
/* fix formats */
|
||||
if type=2 or type=6 then do;
|
||||
length fmt $49.;
|
||||
if format='' then fmt=cats('$',length,'.');
|
||||
else if formatl=0 then fmt=cats(format,'.');
|
||||
else fmt=cats(format,formatl,'.');
|
||||
newlen=max(formatl,length);
|
||||
end;
|
||||
else do;
|
||||
if format='' then fmt='best.';
|
||||
else if formatl=0 then fmt=cats(format,'.');
|
||||
else if formatd=0 then fmt=cats(format,formatl,'.');
|
||||
else fmt=cats(format,formatl,'.',formatd);
|
||||
/* needs to be wide, for datetimes etc */
|
||||
newlen=max(length,formatl,24);
|
||||
end;
|
||||
/* 32 char unique name */
|
||||
newname='sasjs'!!substr(cats(put(md5(name),$hex32.)),1,27);
|
||||
|
||||
call symputx(cats('name',_n_),name,'l');
|
||||
call symputx(cats('newname',_n_),newname,'l');
|
||||
call symputx(cats('len',_n_),newlen,'l');
|
||||
call symputx(cats('fmt',_n_),fmt,'l');
|
||||
call symputx(cats('type',_n_),type,'l');
|
||||
run;
|
||||
data &fmtds;
|
||||
/* rename on entry */
|
||||
set &ds(rename=(
|
||||
%local i;
|
||||
%do i=1 %to &nobs;
|
||||
&&name&i=&&newname&i
|
||||
%end;
|
||||
));
|
||||
%do i=1 %to &nobs;
|
||||
length &&name&i $&&len&i;
|
||||
&&name&i=left(put(&&newname&i,&&fmt&i));
|
||||
drop &&newname&i;
|
||||
%end;
|
||||
if _error_ then call symputx('syscc',1012);
|
||||
run;
|
||||
%let ds=&fmtds;
|
||||
%end; /* &fmt=Y */
|
||||
data _null_;file &jref mod encoding='utf-8';
|
||||
put "["; call symputx('cols',0,'l');
|
||||
proc sort data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
|
||||
proc sort
|
||||
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
|
||||
out=_data_;
|
||||
by varnum;
|
||||
|
||||
data _null_;
|
||||
data _null_;
|
||||
set _last_ end=last;
|
||||
call symputx(cats('name',_n_),name,'l');
|
||||
call symputx(cats('type',_n_),type,'l');
|
||||
@@ -121,8 +188,9 @@
|
||||
)))))!!'"';
|
||||
%end;
|
||||
%end;
|
||||
run;
|
||||
/* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */
|
||||
run;
|
||||
/* write to temp loc to avoid _webout truncation
|
||||
- https://support.sas.com/kb/49/325.html */
|
||||
filename _sjs temp lrecl=131068 encoding='utf-8';
|
||||
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod;
|
||||
set &tempds;
|
||||
@@ -131,7 +199,7 @@
|
||||
%do i=1 %to &cols;
|
||||
%if &i>1 %then "," ;
|
||||
%if &action=OBJ %then """&&name&i"":" ;
|
||||
&&name&i
|
||||
&&name&i
|
||||
%end;
|
||||
%if &action=ARR %then "]" ; %else "}" ; ;
|
||||
proc sql;
|
||||
@@ -158,8 +226,8 @@
|
||||
%end;
|
||||
|
||||
%else %if &action=CLOSE %then %do;
|
||||
data _null_;file &jref encoding='utf-8';
|
||||
data _null_;file &jref encoding='utf-8' mod;
|
||||
put "}";
|
||||
run;
|
||||
%end;
|
||||
%mend;
|
||||
%mend mp_jsonout;
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
/**
|
||||
@file
|
||||
@brief Convert all library members to CARDS files
|
||||
@details Gets list of members then calls the <code>%mp_ds2cards()</code>
|
||||
macro
|
||||
usage:
|
||||
@details Gets list of members then calls the <code>%mp_ds2cards()</code> macro.
|
||||
Usage:
|
||||
|
||||
%mp_lib2cards(lib=sashelp
|
||||
, outloc= C:\temp )
|
||||
%mp_lib2cards(lib=sashelp
|
||||
, outloc= C:\temp )
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
The output will be one cards file in the `outloc` directory per dataset in the
|
||||
input `lib` library. If the `outloc` directory does not exist, it is created.
|
||||
|
||||
To create a single SAS file with the first 1000 records of each table in a
|
||||
library you could use this syntax:
|
||||
|
||||
%mp_lib2cards(lib=sashelp
|
||||
, outloc= /tmp
|
||||
, outfile= myfile.sas
|
||||
, maxobs= 1000
|
||||
)
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_mkdir.sas
|
||||
@li mf_trimstr.sas
|
||||
@li mp_ds2cards.sas
|
||||
|
||||
@param lib= Library in which to convert all datasets
|
||||
@param outloc= Location in which to store output. Defaults to WORK library.
|
||||
Do not use a trailing slash (my/path not my/path/). No quotes.
|
||||
@param maxobs= limit output to the first <code>maxobs</code> observations
|
||||
@param [in] lib= Library in which to convert all datasets
|
||||
@param [out] outloc= Location in which to store output. Defaults to WORK
|
||||
library. No quotes.
|
||||
@param [out] outfile= Optional output file NAME - if provided, then will create
|
||||
a single output file instead of one file per input table.
|
||||
@param [in] maxobs= limit output to the first <code>maxobs</code> observations
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -25,6 +39,7 @@
|
||||
,outloc=%sysfunc(pathname(work)) /* without trailing slash */
|
||||
,maxobs=max
|
||||
,random_sample=NO
|
||||
,outfile=0
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* Find the tables */
|
||||
@@ -36,16 +51,28 @@ select distinct lowcase(memname)
|
||||
from dictionary.tables
|
||||
where upcase(libname)="%upcase(&lib)";
|
||||
|
||||
/* trim trailing slash, if provided */
|
||||
%let outloc=%mf_trimstr(&outloc,/);
|
||||
%let outloc=%mf_trimstr(&outloc,\);
|
||||
|
||||
/* create the output directory */
|
||||
%mf_mkdir(&outloc)
|
||||
|
||||
/* create the cards files */
|
||||
%do x=1 %to %sysfunc(countw(&memlist));
|
||||
%let ds=%scan(&memlist,&x);
|
||||
%mp_ds2cards(base_ds=&lib..&ds
|
||||
,cards_file="&outloc/&ds..sas"
|
||||
,maxobs=&maxobs
|
||||
,random_sample=&random_sample)
|
||||
%let ds=%scan(&memlist,&x);
|
||||
%mp_ds2cards(base_ds=&lib..&ds
|
||||
,maxobs=&maxobs
|
||||
,random_sample=&random_sample
|
||||
%if "&outfile" ne "0" %then %do;
|
||||
,append=YES
|
||||
,cards_file="&outloc/&outfile"
|
||||
%end;
|
||||
%else %do;
|
||||
,append=NO
|
||||
,cards_file="&outloc/&ds..sas"
|
||||
%end;
|
||||
)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_lib2cards;
|
||||
98
base/mp_mdtablewrite.sas
Normal file
98
base/mp_mdtablewrite.sas
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
@file
|
||||
@brief Create a Markdown Table from a dataset
|
||||
@details A markdown table is a simple table representation for use in
|
||||
documents written in markdown format.
|
||||
|
||||
An online generator is available here:
|
||||
https://www.tablesgenerator.com/markdown_tables
|
||||
|
||||
This structure is also used by the Macro Core library for documenting input/
|
||||
output datasets, as well as the sasjs/cli tool for documenting inputs/outputs
|
||||
for web services.
|
||||
|
||||
We take the standard definition one step further by embedding the informat
|
||||
in the table header row, like so:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
Which resolves to:
|
||||
|
||||
|var1:$|var2:best.|var3:date9.|
|
||||
|---|---|---|
|
||||
|some text|42|01JAN1960|
|
||||
|blah|1|31DEC1999|
|
||||
|
||||
|
||||
Usage:
|
||||
|
||||
%mp_mdtablewrite(libds=sashelp.class,showlog=YES)
|
||||
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvarformat.sas
|
||||
|
||||
@param [in] libds= the library / dataset to create or read from.
|
||||
@param [out] fref= Fileref to contain the markdown. Default=mdtable.
|
||||
@param [out] showlog= set to YES to show the markdown in the log. Default=NO.
|
||||
|
||||
@version 9.3
|
||||
@author Allan Bowe
|
||||
**/
|
||||
|
||||
%macro mp_mdtablewrite(
|
||||
libds=,
|
||||
fref=mdtable,
|
||||
showlog=NO
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
/* check fileref is assigned */
|
||||
%if %sysfunc(fileref(&fref)) > 0 %then %do;
|
||||
filename &fref temp;
|
||||
%end;
|
||||
|
||||
%local vars;
|
||||
%let vars=%mf_getvarlist(&libds);
|
||||
|
||||
/* create the header row */
|
||||
data _null_;
|
||||
file &fref;
|
||||
length line $32767;
|
||||
put '|'
|
||||
%local i var fmt;
|
||||
%do i=1 %to %sysfunc(countw(&vars));
|
||||
%let var=%scan(&vars,&i);
|
||||
%let fmt=%mf_getvarformat(&libds,&var,force=1);
|
||||
"&var:&fmt|"
|
||||
%end;
|
||||
;
|
||||
put '|'
|
||||
%do i=1 %to %sysfunc(countw(&vars));
|
||||
"---|"
|
||||
%end;
|
||||
;
|
||||
run;
|
||||
|
||||
/* write out the data */
|
||||
data _null_;
|
||||
file &fref mod dlm='|' lrecl=32767;
|
||||
set &libds ;
|
||||
length line $32767;
|
||||
line=cats('|',%mf_getvarlist(&libds,dlm=%str(,'|',)),'|');
|
||||
put line;
|
||||
run;
|
||||
|
||||
%if %upcase(&showlog)=YES %then %do;
|
||||
options ps=max;
|
||||
data _null_;
|
||||
infile &fref;
|
||||
input;
|
||||
putlog _infile_;
|
||||
run;
|
||||
%end;
|
||||
|
||||
%mend mp_mdtablewrite;
|
||||
@@ -39,4 +39,4 @@
|
||||
,dttm=%sysfunc(datetime());
|
||||
quit;
|
||||
|
||||
%mend;
|
||||
%mend mp_perflog;
|
||||
88
base/mp_prevobs.sas
Normal file
88
base/mp_prevobs.sas
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
@file
|
||||
@brief Enables previous observations to be re-instated
|
||||
@details Remembers the last X observations by storing them in a hash table.
|
||||
Is a convenience over the use of lag() or retain, when an entire observation
|
||||
needs to be restored.
|
||||
|
||||
This macro will also restore automatic variables (such as _n_ and _error_).
|
||||
|
||||
Example Usage:
|
||||
|
||||
data example;
|
||||
set sashelp.class;
|
||||
calc_var=_n_*3;
|
||||
%* initialise hash and save from PDV ;
|
||||
%mp_prevobs(INIT,history=2)
|
||||
if _n_ =10 then do;
|
||||
%* fetch previous but 1 record;
|
||||
%mp_prevobs(FETCH,-2)
|
||||
put _n_= name= age= calc_var=;
|
||||
%* fetch previous record;
|
||||
%mp_prevobs(FETCH,-1)
|
||||
put _n_= name= age= calc_var=;
|
||||
%* reinstate current record ;
|
||||
%mp_prevobs(FETCH,0)
|
||||
put _n_= name= age= calc_var=;
|
||||
end;
|
||||
run;
|
||||
|
||||
Result:
|
||||
|
||||
<img src="https://imgur.com/PSjHoET.png" alt="mp_prevobs sas" width="400"/>
|
||||
|
||||
Credit is made to `data _null_` for authoring this very helpful paper:
|
||||
https://www.lexjansen.com/pharmasug/2008/cc/CC08.pdf
|
||||
|
||||
@param action Either FETCH a current or previous record, or INITialise.
|
||||
@param record The relative (to current) position of the previous observation
|
||||
to return.
|
||||
@param history= The number of records to retain in the hash table. Default=5
|
||||
@param prefix= the prefix to give to the variables used to store the hash name
|
||||
and index. Default=mp_prevobs
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_prevobs(action,record,history=5,prefix=mp_prevobs
|
||||
)/*/STORE SOURCE*/;
|
||||
%let action=%upcase(&action);
|
||||
%let prefix=%upcase(&prefix);
|
||||
%let record=%eval((&record+0) * -1);
|
||||
|
||||
%if &action=INIT %then %do;
|
||||
|
||||
if _n_ eq 1 then do;
|
||||
attrib &prefix._VAR length=$64;
|
||||
dcl hash &prefix._HASH(ordered:'Y');
|
||||
&prefix._KEY=0;
|
||||
&prefix._HASH.defineKey("&prefix._KEY");
|
||||
do while(1);
|
||||
call vnext(&prefix._VAR);
|
||||
if &prefix._VAR='' then leave;
|
||||
if &prefix._VAR eq "&prefix._VAR" then continue;
|
||||
else if &prefix._VAR eq "&prefix._KEY" then continue;
|
||||
&prefix._HASH.defineData(&prefix._VAR);
|
||||
end;
|
||||
&prefix._HASH.defineDone();
|
||||
end;
|
||||
/* this part has to happen before FETCHing */
|
||||
&prefix._KEY+1;
|
||||
&prefix._rc=&prefix._HASH.add();
|
||||
if &prefix._rc then putlog 'adding' &prefix._rc=;
|
||||
%if &history>0 %then %do;
|
||||
if &prefix._key>&history+1 then
|
||||
&prefix._HASH.remove(key: &prefix._KEY - &history - 1);
|
||||
if &prefix._rc then putlog 'removing' &prefix._rc=;
|
||||
%end;
|
||||
%end;
|
||||
%else %if &action=FETCH %then %do;
|
||||
if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet";
|
||||
else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record);
|
||||
if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY=
|
||||
"with record &record and " _n_=;
|
||||
%end;
|
||||
|
||||
%mend mp_prevobs;
|
||||
@@ -32,9 +32,9 @@
|
||||
|
||||
|
||||
@returns outds contains the following variables:
|
||||
- level (0 = top level)
|
||||
- &parentvar
|
||||
- &childvar (null if none found)
|
||||
- level (0 = top level)
|
||||
- &parentvar
|
||||
- &childvar (null if none found)
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
@@ -87,4 +87,4 @@ insert into &outds select distinct * from &append_ds;
|
||||
)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_recursivejoin;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
@file
|
||||
@brief Reset an option to original value
|
||||
@details Inspired by the SAS Jedi - https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options/
|
||||
@details Inspired by the SAS Jedi -
|
||||
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
|
||||
Called as follows:
|
||||
|
||||
options obs=30;
|
||||
@@ -29,4 +30,4 @@ data _null_;
|
||||
end;
|
||||
run;
|
||||
|
||||
%mend;
|
||||
%mend mp_resetoption;
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
rootlib
|
||||
|-- LIBREF1
|
||||
| |__ mytable.ddl
|
||||
| |__ someothertable.ddl
|
||||
| |__ mytable.ddl
|
||||
| |__ someothertable.ddl
|
||||
|-- LIBREF2
|
||||
| |__ table1.ddl
|
||||
| |__ table2.ddl
|
||||
| |__ table1.ddl
|
||||
| |__ table2.ddl
|
||||
|-- LIBREF3
|
||||
|__ table3.ddl
|
||||
|__ table4.ddl
|
||||
|__ table3.ddl
|
||||
|__ table4.ddl
|
||||
|
||||
Only files with the .ddl suffix are executed. The parent folder name is used
|
||||
as the libref.
|
||||
@@ -46,4 +46,4 @@
|
||||
|
||||
|
||||
|
||||
%mend;
|
||||
%mend mp_runddl;
|
||||
@@ -9,7 +9,15 @@
|
||||
|
||||
%mp_searchcols(libs=sashelp work, cols=name sex age)
|
||||
|
||||
@param libs=
|
||||
@param libs=(SASHELP) Space separated list of libraries to search for columns
|
||||
@param cols= Space separated list of column names to search for (not case
|
||||
sensitive)
|
||||
@param outds=(mp_searchcols) the table to create with the results. Will have
|
||||
one line per table match.
|
||||
@param match=(ANY) The match type. Valid values:
|
||||
@li ANY - The table contains at least one of the columns
|
||||
@li WILD - The table contains a column with a name that partially matches
|
||||
|
||||
@version 9.2
|
||||
@author Allan Bowe
|
||||
**/
|
||||
@@ -17,6 +25,7 @@
|
||||
%macro mp_searchcols(libs=sashelp
|
||||
,cols=
|
||||
,outds=mp_searchcols
|
||||
,match=ANY
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%put &sysmacroname process began at %sysfunc(datetime(),datetime19.);
|
||||
@@ -32,14 +41,16 @@ create table _data_ as
|
||||
where upcase(libname) in ("IMPOSSIBLE",
|
||||
%local x;
|
||||
%do x=1 %to %sysfunc(countw(&libs));
|
||||
"%upcase(%scan(&libs,&x))"
|
||||
"%upcase(%scan(&libs,&x))"
|
||||
%end;
|
||||
)
|
||||
%end;
|
||||
order by 1,2,3;
|
||||
|
||||
%local tempds;
|
||||
%let tempds=&syslast;
|
||||
data &outds;
|
||||
set &syslast;
|
||||
set &tempds;
|
||||
length cols matchcols $32767;
|
||||
cols=upcase(symget('cols'));
|
||||
colcount=countw(cols);
|
||||
@@ -53,10 +64,29 @@ data &outds;
|
||||
retain matchcols;
|
||||
matchcols='';
|
||||
end;
|
||||
%if &match=ANY %then %do;
|
||||
if findw(cols,name,,'spit') then do;
|
||||
sumcols+1;
|
||||
matchcols=cats(matchcols)!!' '!!cats(name);
|
||||
end;
|
||||
%end;
|
||||
%else %if &match=WILD %then %do;
|
||||
if _n_=1 then do;
|
||||
retain wcount;
|
||||
wcount=countw(cols);
|
||||
drop wcount;
|
||||
end;
|
||||
do i=1 to wcount;
|
||||
length curword $32;
|
||||
curword=scan(cols,i,' ');
|
||||
drop curword;
|
||||
if index(name,cats(curword)) then do;
|
||||
sumcols+1;
|
||||
matchcols=cats(matchcols)!!' '!!cats(curword);
|
||||
end;
|
||||
end;
|
||||
%end;
|
||||
|
||||
if last.memname then do;
|
||||
if sumcols>0 then output;
|
||||
if sumcols=colcount then putlog "Full Match: " libname memname;
|
||||
@@ -66,6 +96,8 @@ run;
|
||||
|
||||
proc sort; by descending sumcols memname libname; run;
|
||||
|
||||
proc sql;
|
||||
drop table &tempds;
|
||||
%put &sysmacroname process finished at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
%mend;
|
||||
%mend mp_searchcols;
|
||||
@@ -3,7 +3,7 @@
|
||||
@brief Searches all data in a library
|
||||
@details
|
||||
Scans an entire library and creates a copy of any table
|
||||
containing a specific string or numeric value. Only
|
||||
containing a specific string OR numeric value. Only
|
||||
matching records are written out.
|
||||
If both a string and numval are provided, the string
|
||||
will take precedence.
|
||||
@@ -20,12 +20,13 @@
|
||||
@param ds= the dataset to search (leave blank to search entire library)
|
||||
@param string= the string value to search
|
||||
@param numval= the numeric value to search (must be exact)
|
||||
@param outloc= the directory in which to create the output datasets with matching
|
||||
rows. Will default to a subfolder in the WORK library.
|
||||
@param outobs= set to a positive integer to restrict the number of observations
|
||||
@param outloc= the directory in which to create the output datasets with
|
||||
matching rows. Will default to a subfolder in the WORK library.
|
||||
@param outobs= set to a positive integer to restrict the number of
|
||||
observations
|
||||
@param filter_text= add a (valid) filter clause to further filter the results
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getvarlist.sas
|
||||
@li mf_getvartype.sas
|
||||
@li mf_mkdir.sas
|
||||
@@ -36,7 +37,7 @@
|
||||
**/
|
||||
|
||||
%macro mp_searchdata(lib=sashelp
|
||||
,ds=
|
||||
,ds=
|
||||
,string= /* the query will use a contains (?) operator */
|
||||
,numval= /* numeric must match exactly */
|
||||
,outloc=%sysfunc(pathname(work))/mpsearch
|
||||
@@ -44,9 +45,14 @@
|
||||
,filter_text=%str(1=1)
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%local table_list table table_num table colnum col start_tm vars type coltype;
|
||||
%local table_list table table_num table colnum col start_tm check_tm vars type
|
||||
coltype;
|
||||
%put process began at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
%if &syscc ge 4 %then %do;
|
||||
%put %str(WAR)NING: SYSCC=&syscc on macro entry;
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%if &string = %then %let type=N;
|
||||
%else %let type=C;
|
||||
@@ -57,14 +63,14 @@ libname mpsearch "&outloc";
|
||||
/* get the list of tables in the library */
|
||||
proc sql noprint;
|
||||
select distinct memname into: table_list separated by ' '
|
||||
from dictionary.tables
|
||||
from dictionary.tables
|
||||
where upcase(libname)="%upcase(&lib)"
|
||||
%if &ds ne %then %do;
|
||||
and upcase(memname)=%upcase("&ds")
|
||||
%end;
|
||||
;
|
||||
/* check that we have something to check */
|
||||
proc sql
|
||||
proc sql
|
||||
%if &outobs>-1 %then %do;
|
||||
outobs=&outobs
|
||||
%end;
|
||||
@@ -78,9 +84,10 @@ proc sql
|
||||
%put NO COLUMNS IN &lib..&table! This will be skipped.;
|
||||
%end;
|
||||
%else %do;
|
||||
%let check_tm=%sysfunc(datetime());
|
||||
/* build sql statement */
|
||||
create table mpsearch.&table as select * from &lib..&table
|
||||
where %unquote(&filter_text) and
|
||||
where %unquote(&filter_text) and
|
||||
(0
|
||||
/* loop through columns */
|
||||
%do colnum=1 %to %sysfunc(countw(&vars,%str( )));
|
||||
@@ -88,14 +95,20 @@ proc sql
|
||||
%let coltype=%mf_getvartype(&lib..&table,&col);
|
||||
%if &type=C and &coltype=C %then %do;
|
||||
/* if a char column, see if it contains the string */
|
||||
or (&col ? "&string")
|
||||
or ("&col"n ? "&string")
|
||||
%end;
|
||||
%else %if &type=N and &coltype=N %then %do;
|
||||
/* if numeric match exactly */
|
||||
or (&col = &numval)
|
||||
or ("&col"n = &numval)
|
||||
%end;
|
||||
%end;
|
||||
);
|
||||
%put Search query for &table took
|
||||
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
|
||||
%if &sqlrc ne 0 %then %do;
|
||||
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table;
|
||||
%return;
|
||||
%end;
|
||||
%if %mf_nobs(mpsearch.&table)=0 %then %do;
|
||||
drop table mpsearch.&table;
|
||||
%end;
|
||||
@@ -104,4 +117,4 @@ proc sql
|
||||
|
||||
%put process finished at %sysfunc(datetime(),datetime19.);
|
||||
|
||||
%mend;
|
||||
%mend mp_searchdata;
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
@brief Logs a key value pair a control dataset
|
||||
@details If the dataset does not exist, it is created. Usage:
|
||||
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
%mp_setkeyvalue(someindex,22,type=N)
|
||||
%mp_setkeyvalue(somenewindex,somevalue)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_existds.sas
|
||||
|
||||
@param key Provide a key on which to perform the lookup
|
||||
@param value Provide a value
|
||||
@param type= either C or N will populate valc and valn respectively. C is
|
||||
default.
|
||||
default.
|
||||
@param libds= define the target table to hold the parameters
|
||||
|
||||
@version 9.2
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
%if not (%mf_existds(&libds)) %then %do;
|
||||
data &libds (index=(key/unique));
|
||||
length key $32 valc $256 valn 8 type $1;
|
||||
length key $64 valc $2048 valn 8 type $1;
|
||||
call missing(of _all_);
|
||||
stop;
|
||||
run;
|
||||
@@ -49,4 +49,4 @@
|
||||
|
||||
quit;
|
||||
|
||||
%mend;
|
||||
%mend mp_setkeyvalue;
|
||||
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
@file
|
||||
@brief Capture session start / finish times and request details
|
||||
@details For details, see http://www.rawsas.com/2015/09/logging-of-stored-process-server.html.
|
||||
@details For details, see
|
||||
https://rawsas.com/event-logging-of-stored-process-server-sessions.
|
||||
Requires a base table in the following structure (name can be changed):
|
||||
|
||||
proc sql;
|
||||
create table &libds(
|
||||
request_dttm num not null format=datetime.
|
||||
request_dttm num not null format=datetime.
|
||||
,status_cd char(4) not null
|
||||
,_metaperson varchar(100) not null
|
||||
,_program varchar(500)
|
||||
@@ -70,4 +71,4 @@
|
||||
proc append base=&libds data=&syslast nowarn;run;
|
||||
|
||||
options &etls_syntaxcheck;
|
||||
%mend;
|
||||
%mend mp_stprequests;
|
||||
@@ -1,22 +1,24 @@
|
||||
/**
|
||||
@file mp_streamfile.sas
|
||||
@file
|
||||
@brief Streams a file to _webout according to content type
|
||||
@details Will set headers using appropriate functions (SAS 9 vs Viya) and send
|
||||
content as a binary stream.
|
||||
|
||||
Usage:
|
||||
|
||||
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
filename mc url
|
||||
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
|
||||
%inc mc;
|
||||
|
||||
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
|
||||
|
||||
<h4> Dependencies </h4>
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getplatform.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
|
||||
@@ -27,14 +29,27 @@
|
||||
%macro mp_streamfile(
|
||||
contenttype=TEXT
|
||||
,inloc=
|
||||
,inref=0
|
||||
,outname=
|
||||
)/*/STORE SOURCE*/;
|
||||
|
||||
%let contentype=%upcase(&contenttype);
|
||||
%local platform; %let platform=%mf_getplatform();
|
||||
|
||||
|
||||
/**
|
||||
* check engine type to avoid the below err message:
|
||||
* > Function is only valid for filerefs using the CACHE access method.
|
||||
*/
|
||||
%local streamweb;
|
||||
%let streamweb=0;
|
||||
data _null_;
|
||||
set sashelp.vextfl(where=(upcase(fileref)="_WEBOUT"));
|
||||
if xengine='STREAM' then call symputx('streamweb',1,'l');
|
||||
run;
|
||||
|
||||
%if &contentype=ZIP %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/zip');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
@@ -42,12 +57,13 @@
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.zip'
|
||||
contenttype='application/zip'
|
||||
contenttype='application/zip'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=EXCEL %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
/* suitable for XLS format */
|
||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/vnd.ms-excel');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
@@ -55,12 +71,27 @@
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
|
||||
contenttype='application/vnd.ms-excel'
|
||||
contenttype='application/vnd.ms-excel'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=XLSX %then %do;
|
||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
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.xls'
|
||||
contenttype=
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
contentdisp="attachment; filename=&outname";
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=TEXT %then %do;
|
||||
%if &platform=SASMETA %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");
|
||||
@@ -73,7 +104,7 @@
|
||||
%end;
|
||||
%end;
|
||||
%else %if &contentype=CSV %then %do;
|
||||
%if &platform=SASMETA %then %do;
|
||||
%if &platform=SASMETA and &streamweb=1 %then %do;
|
||||
data _null_;
|
||||
rc=stpsrv_header('Content-type','application/csv');
|
||||
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
|
||||
@@ -88,7 +119,7 @@
|
||||
%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";
|
||||
contenttype="text/html";
|
||||
%end;
|
||||
%end;
|
||||
%else %do;
|
||||
@@ -96,6 +127,11 @@
|
||||
%return;
|
||||
%end;
|
||||
|
||||
%mp_binarycopy(inloc="&inloc",outref=_webout)
|
||||
%if &inref ne 0 %then %do;
|
||||
%mp_binarycopy(inref=&inref,outref=_webout)
|
||||
%end;
|
||||
%else %do;
|
||||
%mp_binarycopy(inloc="&inloc",outref=_webout)
|
||||
%end;
|
||||
|
||||
%mend;
|
||||
%mend mp_streamfile;
|
||||
|
||||
92
base/mp_testjob.sas
Normal file
92
base/mp_testjob.sas
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
@file
|
||||
@brief Runs arbitrary code for a specified amount of time
|
||||
@details Executes a series of procs and data steps to enable performance
|
||||
testing of arbitrary jobs.
|
||||
|
||||
%mp_testjob(
|
||||
duration=60*5
|
||||
)
|
||||
|
||||
@param [in] duration= the time in seconds which the job should run for. Actual
|
||||
time may vary, as the check is done in between steps. Default = 30 (seconds).
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquelibref.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_mkdir.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_testjob(duration=30
|
||||
)/*/STORE SOURCE*/;
|
||||
%local lib dir ds1 ds2 ds3 start_tm i;
|
||||
|
||||
%let start_tm=%sysfunc(datetime());
|
||||
%let duration=%sysevalf(&duration);
|
||||
|
||||
/* create a temporary library in WORK */
|
||||
%let lib=%mf_getuniquelibref();
|
||||
%let dir=%mf_getuniquename();
|
||||
%mf_mkdir(%sysfunc(pathname(work))/&dir)
|
||||
libname &lib "%sysfunc(pathname(work))/&dir";
|
||||
|
||||
/* loop through until time expires */
|
||||
%let ds1=%mf_getuniquename();
|
||||
%let ds2=%mf_getuniquename();
|
||||
%let ds3=%mf_getuniquename();
|
||||
%do i=0 %to 1;
|
||||
|
||||
/* create big dataset */
|
||||
data &lib..&ds1(compress=no );
|
||||
do x=1 to 1000000;
|
||||
randnum0=ranuni(0)*3;
|
||||
randnum1=ranuni(0)*2;
|
||||
bigchar=repeat('A',300);
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc summary ;
|
||||
class randnum0 randnum1;
|
||||
output out=&lib..&ds2;
|
||||
run;quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* add more data */
|
||||
proc sql;
|
||||
create table &lib..&ds3 as
|
||||
select *, ranuni(0)*10 as randnum2
|
||||
from &lib..&ds1
|
||||
order by randnum1;
|
||||
quit;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
proc sort data=&lib..&ds3;
|
||||
by descending x;
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
/* wait 5 seconds */
|
||||
data _null_;
|
||||
call sleep(5,1);
|
||||
run;
|
||||
%if %sysevalf( (%sysfunc(datetime())-&start_tm)>&duration ) %then %goto gate;
|
||||
|
||||
%let i=0;
|
||||
|
||||
%end;
|
||||
|
||||
%gate:
|
||||
%put time is up!;
|
||||
proc datasets lib=&lib kill;
|
||||
run;
|
||||
quit;
|
||||
libname &lib clear;
|
||||
|
||||
|
||||
%mend mp_testjob;
|
||||
264
base/mp_testservice.sas
Normal file
264
base/mp_testservice.sas
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
@file mp_testservice.sas
|
||||
@brief Will execute a test against a SASjs web service on SAS 9 or Viya
|
||||
@details Prepares the input files and retrieves the resulting datasets from
|
||||
the response JSON.
|
||||
|
||||
%mp_testjob(
|
||||
duration=60*5
|
||||
)
|
||||
|
||||
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] 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>
|
||||
@li mf_getplatform.sas
|
||||
@li mf_getuniquefileref.sas
|
||||
@li mf_getuniquename.sas
|
||||
@li mp_abort.sas
|
||||
@li mp_binarycopy.sas
|
||||
@li mv_getjobresult.sas
|
||||
@li mv_jobflow.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_testservice(program,
|
||||
inputfiles=0,
|
||||
inputparams=0,
|
||||
debug=log,
|
||||
mdebug=0,
|
||||
outlib=0,
|
||||
outref=0,
|
||||
viyaresult=WEBOUT_JSON,
|
||||
viyacontext=SAS Job Execution compute context
|
||||
)/*/STORE SOURCE*/;
|
||||
%local dbg;
|
||||
%if &mdebug=1 %then %do;
|
||||
%put &sysmacroname entry vars:;
|
||||
%put _local_;
|
||||
%end;
|
||||
%else %let dbg=*;
|
||||
|
||||
/* sanitise inputparams */
|
||||
%local pcnt;
|
||||
%let pcnt=0;
|
||||
%if &inputparams ne 0 %then %do;
|
||||
data _null_;
|
||||
set &inputparams;
|
||||
if not nvalid(name,'v7') then putlog (_all_)(=);
|
||||
else if name in (
|
||||
'program','inputfiles','inputparams','debug','outlib','outref'
|
||||
) then putlog (_all_)(=);
|
||||
else do;
|
||||
x+1;
|
||||
call symputx(name,quote(cats(value)),'l');
|
||||
call symputx('pval'!!left(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;
|
||||
|
||||
|
||||
%local fref1 webref;
|
||||
%let fref1=%mf_getuniquefileref();
|
||||
%let webref=%mf_getuniquefileref();
|
||||
|
||||
%local platform;
|
||||
%let platform=%mf_getplatform();
|
||||
%if &platform=SASMETA %then %do;
|
||||
|
||||
/* parse the input files */
|
||||
%local webcount i var;
|
||||
%if %quote(&inputfiles) ne 0 %then %do;
|
||||
%let webcount=%sysfunc(countw(&inputfiles));
|
||||
%put &=webcount;
|
||||
%do i=1 %to &webcount;
|
||||
%let var=%scan(&inputfiles,&i,%str( ));
|
||||
%local webfref&i webname&i;
|
||||
%let webref&i=%scan(&var,1,%str(:));
|
||||
%let webname&i=%scan(&var,2,%str(:));
|
||||
%put webref&i=&&webref&i;
|
||||
%put webname&i=&&webname&i;
|
||||
%end;
|
||||
%end;
|
||||
%else %let webcount=0;
|
||||
|
||||
proc stp program="&program";
|
||||
inputparam _program="&program"
|
||||
%do i=1 %to &webcount;
|
||||
%if &webcount=1 %then %do;
|
||||
_webin_fileref="&&webref&i"
|
||||
_webin_name="&&webname&i"
|
||||
%end;
|
||||
%else %do;
|
||||
_webin_fileref&i="&&webref&i"
|
||||
_webin_name&i="&&webname&i"
|
||||
%end;
|
||||
%end;
|
||||
_webin_file_count="&webcount"
|
||||
_debug="&debug"
|
||||
%do i=1 %to &pcnt;
|
||||
/* resolve name only, proc stp fetches value */
|
||||
&&pval&i=&&&&&&pval&i
|
||||
%end;
|
||||
;
|
||||
%do i=1 %to &webcount;
|
||||
inputfile &&webref&i;
|
||||
%end;
|
||||
outputfile _webout=&webref;
|
||||
run;
|
||||
|
||||
data _null_;
|
||||
infile &webref;
|
||||
file &fref1;
|
||||
input;
|
||||
length line $10000;
|
||||
if index(_infile_,'>>weboutBEGIN<<') then do;
|
||||
line=tranwrd(_infile_,'>>weboutBEGIN<<','');
|
||||
put line;
|
||||
end;
|
||||
else if index(_infile_,'>>weboutEND<<') then do;
|
||||
line=tranwrd(_infile_,'>>weboutEND<<','');
|
||||
put line;
|
||||
stop;
|
||||
end;
|
||||
else put _infile_;
|
||||
run;
|
||||
data _null_;
|
||||
infile &fref1;
|
||||
input;
|
||||
put _infile_;
|
||||
run;
|
||||
%if &outlib ne 0 %then %do;
|
||||
libname &outlib json (&fref1);
|
||||
%end;
|
||||
%if &outref ne 0 %then %do;
|
||||
filename &outref temp;
|
||||
%mp_binarycopy(inref=&webref,outref=&outref)
|
||||
%end;
|
||||
|
||||
%end;
|
||||
%else %if &platform=SASVIYA %then %do;
|
||||
|
||||
/* prepare inputparams */
|
||||
%local ds1;
|
||||
%let ds1=%mf_getuniquename();
|
||||
%if "&inputparams" ne "0" %then %do;
|
||||
proc transpose data=&inputparams out=&ds1;
|
||||
id name;
|
||||
var value;
|
||||
run;
|
||||
%end;
|
||||
%else %do;
|
||||
data &ds1;run;
|
||||
%end;
|
||||
|
||||
/* parse the input files - convert to sasjs params */
|
||||
%local webcount i var sasjs_tables;
|
||||
%if %quote(&inputfiles) ne 0 %then %do;
|
||||
%let webcount=%sysfunc(countw(&inputfiles));
|
||||
%put &=webcount;
|
||||
%do i=1 %to &webcount;
|
||||
%let var=%scan(&inputfiles,&i,%str( ));
|
||||
%local webfref&i webname&i sasjs&i.data;
|
||||
%let webref&i=%scan(&var,1,%str(:));
|
||||
%let webname&i=%scan(&var,2,%str(:));
|
||||
%put webref&i=&&webref&i;
|
||||
%put webname&i=&&webname&i;
|
||||
|
||||
%let sasjs_tables=&sasjs_tables &&webname&i;
|
||||
data _null_;
|
||||
infile &&webref&i lrecl=32767;
|
||||
input;
|
||||
if _n_=1 then call symputx("sasjs&i.data",_infile_);
|
||||
else call symputx(
|
||||
"sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_)
|
||||
);
|
||||
putlog "&sysmacroname infile: " _infile_;
|
||||
run;
|
||||
data &ds1;
|
||||
set &ds1;
|
||||
length sasjs&i.data $32767 sasjs_tables $1000;
|
||||
sasjs&i.data=symget("sasjs&i.data");
|
||||
sasjs_tables=symget("sasjs_tables");
|
||||
run;
|
||||
%end;
|
||||
%end;
|
||||
%else %let webcount=0;
|
||||
|
||||
data &ds1;
|
||||
retain _program "&program";
|
||||
retain _contextname "&viyacontext";
|
||||
set &ds1;
|
||||
putlog "&sysmacroname inputparams:";
|
||||
putlog (_all_)(=);
|
||||
run;
|
||||
|
||||
%mv_jobflow(inds=&ds1
|
||||
,maxconcurrency=1
|
||||
,outds=work.results
|
||||
,outref=&fref1
|
||||
,mdebug=&mdebug
|
||||
)
|
||||
/* show the log */
|
||||
data _null_;
|
||||
infile &fref1;
|
||||
input;
|
||||
putlog _infile_;
|
||||
run;
|
||||
/* get the uri to fetch results */
|
||||
data _null_;
|
||||
set work.results;
|
||||
call symputx('uri',uri);
|
||||
putlog "&sysmacroname: fetching results for " uri;
|
||||
run;
|
||||
/* fetch results from webout.json */
|
||||
%mv_getjobresult(uri=&uri,
|
||||
result=&viyaresult,
|
||||
outref=&outref,
|
||||
outlib=&outlib,
|
||||
mdebug=&mdebug
|
||||
)
|
||||
|
||||
%end;
|
||||
%else %do;
|
||||
%put %str(ERR)OR: Unrecognised platform: &platform;
|
||||
%end;
|
||||
|
||||
%if &mdebug=0 %then %do;
|
||||
filename &webref clear;
|
||||
%end;
|
||||
%else %do;
|
||||
%put &sysmacroname exit vars:;
|
||||
%put _local_;
|
||||
%end;
|
||||
|
||||
%mend mp_testservice;
|
||||
59
base/mp_testwritespeedlibrary.sas
Normal file
59
base/mp_testwritespeedlibrary.sas
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
@file mp_testwritespeedlibrary.sas
|
||||
@brief Tests the write speed of a new table in a SAS library
|
||||
@details Will create a new table of a certain size in an
|
||||
existing SAS library. The table will have one column,
|
||||
and will be subsequently deleted.
|
||||
|
||||
%mp_testwritespeedlibrary(
|
||||
lib=work
|
||||
,size=0.5
|
||||
,outds=work.results
|
||||
)
|
||||
|
||||
@param lib= (WORK) The library in which to create the table
|
||||
@param size= (0.1) The size in GB of the table to create
|
||||
@param outds= (WORK.RESULTS) The output dataset to be created.
|
||||
|
||||
<h4> SAS Macros </h4>
|
||||
@li mf_getuniquename.sas
|
||||
@li mf_existds.sas
|
||||
|
||||
@version 9.4
|
||||
@author Allan Bowe
|
||||
|
||||
**/
|
||||
|
||||
%macro mp_testwritespeedlibrary(lib=WORK
|
||||
,outds=work.results
|
||||
,size=0.1
|
||||
)/*/STORE SOURCE*/;
|
||||
%local ds start;
|
||||
|
||||
/* find an unused, unique name for the new table */
|
||||
%let ds=%mf_getuniquename();
|
||||
%do %until(%mf_existds(&lib..&ds)=0);
|
||||
%let ds=%mf_getuniquename();
|
||||
%end;
|
||||
|
||||
%let start=%sysfunc(datetime());
|
||||
|
||||
data &lib..&ds(compress=no keep=x);
|
||||
header=128*1024;
|
||||
size=(1073741824/8 * &size) - header;
|
||||
do x=1 to size;
|
||||
output;
|
||||
end;
|
||||
run;
|
||||
|
||||
proc sql;
|
||||
drop table &lib..&ds;
|
||||
|
||||
data &outds;
|
||||
lib="&lib";
|
||||
start_dttm=put(&start,datetime19.);
|
||||
end_dttm=put(datetime(),datetime19.);
|
||||
duration_seconds=end_dttm-start_dttm;
|
||||
run;
|
||||
|
||||
%mend mp_testwritespeedlibrary;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user