1
0
mirror of https://github.com/sasjs/core.git synced 2026-01-06 09:00:06 +00:00

Compare commits

...

40 Commits

Author SHA1 Message Date
Allan Bowe
d0bd88907f Merge pull request #336 from sasjs/issue335
Issue335
2023-06-20 11:01:29 +01:00
4e21772207 ci: sasjs cli fix 2023-06-20 11:39:19 +02:00
Allan
39f700bed2 chore: regenerating all.sas
(also adding reminder in PR template)
2023-06-20 09:46:43 +01:00
Allan
dcb7958950 feat: enabling informats to be ingested with the mp_loadformat macro 2023-06-20 00:21:19 +01:00
Allan
146610b5a7 chore(tests): fixing tests on mp_aligndecimal 2023-06-20 00:19:27 +01:00
Allan
9887efcf60 feat: new mp_aligndecimal macro
Includes a test, and an update to the mp_assertcolvals test to include a new test type (NOVAL)
2023-06-19 23:54:18 +01:00
Allan Bowe
a2c7bdafb4 chore: update yaml for build/deploy/test 2023-06-15 09:13:34 +01:00
Allan Bowe
c58b5c7a52 Merge pull request #332 from sasjs/patches
fix: invalid macro variable in mp_lockanytable.sas
2023-04-07 11:30:04 +01:00
Allan Bowe
22c7e5b4dd Merge branch 'main' into patches 2023-04-07 11:29:30 +01:00
allan
244171f8c4 fix: failing test due to missing sashelp table, also added scope check 2023-04-06 13:41:02 +01:00
Allan Bowe
fd765e2d68 Merge pull request #333 from sasjs/vpn
github action vpn
2023-04-06 12:07:56 +01:00
840cb5ef44 chore: ci - sas9.4gl.io 2023-04-06 11:16:25 +02:00
918ce96fce chore: ci - jammy ubuntu 2023-04-05 23:07:35 +02:00
f1712c34e8 chore: ci - jammy ubuntu 2023-04-05 22:58:48 +02:00
11ec20b472 chore: ci - jammy ubuntu 2023-04-05 22:54:50 +02:00
f42f111462 chore: ci - jammy ubuntu 2023-04-05 22:49:45 +02:00
907725c5ba chore: ci - jammy ubuntu 2023-04-05 22:48:09 +02:00
95b78b91e1 chore: ci release 2023-04-05 22:46:17 +02:00
e4771b9c14 ci: trigger 2023-04-05 22:24:18 +02:00
ba8190883e chore: ci added vpn for sas9 2023-04-05 22:13:13 +02:00
allan
32dd057e83 fix: avoid open file handle when the variable to find is not provided (in mf_existvar)
Includes a test that was failing and is now passing
2023-04-05 15:29:53 +01:00
allan
7471bd42a4 fix: invalid macro variable in mp_lockanytable.sas 2023-04-05 14:55:00 +01:00
munja
702a4ecd3a chore(lint): adding lineendings rule (LF) 2023-03-31 13:58:30 +01:00
Allan Bowe
5da97295ff Merge pull request #330 from sasjs/issue329
fix: closes #329 by handling the case of unlocking a table that was n…
2023-02-16 14:35:29 +00:00
munja
dc556bdef0 fix: closes #329 by handling the case of unlocking a table that was never locked in mp_lockanytable.sas, also created a corresponding test plus an extra test to check for scope leakage. all.sas was regenerated. 2023-02-16 14:33:53 +00:00
Allan Bowe
111731bf35 Merge pull request #328 from sasjs/all-contributors/add-henrik-forsell
docs: add henrik-forsell as a contributor for doc
2023-02-15 09:32:09 +00:00
allcontributors[bot]
2c526cf9dd docs: update .all-contributorsrc [skip ci] 2023-02-15 09:31:52 +00:00
allcontributors[bot]
660e02193f docs: update README.md [skip ci] 2023-02-15 09:31:51 +00:00
Allan Bowe
00b4dee86e Merge pull request #327 from henrik-forsell/doc-fix
Changed documentation wording (Column to Dataset)
2023-02-15 09:28:50 +00:00
Henrik Forsell
3913825c22 Changed documentation wording (Column to Dataset) 2023-02-15 15:57:11 +13:00
Allan Bowe
0f143d603b Merge pull request #326 from sasjs/325-error-the-keyword-parameter-maxobs-was-not-defined-with-the-macro
fix: closes #325 by including maxobs param
2023-02-13 14:05:36 +00:00
munja
f1d5fa2c0a fix: closes #325 by including maxobs param 2023-02-13 14:01:43 +00:00
Allan Bowe
a88689428f fix: updating cycjimmy/semantic-release-action to v3 2023-01-25 12:56:38 +00:00
munja
8843fa8bfc fix: adding nrstr() wrapper in mm_adduser2group 2023-01-25 12:53:10 +00:00
Allan Bowe
22d046cf5c Merge pull request #324 from sasjs/servertestfixes
fix: updating ms_runstp and ms_testservice macros to cater for latest…
2023-01-06 13:15:11 +01:00
munja
29e3eb34aa fix: updating ms_runstp and ms_testservice macros to cater for latest response JSON formats in sasjs/server 2023-01-06 12:13:36 +00:00
munja
1af52a6683 fix: adding des= macro option to mf_abort 2023-01-02 11:26:21 +00:00
munja
fc0c96dd94 chore: merge 2022-12-30 12:41:08 +00:00
munja
b9c4882553 fix: linting issues 2022-12-30 12:38:34 +00:00
Allan Bowe
011b2b185c chore: removing broken badges in README 2022-12-28 20:25:05 +00:00
35 changed files with 1717 additions and 748 deletions

View File

@@ -135,6 +135,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "henrik-forsell",
"name": "Henrik Forsell",
"avatar_url": "https://avatars.githubusercontent.com/u/109935936?v=4",
"profile": "https://github.com/henrik-forsell",
"contributions": [
"doc"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -15,3 +15,4 @@ What code changes have been made to achieve the intent.
- [ ] Code is formatted correctly (`sasjs lint`). - [ ] Code is formatted correctly (`sasjs lint`).
- [ ] Any new functionality has been unit tested. - [ ] Any new functionality has been unit tested.
- [ ] All unit tests are passing (`sasjs test`). - [ ] All unit tests are passing (`sasjs test`).
- [ ] `all.sas` has been regenerated (`python3 build.py`)

25
.github/vpn/config.ovpn vendored Normal file
View File

@@ -0,0 +1,25 @@
# Client
client
tls-client
dev tun
# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934)
proto tcp
remote vpn.4gl.io 7494
resolv-retry infinite
cipher AES-256-CBC
auth SHA256
script-security 2
keepalive 10 120
remote-cert-tls server
# Keys
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1
# Security
nobind
persist-key
persist-tun
verb 3

View File

@@ -15,7 +15,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Semantic Release - name: Semantic Release
uses: cycjimmy/semantic-release-action@v2 uses: cycjimmy/semantic-release-action@v3
env: env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -21,32 +21,49 @@ jobs:
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-jammy.list
sudo apt update
sudo apt install openvpn3=17~betaUb22042+jammy
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install doxygen run: sudo apt-get install doxygen
- name: Install dependencies - name: Install dependencies
run: npm ci run: npm ci
- name: Check code style - name: Check code style (aborts if errors found)
run: npm run lint run: npx sasjs lint
- name: Add client - name: Add client
run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya run: echo "CLIENT=${{secrets.SAS9_4GL_IO_CLIENT}}"> .env.server
- name: Add secret
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya
- name: Add access token - name: Add access token
run: echo "ACCESS_TOKEN=${{secrets.ACCESS_TOKEN}}" >> .env.viya run: echo "ACCESS_TOKEN=${{secrets.SAS9_4GL_IO_ACCESS_TOKEN}}" >> .env.server
- name: Add refresh token - name: Build & Deploy Project to SAS server
run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya run: npx sasjs cbd -t server
- name: Build Project - name: Run all tests
run: npm run build run: npx sasjs test -t server
- name: Run SASjs tests
run: npm run test
env: env:
CI: true CI: true
CLIENT: ${{secrets.CLIENT}} CLIENT: ${{secrets.CLIENT}}

View File

@@ -4,6 +4,7 @@
"hasDoxygenHeader": true, "hasDoxygenHeader": true,
"hasMacroNameInMend": true, "hasMacroNameInMend": true,
"hasMacroParentheses": true, "hasMacroParentheses": true,
"lineEndings": "lf",
"noGremlins": true, "noGremlins": true,
"noNestedMacros": false, "noNestedMacros": false,
"noSpacesInFileNames": true, "noSpacesInFileNames": true,

View File

@@ -2,8 +2,6 @@
[![npm package][npm-image]][npm-url] [![npm package][npm-image]][npm-url]
[![Github Workflow][githubworkflow-image]][githubworkflow-url] [![Github Workflow][githubworkflow-image]][githubworkflow-url]
![npm](https://img.shields.io/npm/dt/@sasjs/core) ![npm](https://img.shields.io/npm/dt/@sasjs/core)
![Snyk Vulnerabilities for npm package](https://img.shields.io/snyk/vulnerabilities/npm/@sasjs/core)
[![License](https://img.shields.io/apm/l/atomic-design-ui.svg)](/LICENSE)
![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core) ![GitHub top language](https://img.shields.io/github/languages/top/sasjs/core)
[![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sasjs/core)](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed) [![GitHub closed issues](https://img.shields.io/github/issues-closed-raw/sasjs/core)](https://github.com/sasjs/core/issues?q=is%3Aissue+is%3Aclosed)
[![GitHub issues](https://img.shields.io/github/issues-raw/sasjs/core)](https://github.com/sasjs/core/issues) [![GitHub issues](https://img.shields.io/github/issues-raw/sasjs/core)](https://github.com/sasjs/core/issues)
@@ -248,7 +246,7 @@ The following repositories are also worth checking out:
## Contributors ✨ ## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-) [![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -256,22 +254,25 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- markdownlint-disable --> <!-- markdownlint-disable -->
<table> <table>
<tr> <tbody>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td> <tr>
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt="Allan Bowe"/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt="rafgag"/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt="Trevor Moody"/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td> <td align="center" valign="top" width="14.28%"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt="Krishna Acondy"/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt="Muhammad Saad "/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td> <td align="center" valign="top" width="14.28%"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt="Yury Shkoda"/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
</tr> <td align="center" valign="top" width="14.28%"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt="Mihajlo Medjedovic"/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<tr> </tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td> <tr>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt="kkchandok"/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt="Vladislav Parhomchik"/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt="Vignesh T."/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/yabwon"><img src="https://avatars.githubusercontent.com/u/9314894?v=4?s=100" width="100px;" alt="Bart Jablonski"/><br /><sub><b>Bart Jablonski</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=yabwon" title="Code">💻</a></td>
</tr> <td align="center" valign="top" width="14.28%"><a href="https://bandism.net/"><img src="https://avatars.githubusercontent.com/u/22633385?v=4?s=100" width="100px;" alt="Ikko Ashimine"/><br /><sub><b>Ikko Ashimine</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=eltociear" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/henrik-forsell"><img src="https://avatars.githubusercontent.com/u/109935936?v=4?s=100" width="100px;" alt="Henrik Forsell"/><br /><sub><b>Henrik Forsell</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=henrik-forsell" title="Documentation">📖</a></td>
</tr>
</tbody>
</table> </table>
<!-- markdownlint-restore --> <!-- markdownlint-restore -->

273
all.sas
View File

@@ -30,7 +30,7 @@ options noquotelenmax;
**/ **/
%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1) %macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
)/*/STORE SOURCE*/; )/des='ungraceful abort' /*STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return; %if not(%eval(%unquote(&iftrue))) %then %return;
@@ -42,7 +42,8 @@ options noquotelenmax;
%mend mf_abort; %mend mf_abort;
/** @endcond *//** /** @endcond */
/**
@file @file
@brief de-duplicates a macro string @brief de-duplicates a macro string
@details Removes all duplicates from a string of words. A delimeter can be @details Removes all duplicates from a string of words. A delimeter can be
@@ -311,13 +312,17 @@ https://github.com/yabwon/SAS_PACKAGES/blob/main/packages/baseplus.md#functionex
%local dsid rc; %local dsid rc;
%let dsid=%sysfunc(open(&libds,is)); %let dsid=%sysfunc(open(&libds,is));
%if &dsid=0 or %length(&var)=0 %then %do; %if &dsid=0 %then %do;
%put %sysfunc(sysmsg()); %put %sysfunc(sysmsg());
0 0
%end;
%else %if %length(&var)=0 %then %do;
0
%let rc=%sysfunc(close(&dsid));
%end; %end;
%else %do; %else %do;
%sysfunc(varnum(&dsid,&var)) %sysfunc(varnum(&dsid,&var))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend mf_existvar; %mend mf_existvar;
@@ -2653,6 +2658,100 @@ and %superq(SYSPROCESSNAME) ne %str(Compute Server)
%mend mp_abort; %mend mp_abort;
/** @endcond */ /** @endcond */
/**
@file
@brief Apply leading blanks to align numbers vertically in a char variable
@details This is particularly useful when storing numbers (as character) that
need to be sorted.
It works by splitting the number left and right of the decimal place, and
aligning it accordingly. A temporary variable is created as part of this
process (which is automatically dropped)
The macro can be used only in data step, eg as follows:
data _null_;
length myvar $50;
do i=1 to 1000 by 50;
if mod(i,2)=0 then j=ranuni(0)*i*100;
else j=i*100;
%mp_aligndecimal(myvar,width=7)
leading_spaces=length(myvar)-length(cats(myvar));
putlog +leading_spaces myvar;
end;
run;
The generated code will look something like this:
length aligndp4e49996 $7;
if index(myvar,'.') then do;
aligndp4e49996=cats(scan(myvar,1,'.'));
aligndp4e49996=right(aligndp4e49996);
myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.'));
end;
else do;
aligndp4e49996=myvar;
aligndp4e49996=right(aligndp4e49996);
myvar=aligndp4e49996;
end;
Results (myvar variable):
0.7683559324
122.8232796
99419.50552
42938.5143414
763.3799189
15170.606073
15083.285773
85443.198707
2022999.2251
12038.658867
1350582.6734
52777.258221
11723.347628
33101.268376
6181622.8603
7390614.0669
73384.537893
1788362.1016
2774586.2219
7998580.8415
@param var The (data step) variable to create
@param width= (8) The number of characters BEFORE the decimal point
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
<h4> Related Programs </h4>
@li mp_aligndecimal.test.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_aligndecimal(var,width=8);
%local tmpvar;
%let tmpvar=%mf_getuniquename(prefix=aligndp);
length &tmpvar $&width;
if index(&var,'.') then do;
&tmpvar=cats(scan(&var,1,'.'));
&tmpvar=right(&tmpvar);
&var=&tmpvar!!'.'!!cats(scan(&var,2,'.'));
end;
else do;
&tmpvar=cats(&var);
&tmpvar=right(&tmpvar);
&var=&tmpvar;
end;
drop &tmpvar;
%mend mp_aligndecimal;
/** /**
@file @file
@brief Append (concatenate) two or more files. @brief Append (concatenate) two or more files.
@@ -2913,7 +3012,7 @@ run;
results. If it does not exist, it will be created, with the following format: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---| |---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns| |User Provided description|PASS|Dataset &inds contained ALL columns|
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -2987,7 +3086,7 @@ run;
results. If it does not exist, it will be created, with the following format: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---| |---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns| |User Provided description|PASS|Dataset &inds contained ALL columns|
<h4> Related Macros </h4> <h4> Related Macros </h4>
@@ -3132,6 +3231,7 @@ run;
@param [in] test= (ALLVALS) The test to apply. Valid values are: @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 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 @li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
@li NOVAL - Test is a PASS if there are NO matches in checkvals
@param [out] outds= (work.test_results) The output dataset to contain the @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: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
@@ -3187,7 +3287,7 @@ run;
%let test=%upcase(&test); %let test=%upcase(&test);
%if &test ne ALLVALS and &test ne ANYVAL %then %do; %if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do;
%mp_abort( %mp_abort(
mac=&sysmacroname, mac=&sysmacroname,
msg=%str(Invalid test - &test) msg=%str(Invalid test - &test)
@@ -3198,12 +3298,12 @@ run;
%let result=-1; %let result=-1;
%let orig=-1; %let orig=-1;
proc sql noprint; proc sql noprint;
select count(*) into: result select count(*) into: result trimmed
from &lib..&ds from &lib..&ds
where &col not in ( where &col not in (
select &ccol from &clib..&cds select &ccol from &clib..&cds
); );
select count(*) into: orig from &lib..&ds; select count(*) into: orig trimmed from &lib..&ds;
quit; quit;
%local notfound tmp1 tmp2; %local notfound tmp1 tmp2;
@@ -3235,7 +3335,7 @@ run;
length test_description $256 test_result $4 test_comments $256; length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc'); test_description=symget('desc');
test_result='FAIL'; test_result='FAIL';
test_comments="&sysmacroname: &lib..&ds..&col has &result values " test_comments="&sysmacroname: &lib..&ds..&col has &result/&orig values "
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound'); !!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
%if &test=ANYVAL %then %do; %if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS'; if &result < &orig then test_result='PASS';
@@ -3243,6 +3343,9 @@ run;
%else %if &test=ALLVALS %then %do; %else %if &test=ALLVALS %then %do;
if &result=0 then test_result='PASS'; if &result=0 then test_result='PASS';
%end; %end;
%else %if &test=NOVAL %then %do;
if &result=&orig then test_result='PASS';
%end;
%else %do; %else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test"; test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end; %end;
@@ -4018,6 +4121,7 @@ run;
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mf_getvarformat.sas @li mf_getvarformat.sas
@li mp_aligndecimal.sas
@li mp_getformats.sas @li mp_getformats.sas
@li mp_loadformat.sas @li mp_loadformat.sas
@li mp_ds2fmtds.sas @li mp_ds2fmtds.sas
@@ -4059,13 +4163,13 @@ run;
data &cntlout; data &cntlout;
if 0 then set &ddlds; if 0 then set &ddlds;
set &cntlds; set &cntlds;
if type="N" then do; if type in ("I","N") then do; /* numeric (in)format */
start=cats(start); %mp_aligndecimal(start,width=16)
end=cats(end); %mp_aligndecimal(end,width=16)
end; end;
run; run;
proc sort; proc sort;
by fmtname start; by type fmtname start;
run; run;
proc sql; proc sql;
@@ -10025,6 +10129,7 @@ select distinct lowcase(memname)
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mddl_dc_difftable.sas @li mddl_dc_difftable.sas
@li mddl_dc_locktable.sas @li mddl_dc_locktable.sas
@li mp_aligndecimal.sas
@li mp_loadformat.test.sas @li mp_loadformat.test.sas
@li mp_lockanytable.sas @li mp_lockanytable.sas
@li mp_stackdiffs.sas @li mp_stackdiffs.sas
@@ -10114,7 +10219,16 @@ run;
* First, extract only relevant formats from the catalog * First, extract only relevant formats from the catalog
*/ */
proc sql noprint; proc sql noprint;
select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds; select distinct
case
when type='N' then upcase(fmtname)
when type='C' then cats('$',upcase(fmtname))
when type='I' then cats('@',upcase(fmtname))
when type='J' then cats('@$',upcase(fmtname))
else "&sysmacroname:UNHANDLED"
end
into: fmtlist separated by ' '
from &libds;
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts) %mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
@@ -10126,16 +10240,24 @@ select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
data &inlibds; data &inlibds;
length &delete_col $3; length &delete_col $3;
if 0 then set &template; if 0 then set &template;
length start end $10000;
set &libds; set &libds;
if &delete_col='' then &delete_col='No'; if &delete_col='' then &delete_col='No';
fmtname=upcase(fmtname); fmtname=upcase(fmtname);
type=upcase(type);
if missing(type) then do; if missing(type) then do;
if substr(fmtname,1,1)='$' then type='C'; if substr(fmtname,1,1)='@' then do;
else type='N'; if substr(fmtname,2,1)='$' then type='J';
else type='I';
end;
else do;
if substr(fmtname,1,1)='$' then type='C';
else type='N';
end;
end; end;
if type='N' then do; if type in ('N','I') then do;
start=cats(start); %mp_aligndecimal(start,width=16)
end=cats(end); %mp_aligndecimal(end,width=16)
end; end;
run; run;
@@ -10149,9 +10271,10 @@ create table &outds_add(drop=&delete_col) as
left join &base_fmts b left join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where b.fmtname is null where b.fmtname is null
and upcase(a.&delete_col) ne "YES" and upcase(a.&delete_col) ne "YES"
order by fmtname, start;; order by type, fmtname, start;
/** /**
* Identify deleted records * Identify deleted records
@@ -10162,8 +10285,9 @@ create table &outds_del(drop=&delete_col) as
inner join &base_fmts b inner join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where upcase(a.&delete_col)="YES" where upcase(a.&delete_col)="YES"
order by fmtname, start; order by type, fmtname, start;
/** /**
* Identify modified records * Identify modified records
@@ -10174,8 +10298,9 @@ create table &outds_mod (drop=&delete_col) as
inner join &base_fmts b inner join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where upcase(a.&delete_col) ne "YES" where upcase(a.&delete_col) ne "YES"
order by fmtname, start; order by type, fmtname, start;
options ibufsize=&ibufsize; options ibufsize=&ibufsize;
@@ -10192,13 +10317,13 @@ options ibufsize=&ibufsize;
&outds_add(in=add) &outds_add(in=add)
&outds_del(in=del); &outds_del(in=del);
if not del and not mod; if not del and not mod;
by fmtname start; by type fmtname start;
run; run;
data &stagedata; data &stagedata;
set &ds1 &outds_mod; set &ds1 &outds_mod;
run; run;
proc sort; proc sort;
by fmtname start; by type fmtname start;
run; run;
%end; %end;
/* mp abort needs to run outside of conditional blocks */ /* mp abort needs to run outside of conditional blocks */
@@ -10246,7 +10371,7 @@ options ibufsize=&ibufsize;
%mp_storediffs(&libcat-FC %mp_storediffs(&libcat-FC
,&base_fmts ,&base_fmts
,FMTNAME START ,TYPE FMTNAME START
,delds=&outds_del ,delds=&outds_del
,modds=&outds_mod ,modds=&outds_mod
,appds=&outds_add ,appds=&outds_add
@@ -10450,7 +10575,7 @@ run;
data _null_; data _null_;
putlog 'NOTE-' / 'NOTE-'; putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: table locked, waiting "@; putlog "NOTE- &sysmacroname: table locked, waiting "@;
putlog "%sysfunc(sleep(&loop_inc)) seconds.. "; putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";
putlog "NOTE- (iteration &x of &loops)"; putlog "NOTE- (iteration &x of &loops)";
putlog 'NOTE-' / 'NOTE-'; putlog 'NOTE-' / 'NOTE-';
run; run;
@@ -10483,7 +10608,10 @@ run;
where LOCK_LIB ="&lib" and LOCK_DS="&ds"; where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit; quit;
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
%if &status=LOCKED %then %do; %if &sqlobs=0 %then %do;
%put %str(WAR)NING: &lib..&ds has never been locked!;
%end;
%else %if &status=LOCKED %then %do;
data _null_; data _null_;
putlog "&sysmacroname: unlocking &lib..&ds:"; putlog "&sysmacroname: unlocking &lib..&ds:";
run; run;
@@ -13948,14 +14076,20 @@ ods package close;
proc sql; proc sql;
create table &libds( create table &libds(
FMTNAME char(32) label='Format name' TYPE char(1) label='Type of format'
,FMTNAME char(32) label='Format name'
/* /*
to accommodate larger START values, mp_loadformat.sas will need the to accommodate larger START values, mp_loadformat.sas will need the
SQL dependency removed (proc sql needs to accommodate 3 index values in SQL dependency removed (proc sql needs to accommodate 3 index values in
a 32767 ibufsize limit) a 32767 ibufsize limit)
*/ */
,START char(10000) label='Starting value for format' ,START char(10000) label='Starting value for format'
,END char(32767) label='Ending value for format' /*
Keep lengths of START and END the same to avoid this err:
"Start is greater than end: -<."
Similar usage note: https://support.sas.com/kb/69/330.html
*/
,END char(10000) label='Ending value for format'
,LABEL char(32767) label='Format value label' ,LABEL char(32767) label='Format value label'
,MIN num length=3 label='Minimum length' ,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length' ,MAX num length=3 label='Maximum length'
@@ -13966,7 +14100,6 @@ create table &libds(
,MULT num label='Multiplier' ,MULT num label='Multiplier'
,FILL char(1) label='Fill character' ,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?' ,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion' ,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion' ,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information' ,HLO char(13) label='Additional information'
@@ -14060,7 +14193,8 @@ run;
filename __us2grp temp; filename __us2grp temp;
proc metadata in= "<UpdateMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata> proc metadata in= "<UpdateMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata>
<Person Id='&uuri'><IdentityGroups><IdentityGroup ObjRef='&guri' /> <Person Id='%nrstr(&uuri)'>
<IdentityGroups><IdentityGroup ObjRef='%nrstr(&guri)' />
</IdentityGroups></Person></Metadata> </IdentityGroups></Person></Metadata>
<NS>SAS</NS><Flags>268435456</Flags></UpdateMetadata>" <NS>SAS</NS><Flags>268435456</Flags></UpdateMetadata>"
out=__us2grp verbose; out=__us2grp verbose;
@@ -14077,7 +14211,8 @@ run;
filename __us2grp clear; filename __us2grp clear;
%mend mm_adduser2group;/** %mend mm_adduser2group;
/**
@file @file
@brief Assigns library directly using details from metadata @brief Assigns library directly using details from metadata
@details Queries metadata to get the libname definition then allocates the @details Queries metadata to get the libname definition then allocates the
@@ -16540,9 +16675,11 @@ data _null_;
put ' '; put ' ';
put '%mend mm_webout; '; put '%mend mm_webout; ';
/* WEBOUT END */ /* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO';
put ' ,maxobs=MAX';
put ');';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing'; put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
put ' ,showmeta=&showmeta'; put ' ,showmeta=&showmeta,maxobs=&maxobs';
put ' )'; put ' )';
put '%mend;'; put '%mend;';
run; run;
@@ -22105,8 +22242,9 @@ options &optval;
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas @li mf_getuniquename.sas
@li mp_abort.sas @li mp_abort.sas
@li mp_chop.sas
**/ **/
@@ -22219,7 +22357,10 @@ run;
run; run;
%end; %end;
filename &outref temp lrecl=32767; %local resp_path;
%let resp_path=%sysfunc(pathname(work))/%mf_getuniquename();
filename &outref "&resp_path" lrecl=32767;
/* prepare request*/ /* prepare request*/
proc http method='POST' headerin=&authref in=&mainref out=&outref proc http method='POST' headerin=&authref in=&mainref out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131"; url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
@@ -22227,6 +22368,7 @@ proc http method='POST' headerin=&authref in=&mainref out=&outref
debug level=2; debug level=2;
%end; %end;
run; run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201) %if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1 or &mdebug=1
%then %do; %then %do;
@@ -22242,11 +22384,22 @@ or &mdebug=1
options &optval; options &optval;
%if &outlogds ne _null_ or &mdebug=1 %then %do; %if &outlogds ne _null_ or &mdebug=1 %then %do;
%local dumplib; %local matchstr chopout;
%let dumplib=%mf_getuniquelibref(); %let matchstr=SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784;
libname &dumplib json fileref=&outref; %let chopout=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop);
%mp_chop("&resp_path"
,matchvar=matchstr
,keep=LAST
,matchpoint=END
,outfile="&chopout"
,mdebug=&mdebug
)
data &outlogds; data &outlogds;
set &dumplib..log; infile "&chopout" lrecl=2000;
length line $2000;
line=_infile_;
%if &mdebug=1 %then %do; %if &mdebug=1 %then %do;
putlog line=; putlog line=;
%end; %end;
@@ -22373,50 +22526,38 @@ run;
) )
/* SASjs services have the _webout embedded in wrapper JSON */ /* chop out JSON section */
/* Files can also be very large - so use a dedicated macro to chop it out */ %local matchstr chopout;
%local matchstr1 matchstr2 ; %let matchstr=SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784;
%let matchstr1={"status":"success","_webout":{; %let chopout=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop);
%let matchstr2=},"log":[{;
%let chopout1=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop1);
%let chopout2=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop2);
%mp_chop("%sysfunc(pathname(&fref1,F))" %mp_chop("%sysfunc(pathname(&fref1,F))"
,matchvar=matchstr1 ,matchvar=matchstr
,keep=LAST
,matchpoint=END
,offset=-1
,outfile="&chopout1"
,mdebug=&mdebug
)
%mp_chop("&chopout1"
,matchvar=matchstr2
,keep=FIRST ,keep=FIRST
,matchpoint=START ,matchpoint=START
,offset=1 ,offset=-1
,outfile="&chopout2" ,outfile="&chopout"
,mdebug=&mdebug ,mdebug=&mdebug
) )
%if &outlib ne 0 %then %do; %if &outlib ne 0 %then %do;
libname &outlib json "&chopout2"; libname &outlib json "&chopout";
%end; %end;
%if &outref ne 0 %then %do; %if &outref ne 0 %then %do;
filename &outref "&chopout2"; filename &outref "&chopout";
%end; %end;
%if &mdebug=0 %then %do; %if &mdebug=0 %then %do;
filename &webref clear; filename &webref clear;
filename &fref1 clear; filename &fref1 clear;
filename &fref2 clear;
%end; %end;
%else %do; %else %do;
%put &sysmacroname exit vars:; %put &sysmacroname exit vars:;
%put _local_; %put _local_;
%end; %end;
%mend ms_testservice;/** %mend ms_testservice;
/**
@file @file
@brief Send data to/from sasjs/server @brief Send data to/from sasjs/server
@details This macro should be added to the start of each web service, @details This macro should be added to the start of each web service,

View File

@@ -12,7 +12,7 @@
**/ **/
%macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1) %macro mf_abort(mac=mf_abort.sas, msg=, iftrue=%str(1=1)
)/*/STORE SOURCE*/; )/des='ungraceful abort' /*STORE SOURCE*/;
%if not(%eval(%unquote(&iftrue))) %then %return; %if not(%eval(%unquote(&iftrue))) %then %return;
@@ -24,4 +24,4 @@
%mend mf_abort; %mend mf_abort;
/** @endcond */ /** @endcond */

View File

@@ -25,13 +25,17 @@
%local dsid rc; %local dsid rc;
%let dsid=%sysfunc(open(&libds,is)); %let dsid=%sysfunc(open(&libds,is));
%if &dsid=0 or %length(&var)=0 %then %do; %if &dsid=0 %then %do;
%put %sysfunc(sysmsg()); %put %sysfunc(sysmsg());
0 0
%end;
%else %if %length(&var)=0 %then %do;
0
%let rc=%sysfunc(close(&dsid));
%end; %end;
%else %do; %else %do;
%sysfunc(varnum(&dsid,&var)) %sysfunc(varnum(&dsid,&var))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend mf_existvar; %mend mf_existvar;

94
base/mp_aligndecimal.sas Normal file
View File

@@ -0,0 +1,94 @@
/**
@file
@brief Apply leading blanks to align numbers vertically in a char variable
@details This is particularly useful when storing numbers (as character) that
need to be sorted.
It works by splitting the number left and right of the decimal place, and
aligning it accordingly. A temporary variable is created as part of this
process (which is automatically dropped)
The macro can be used only in data step, eg as follows:
data _null_;
length myvar $50;
do i=1 to 1000 by 50;
if mod(i,2)=0 then j=ranuni(0)*i*100;
else j=i*100;
%mp_aligndecimal(myvar,width=7)
leading_spaces=length(myvar)-length(cats(myvar));
putlog +leading_spaces myvar;
end;
run;
The generated code will look something like this:
length aligndp4e49996 $7;
if index(myvar,'.') then do;
aligndp4e49996=cats(scan(myvar,1,'.'));
aligndp4e49996=right(aligndp4e49996);
myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.'));
end;
else do;
aligndp4e49996=myvar;
aligndp4e49996=right(aligndp4e49996);
myvar=aligndp4e49996;
end;
Results (myvar variable):
0.7683559324
122.8232796
99419.50552
42938.5143414
763.3799189
15170.606073
15083.285773
85443.198707
2022999.2251
12038.658867
1350582.6734
52777.258221
11723.347628
33101.268376
6181622.8603
7390614.0669
73384.537893
1788362.1016
2774586.2219
7998580.8415
@param var The (data step) variable to create
@param width= (8) The number of characters BEFORE the decimal point
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
<h4> Related Programs </h4>
@li mp_aligndecimal.test.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_aligndecimal(var,width=8);
%local tmpvar;
%let tmpvar=%mf_getuniquename(prefix=aligndp);
length &tmpvar $&width;
if index(&var,'.') then do;
&tmpvar=cats(scan(&var,1,'.'));
&tmpvar=right(&tmpvar);
&var=&tmpvar!!'.'!!cats(scan(&var,2,'.'));
end;
else do;
&tmpvar=cats(&var);
&tmpvar=right(&tmpvar);
&var=&tmpvar;
end;
drop &tmpvar;
%mend mp_aligndecimal;

View File

@@ -22,7 +22,7 @@
results. If it does not exist, it will be created, with the following format: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---| |---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns| |User Provided description|PASS|Dataset &inds contained ALL columns|
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe

View File

@@ -41,7 +41,7 @@
results. If it does not exist, it will be created, with the following format: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---| |---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns| |User Provided description|PASS|Dataset &inds contained ALL columns|
<h4> Related Macros </h4> <h4> Related Macros </h4>

View File

@@ -42,6 +42,7 @@
@param [in] test= (ALLVALS) The test to apply. Valid values are: @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 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 @li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
@li NOVAL - Test is a PASS if there are NO matches in checkvals
@param [out] outds= (work.test_results) The output dataset to contain the @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: results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
@@ -97,7 +98,7 @@
%let test=%upcase(&test); %let test=%upcase(&test);
%if &test ne ALLVALS and &test ne ANYVAL %then %do; %if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do;
%mp_abort( %mp_abort(
mac=&sysmacroname, mac=&sysmacroname,
msg=%str(Invalid test - &test) msg=%str(Invalid test - &test)
@@ -108,12 +109,12 @@
%let result=-1; %let result=-1;
%let orig=-1; %let orig=-1;
proc sql noprint; proc sql noprint;
select count(*) into: result select count(*) into: result trimmed
from &lib..&ds from &lib..&ds
where &col not in ( where &col not in (
select &ccol from &clib..&cds select &ccol from &clib..&cds
); );
select count(*) into: orig from &lib..&ds; select count(*) into: orig trimmed from &lib..&ds;
quit; quit;
%local notfound tmp1 tmp2; %local notfound tmp1 tmp2;
@@ -145,7 +146,7 @@
length test_description $256 test_result $4 test_comments $256; length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc'); test_description=symget('desc');
test_result='FAIL'; test_result='FAIL';
test_comments="&sysmacroname: &lib..&ds..&col has &result values " test_comments="&sysmacroname: &lib..&ds..&col has &result/&orig values "
!!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound'); !!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
%if &test=ANYVAL %then %do; %if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS'; if &result < &orig then test_result='PASS';
@@ -153,6 +154,9 @@
%else %if &test=ALLVALS %then %do; %else %if &test=ALLVALS %then %do;
if &result=0 then test_result='PASS'; if &result=0 then test_result='PASS';
%end; %end;
%else %if &test=NOVAL %then %do;
if &result=&orig then test_result='PASS';
%end;
%else %do; %else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test"; test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end; %end;

View File

@@ -28,6 +28,7 @@
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mf_getvarformat.sas @li mf_getvarformat.sas
@li mp_aligndecimal.sas
@li mp_getformats.sas @li mp_getformats.sas
@li mp_loadformat.sas @li mp_loadformat.sas
@li mp_ds2fmtds.sas @li mp_ds2fmtds.sas
@@ -69,13 +70,13 @@ run;
data &cntlout; data &cntlout;
if 0 then set &ddlds; if 0 then set &ddlds;
set &cntlds; set &cntlds;
if type="N" then do; if type in ("I","N") then do; /* numeric (in)format */
start=cats(start); %mp_aligndecimal(start,width=16)
end=cats(end); %mp_aligndecimal(end,width=16)
end; end;
run; run;
proc sort; proc sort;
by fmtname start; by type fmtname start;
run; run;
proc sql; proc sql;

View File

@@ -45,6 +45,7 @@
<h4> Related Macros </h4> <h4> Related Macros </h4>
@li mddl_dc_difftable.sas @li mddl_dc_difftable.sas
@li mddl_dc_locktable.sas @li mddl_dc_locktable.sas
@li mp_aligndecimal.sas
@li mp_loadformat.test.sas @li mp_loadformat.test.sas
@li mp_lockanytable.sas @li mp_lockanytable.sas
@li mp_stackdiffs.sas @li mp_stackdiffs.sas
@@ -134,7 +135,16 @@ run;
* First, extract only relevant formats from the catalog * First, extract only relevant formats from the catalog
*/ */
proc sql noprint; proc sql noprint;
select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds; select distinct
case
when type='N' then upcase(fmtname)
when type='C' then cats('$',upcase(fmtname))
when type='I' then cats('@',upcase(fmtname))
when type='J' then cats('@$',upcase(fmtname))
else "&sysmacroname:UNHANDLED"
end
into: fmtlist separated by ' '
from &libds;
%mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts) %mp_cntlout(libcat=&libcat,fmtlist=&fmtlist,cntlout=&base_fmts)
@@ -146,16 +156,24 @@ select distinct upcase(fmtname) into: fmtlist separated by ' ' from &libds;
data &inlibds; data &inlibds;
length &delete_col $3; length &delete_col $3;
if 0 then set &template; if 0 then set &template;
length start end $10000;
set &libds; set &libds;
if &delete_col='' then &delete_col='No'; if &delete_col='' then &delete_col='No';
fmtname=upcase(fmtname); fmtname=upcase(fmtname);
type=upcase(type);
if missing(type) then do; if missing(type) then do;
if substr(fmtname,1,1)='$' then type='C'; if substr(fmtname,1,1)='@' then do;
else type='N'; if substr(fmtname,2,1)='$' then type='J';
else type='I';
end;
else do;
if substr(fmtname,1,1)='$' then type='C';
else type='N';
end;
end; end;
if type='N' then do; if type in ('N','I') then do;
start=cats(start); %mp_aligndecimal(start,width=16)
end=cats(end); %mp_aligndecimal(end,width=16)
end; end;
run; run;
@@ -169,9 +187,10 @@ create table &outds_add(drop=&delete_col) as
left join &base_fmts b left join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where b.fmtname is null where b.fmtname is null
and upcase(a.&delete_col) ne "YES" and upcase(a.&delete_col) ne "YES"
order by fmtname, start;; order by type, fmtname, start;
/** /**
* Identify deleted records * Identify deleted records
@@ -182,8 +201,9 @@ create table &outds_del(drop=&delete_col) as
inner join &base_fmts b inner join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where upcase(a.&delete_col)="YES" where upcase(a.&delete_col)="YES"
order by fmtname, start; order by type, fmtname, start;
/** /**
* Identify modified records * Identify modified records
@@ -194,8 +214,9 @@ create table &outds_mod (drop=&delete_col) as
inner join &base_fmts b inner join &base_fmts b
on a.fmtname=b.fmtname on a.fmtname=b.fmtname
and a.start=b.start and a.start=b.start
and a.type=b.type
where upcase(a.&delete_col) ne "YES" where upcase(a.&delete_col) ne "YES"
order by fmtname, start; order by type, fmtname, start;
options ibufsize=&ibufsize; options ibufsize=&ibufsize;
@@ -212,13 +233,13 @@ options ibufsize=&ibufsize;
&outds_add(in=add) &outds_add(in=add)
&outds_del(in=del); &outds_del(in=del);
if not del and not mod; if not del and not mod;
by fmtname start; by type fmtname start;
run; run;
data &stagedata; data &stagedata;
set &ds1 &outds_mod; set &ds1 &outds_mod;
run; run;
proc sort; proc sort;
by fmtname start; by type fmtname start;
run; run;
%end; %end;
/* mp abort needs to run outside of conditional blocks */ /* mp abort needs to run outside of conditional blocks */
@@ -266,7 +287,7 @@ options ibufsize=&ibufsize;
%mp_storediffs(&libcat-FC %mp_storediffs(&libcat-FC
,&base_fmts ,&base_fmts
,FMTNAME START ,TYPE FMTNAME START
,delds=&outds_del ,delds=&outds_del
,modds=&outds_mod ,modds=&outds_mod
,appds=&outds_add ,appds=&outds_add

View File

@@ -167,7 +167,7 @@ run;
data _null_; data _null_;
putlog 'NOTE-' / 'NOTE-'; putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: table locked, waiting "@; putlog "NOTE- &sysmacroname: table locked, waiting "@;
putlog "%sysfunc(sleep(&loop_inc)) seconds.. "; putlog "%sysfunc(sleep(&loop_secs)) seconds.. ";
putlog "NOTE- (iteration &x of &loops)"; putlog "NOTE- (iteration &x of &loops)";
putlog 'NOTE-' / 'NOTE-'; putlog 'NOTE-' / 'NOTE-';
run; run;
@@ -200,7 +200,10 @@ run;
where LOCK_LIB ="&lib" and LOCK_DS="&ds"; where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit; quit;
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc; %if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
%if &status=LOCKED %then %do; %if &sqlobs=0 %then %do;
%put %str(WAR)NING: &lib..&ds has never been locked!;
%end;
%else %if &status=LOCKED %then %do;
data _null_; data _null_;
putlog "&sysmacroname: unlocking &lib..&ds:"; putlog "&sysmacroname: unlocking &lib..&ds:";
run; run;

View File

@@ -12,14 +12,20 @@
proc sql; proc sql;
create table &libds( create table &libds(
FMTNAME char(32) label='Format name' TYPE char(1) label='Type of format'
,FMTNAME char(32) label='Format name'
/* /*
to accommodate larger START values, mp_loadformat.sas will need the to accommodate larger START values, mp_loadformat.sas will need the
SQL dependency removed (proc sql needs to accommodate 3 index values in SQL dependency removed (proc sql needs to accommodate 3 index values in
a 32767 ibufsize limit) a 32767 ibufsize limit)
*/ */
,START char(10000) label='Starting value for format' ,START char(10000) label='Starting value for format'
,END char(32767) label='Ending value for format' /*
Keep lengths of START and END the same to avoid this err:
"Start is greater than end: -<."
Similar usage note: https://support.sas.com/kb/69/330.html
*/
,END char(10000) label='Ending value for format'
,LABEL char(32767) label='Format value label' ,LABEL char(32767) label='Format value label'
,MIN num length=3 label='Minimum length' ,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length' ,MAX num length=3 label='Maximum length'
@@ -30,7 +36,6 @@ create table &libds(
,MULT num label='Multiplier' ,MULT num label='Multiplier'
,FILL char(1) label='Fill character' ,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?' ,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion' ,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion' ,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information' ,HLO char(13) label='Additional information'

View File

@@ -81,7 +81,8 @@ run;
filename __us2grp temp; filename __us2grp temp;
proc metadata in= "<UpdateMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata> proc metadata in= "<UpdateMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata>
<Person Id='&uuri'><IdentityGroups><IdentityGroup ObjRef='&guri' /> <Person Id='%nrstr(&uuri)'>
<IdentityGroups><IdentityGroup ObjRef='%nrstr(&guri)' />
</IdentityGroups></Person></Metadata> </IdentityGroups></Person></Metadata>
<NS>SAS</NS><Flags>268435456</Flags></UpdateMetadata>" <NS>SAS</NS><Flags>268435456</Flags></UpdateMetadata>"
out=__us2grp verbose; out=__us2grp verbose;
@@ -98,4 +99,4 @@ run;
filename __us2grp clear; filename __us2grp clear;
%mend mm_adduser2group; %mend mm_adduser2group;

View File

@@ -646,9 +646,11 @@ data _null_;
put ' '; put ' ';
put '%mend mm_webout; '; put '%mend mm_webout; ';
/* WEBOUT END */ /* WEBOUT END */
put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO);'; put '%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO';
put ' ,maxobs=MAX';
put ');';
put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing'; put ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt,missing=&missing';
put ' ,showmeta=&showmeta'; put ' ,showmeta=&showmeta,maxobs=&maxobs';
put ' )'; put ' )';
put '%mend;'; put '%mend;';
run; run;

989
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,6 @@
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true" "prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks || true"
}, },
"devDependencies": { "devDependencies": {
"@sasjs/cli": "3.13.0" "@sasjs/cli": "3.24.0"
} }
} }

View File

@@ -1,224 +1,224 @@
/** /**
@file @file
@brief Deploy repo as a SAS PACKAGES module @brief Deploy repo as a SAS PACKAGES module
@details After every release, this program is executed to update the SASPAC @details After every release, this program is executed to update the SASPAC
repo with the latest macros (and same version number). repo with the latest macros (and same version number).
The program is first compiled using sasjs compile, then executed using The program is first compiled using sasjs compile, then executed using
sasjs run. sasjs run.
Requires the server to have SSH keys. Requires the server to have SSH keys.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_gitadd.sas @li mp_gitadd.sas
@li mp_gitreleaseinfo.sas @li mp_gitreleaseinfo.sas
@li mp_gitstatus.sas @li mp_gitstatus.sas
**/ **/
/* get package version */ /* get package version */
%mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=splib) %mp_gitreleaseinfo(GITHUB,sasjs/core,outlib=splib)
data _null_; data _null_;
set splib.root; set splib.root;
call symputx('version',substr(TAG_NAME,2)); call symputx('version',substr(TAG_NAME,2));
run; run;
/* clone the source repo */ /* clone the source repo */
%let dir = %sysfunc(pathname(work))/core; %let dir = %sysfunc(pathname(work))/core;
%put source clone rc=%sysfunc(GITFN_CLONE(https://github.com/sasjs/core,&dir)); %put source clone rc=%sysfunc(GITFN_CLONE(https://github.com/sasjs/core,&dir));
/* /*
clone the target repo. clone the target repo.
If you have issues, see: https://stackoverflow.com/questions/74082874 If you have issues, see: https://stackoverflow.com/questions/74082874
*/ */
options dlcreatedir; options dlcreatedir;
%let dirOut = %sysfunc(pathname(work))/package; %let dirOut = %sysfunc(pathname(work))/package;
libname _ "&dirOut."; libname _ "&dirOut.";
%put tgt clone rc=%sysfunc(GITFN_CLONE( %put tgt clone rc=%sysfunc(GITFN_CLONE(
git@github.com:SASPAC/sasjscore.git, git@github.com:SASPAC/sasjscore.git,
&dirOut, &dirOut,
git, git,
%str( ), %str( ),
/home/sasjssrv/.ssh/id_ecdsa.pub, /home/sasjssrv/.ssh/id_ecdsa.pub,
/home/sasjssrv/.ssh/id_ecdsa /home/sasjssrv/.ssh/id_ecdsa
)); ));
/* /*
Prepare Package Metadata Prepare Package Metadata
*/ */
data _null_; data _null_;
infile CARDS4; infile CARDS4;
file "&dirOut./description.sas"; file "&dirOut./description.sas";
input; input;
if _infile_ =: 'Version:' then put "Version: &version."; if _infile_ =: 'Version:' then put "Version: &version.";
else put _infile_; else put _infile_;
CARDS4; CARDS4;
Type: Package Type: Package
Package: SASjsCore Package: SASjsCore
Title: SAS Macros for Application Development Title: SAS Macros for Application Development
Version: $(PLACEHOLDER) Version: $(PLACEHOLDER)
Author: Allan Bowe Author: Allan Bowe
Maintainer: 4GL Ltd Maintainer: 4GL Ltd
License: MIT License: MIT
Encoding: UTF8 Encoding: UTF8
DESCRIPTION START: DESCRIPTION START:
The SASjs Macro Core library is a component of the SASjs framework, the The SASjs Macro Core library is a component of the SASjs framework, the
source for which is avaible here: https://github.com/sasjs source for which is avaible here: https://github.com/sasjs
Macros are divided by: Macros are divided by:
* Macro Functions (prefix mf_) * Macro Functions (prefix mf_)
* Macro Procedures (prefix mp_) * Macro Procedures (prefix mp_)
* Macros for Metadata (prefix mm_) * Macros for Metadata (prefix mm_)
* Macros for SASjs Server (prefix ms_) * Macros for SASjs Server (prefix ms_)
* Macros for Viya (prefix mv_) * Macros for Viya (prefix mv_)
DESCRIPTION END: DESCRIPTION END:
;;;; ;;;;
run; run;
/* /*
Prepare Package License Prepare Package License
*/ */
data _null_; data _null_;
file "&dirOut./license.sas"; file "&dirOut./license.sas";
infile "&dir/LICENSE"; infile "&dir/LICENSE";
input; input;
put _infile_; put _infile_;
run; run;
/* /*
Extract Core files into MacroCore Package location Extract Core files into MacroCore Package location
*/ */
data members(compress=char); data members(compress=char);
length dref dref2 $ 8 name name2 $ 32 path $ 2048; length dref dref2 $ 8 name name2 $ 32 path $ 2048;
rc = filename(dref, "&dir."); rc = filename(dref, "&dir.");
put dref=; put dref=;
did = dopen(dref); did = dopen(dref);
if did then if did then
do i = 1 to dnum(did); do i = 1 to dnum(did);
name = dread(did, i); name = dread(did, i);
if name in if name in
("base" "ddl" "fcmp" "lua" "meta" "metax" "server" "viya" "xplatform") ("base" "ddl" "fcmp" "lua" "meta" "metax" "server" "viya" "xplatform")
then do; then do;
rc = filename(dref2,catx("/", "&dir.", name)); rc = filename(dref2,catx("/", "&dir.", name));
put dref2= name; put dref2= name;
did2 = dopen(dref2); did2 = dopen(dref2);
if did2 then if did2 then
do j = 1 to dnum(did2); do j = 1 to dnum(did2);
name2 = dread(did2, j); name2 = dread(did2, j);
path = catx("/", "&dir.", name, name2); path = catx("/", "&dir.", name, name2);
if "sas" = scan(name2, -1, ".") then output; if "sas" = scan(name2, -1, ".") then output;
end; end;
rc = dclose(did2); rc = dclose(did2);
rc = filename(dref2); rc = filename(dref2);
end; end;
end; end;
rc = dclose(did); rc = dclose(did);
rc = filename(dref); rc = filename(dref);
keep name name2 path; keep name name2 path;
run; run;
%let temp_options = %sysfunc(getoption(source)) %sysfunc(getoption(notes)); %let temp_options = %sysfunc(getoption(source)) %sysfunc(getoption(notes));
options nosource nonotes; options nosource nonotes;
data _null_; data _null_;
set members; set members;
by name notsorted; by name notsorted;
ord + first.name; ord + first.name;
if first.name then if first.name then
do; do;
call execute('libname _ ' call execute('libname _ '
!! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros")) !! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros"))
!! ";" !! ";"
); );
put @1 "./" ord z3. "_macros/"; put @1 "./" ord z3. "_macros/";
end; end;
put @10 name2; put @10 name2;
call execute(" call execute("
data _null_; data _null_;
infile " !! quote(strip(path)) !! "; infile " !! quote(strip(path)) !! ";
file " !! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros", name2)) !!"; file " !! quote(catx("/", "&dirOut.", put(ord, z3.)!!"_macros", name2)) !!";
input; input;
select; select;
when (2 = trigger) put _infile_; when (2 = trigger) put _infile_;
when (_infile_ = '/**') do; put '/*** HELP START ***//**'; trigger+1; end; when (_infile_ = '/**') do; put '/*** HELP START ***//**'; trigger+1; end;
when (_infile_ = '**/') do; put '**//*** HELP END ***/'; trigger+1; end; when (_infile_ = '**/') do; put '**//*** HELP END ***/'; trigger+1; end;
otherwise put _infile_; otherwise put _infile_;
end; end;
run;"); run;");
run; run;
options &temp_options.; options &temp_options.;
/* /*
Generate SASjsCore Package Generate SASjsCore Package
*/ */
%GeneratePackage( %GeneratePackage(
filesLocation=&dirOut filesLocation=&dirOut
) )
/** /**
* apply new version in a github action * apply new version in a github action
* 1. create folder * 1. create folder
* 2. create template yaml * 2. create template yaml
* 3. replace version number * 3. replace version number
*/ */
%mf_mkdir(&dirout/.github/workflows) %mf_mkdir(&dirout/.github/workflows)
%let desc=Version &version of sasjs/core is now on SAS PACKAGES :ok_hand:; %let desc=Version &version of sasjs/core is now on SAS PACKAGES :ok_hand:;
data _null_; data _null_;
file "&dirout/.github/workflows/release.yml"; file "&dirout/.github/workflows/release.yml";
put "name: SASjs Core Package Publish Tag"; put "name: SASjs Core Package Publish Tag";
put "on:"; put "on:";
put " push:"; put " push:";
put " branches:"; put " branches:";
put " - main"; put " - main";
put "jobs:"; put "jobs:";
put " update:"; put " update:";
put " runs-on: ubuntu-latest"; put " runs-on: ubuntu-latest";
put " steps:"; put " steps:";
put " - uses: actions/checkout@master"; put " - uses: actions/checkout@master";
put " - name: Make Release"; put " - name: Make Release";
put " uses: alice-biometrics/release-creator/@v1.0.5"; put " uses: alice-biometrics/release-creator/@v1.0.5";
put " with:"; put " with:";
put " github_token: ${{ secrets.GH_TOKEN }}"; put " github_token: ${{ secrets.GH_TOKEN }}";
put " branch: main"; put " branch: main";
put " draft: false"; put " draft: false";
put " version: &version"; put " version: &version";
put " description: '&desc'"; put " description: '&desc'";
run; run;
/** /**
* Add, Commit & Push! * Add, Commit & Push!
*/ */
%mp_gitstatus(&dirout,outds=work.gitstatus,mdebug=1) %mp_gitstatus(&dirout,outds=work.gitstatus,mdebug=1)
%mp_gitadd(&dirout,inds=work.gitstatus,mdebug=1) %mp_gitadd(&dirout,inds=work.gitstatus,mdebug=1)
data _null_; data _null_;
rc=gitfn_commit("&dirout" rc=gitfn_commit("&dirout"
,"HEAD","&sysuserid","sasjs@core" ,"HEAD","&sysuserid","sasjs@core"
,"FEAT: Releasing &version" ,"FEAT: Releasing &version"
); );
put rc=; put rc=;
rc=git_push( rc=git_push(
"&dirout" "&dirout"
,"git" ,"git"
,"" ,""
,"/home/sasjssrv/.ssh/id_ecdsa.pub" ,"/home/sasjssrv/.ssh/id_ecdsa.pub"
,"/home/sasjssrv/.ssh/id_ecdsa" ,"/home/sasjssrv/.ssh/id_ecdsa"
); );
run; run;

View File

@@ -39,8 +39,9 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@li mf_getuniquelibref.sas @li mf_getuniquename.sas
@li mp_abort.sas @li mp_abort.sas
@li mp_chop.sas
**/ **/
@@ -153,7 +154,10 @@ run;
run; run;
%end; %end;
filename &outref temp lrecl=32767; %local resp_path;
%let resp_path=%sysfunc(pathname(work))/%mf_getuniquename();
filename &outref "&resp_path" lrecl=32767;
/* prepare request*/ /* prepare request*/
proc http method='POST' headerin=&authref in=&mainref out=&outref proc http method='POST' headerin=&authref in=&mainref out=&outref
url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131"; url="&_sasjs_apiserverurl.&_sasjs_apipath?_program=&pgm%str(&)_debug=131";
@@ -161,6 +165,7 @@ proc http method='POST' headerin=&authref in=&mainref out=&outref
debug level=2; debug level=2;
%end; %end;
run; run;
%if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201) %if (&SYS_PROCHTTP_STATUS_CODE ne 200 and &SYS_PROCHTTP_STATUS_CODE ne 201)
or &mdebug=1 or &mdebug=1
%then %do; %then %do;
@@ -176,11 +181,22 @@ or &mdebug=1
options &optval; options &optval;
%if &outlogds ne _null_ or &mdebug=1 %then %do; %if &outlogds ne _null_ or &mdebug=1 %then %do;
%local dumplib; %local matchstr chopout;
%let dumplib=%mf_getuniquelibref(); %let matchstr=SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784;
libname &dumplib json fileref=&outref; %let chopout=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop);
%mp_chop("&resp_path"
,matchvar=matchstr
,keep=LAST
,matchpoint=END
,outfile="&chopout"
,mdebug=&mdebug
)
data &outlogds; data &outlogds;
set &dumplib..log; infile "&chopout" lrecl=2000;
length line $2000;
line=_infile_;
%if &mdebug=1 %then %do; %if &mdebug=1 %then %do;
putlog line=; putlog line=;
%end; %end;

View File

@@ -108,47 +108,34 @@ run;
) )
/* SASjs services have the _webout embedded in wrapper JSON */ /* chop out JSON section */
/* Files can also be very large - so use a dedicated macro to chop it out */ %local matchstr chopout;
%local matchstr1 matchstr2 ; %let matchstr=SASJS_LOGS_SEPARATOR_163ee17b6ff24f028928972d80a26784;
%let matchstr1={"status":"success","_webout":{; %let chopout=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop);
%let matchstr2=},"log":[{;
%let chopout1=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop1);
%let chopout2=%sysfunc(pathname(work))/%mf_getuniquename(prefix=chop2);
%mp_chop("%sysfunc(pathname(&fref1,F))" %mp_chop("%sysfunc(pathname(&fref1,F))"
,matchvar=matchstr1 ,matchvar=matchstr
,keep=LAST
,matchpoint=END
,offset=-1
,outfile="&chopout1"
,mdebug=&mdebug
)
%mp_chop("&chopout1"
,matchvar=matchstr2
,keep=FIRST ,keep=FIRST
,matchpoint=START ,matchpoint=START
,offset=1 ,offset=-1
,outfile="&chopout2" ,outfile="&chopout"
,mdebug=&mdebug ,mdebug=&mdebug
) )
%if &outlib ne 0 %then %do; %if &outlib ne 0 %then %do;
libname &outlib json "&chopout2"; libname &outlib json "&chopout";
%end; %end;
%if &outref ne 0 %then %do; %if &outref ne 0 %then %do;
filename &outref "&chopout2"; filename &outref "&chopout";
%end; %end;
%if &mdebug=0 %then %do; %if &mdebug=0 %then %do;
filename &webref clear; filename &webref clear;
filename &fref1 clear; filename &fref1 clear;
filename &fref2 clear;
%end; %end;
%else %do; %else %do;
%put &sysmacroname exit vars:; %put &sysmacroname exit vars:;
%put _local_; %put _local_;
%end; %end;
%mend ms_testservice; %mend ms_testservice;

View File

@@ -17,4 +17,24 @@
%mp_assert( %mp_assert(
iftrue=(%mf_existvar(sashelp.class,isjustanumber)=0), iftrue=(%mf_existvar(sashelp.class,isjustanumber)=0),
desc=Checking non existing var does not exist desc=Checking non existing var does not exist
)
data work.lockcheck;
a=1;
output;
stop;
run;
%mp_assert(
iftrue=(%mf_existvar(work.lockcheck,)=0),
desc=Checking non-provided var does not exist
)
proc sql;
update work.lockcheck set a=2;
%mp_assert(
iftrue=(&syscc=0),
desc=Checking the lock was released,
outds=work.test_results
) )

View File

@@ -0,0 +1,46 @@
/**
@file
@brief Testing mp_aligndecimal macro
@details Creates an aligned variable and checks the number of leading blanks
<h4> SAS Macros </h4>
@li mp_aligndecimal.sas
@li mp_assertcolvals.sas
@li mp_assertscope.sas
**/
/* target values */
data work.checkds;
do checkval=' 0.56',' 123.45',' 123.4 ',' 1.2 ',' 0';
output;
end;
run;
/* raw values */
data work.rawds;
set work.checkds;
tgtvar=cats(checkval);
drop checkval;
run;
%mp_assertcolvals(work.rawds.tgtvar,
checkvals=work.checkds.checkval,
desc=No values match (ready to align),
test=NOVAL
)
/* aligned values */
%mp_assertscope(SNAPSHOT)
data work.finalds;
set work.rawds;
%mp_aligndecimal(tgtvar,width=4)
run;
%mp_assertscope(COMPARE)
%mp_assertcolvals(work.finalds.tgtvar,
checkvals=work.checkds.checkval,
desc=All values match (aligned),
test=ALLVALS
)

View File

@@ -7,23 +7,35 @@
@li mp_assertcols.sas @li mp_assertcols.sas
@li mp_assertcolvals.sas @li mp_assertcolvals.sas
@li mp_assertdsobs.sas @li mp_assertdsobs.sas
@li mp_assertscope.sas
**/ **/
/* valid filter */ /* make some data */
%mp_getcols(sashelp.airline,outds=work.info) proc sql;
create table work.src(
SOME_DATETIME float format=datetime19.,
SOME_CHAR char(16),
SOME_NUM num,
SOME_TIME num format=time8.,
SOME_DATE num format=date9.
);
/* run macro, checking for scope leakage */
%mp_assertscope(SNAPSHOT)
%mp_getcols(work.src,outds=work.info)
%mp_assertscope(COMPARE)
%mp_assertdsobs(work.info, %mp_assertdsobs(work.info,
desc=Has 3 records, desc=Has 5 records,
test=EQUALS 3, test=EQUALS 5,
outds=work.test_results outds=work.test_results
) )
data work.check; data work.check;
length val $10; length val $10;
do val='NUMERIC','DATE','CHARACTER'; do val='NUMERIC','DATE','CHARACTER','DATETIME','TIME';
output; output;
end; end;
run; run;

View File

@@ -1,9 +1,11 @@
/** /**
@file @file
@brief Testing mp_loadformat.sas macro @brief Testing mp_loadformat.sas macro
@details first test regular formats, then informats
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mddl_dc_difftable.sas @li mddl_dc_difftable.sas
@li mp_aligndecimal.sas
@li mp_loadformat.sas @li mp_loadformat.sas
@li mp_assert.sas @li mp_assert.sas
@li mp_assertscope.sas @li mp_assertscope.sas
@@ -15,8 +17,10 @@ libname perm (work);
%mddl_dc_difftable(libds=perm.audit) %mddl_dc_difftable(libds=perm.audit)
/* set up regular formats */
data work.loadfmts; data work.loadfmts;
length fmtname $32; /* matching start / end lengths (to baseds) are important */
length fmtname $32 start end $10000;
eexcl='Y'; eexcl='Y';
type='N'; type='N';
do i=1 to 100; do i=1 to 100;
@@ -24,7 +28,9 @@ data work.loadfmts;
do j=1 to 100; do j=1 to 100;
start=cats(j); start=cats(j);
end=cats(j+1); end=cats(j+1);
label= cats('Dummy ',start); %mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
label= cats('Numeric Format ',start);
output; output;
end; end;
end; end;
@@ -42,6 +48,8 @@ data work.stagedata;
else if _n_<350 then do; else if _n_<350 then do;
start=cats(_n_); start=cats(_n_);
end=cats(_n_+1); end=cats(_n_+1);
%mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
label='newval'!!cats(_N_); label='newval'!!cats(_N_);
end; end;
else stop; else stop;
@@ -91,3 +99,118 @@ run;
desc=Test 1 - diffs were found, desc=Test 1 - diffs were found,
outds=work.test_results outds=work.test_results
) )
/* set up a mix of formats */
data work.loadfmts2;
length fmtname $32 start end $10000;
eexcl='Y';
type='J';
do i=1 to 3;
fmtname=cats('SASJS_CI_',i,'X');
do j=1 to 4;
start=cats(j);
end=start;
label= cats('Char INFORMAT ',start);
output;
end;
end;
type='I';
do i=1 to 3;
fmtname=cats('SASJS_NI_',i,'X');
do j=1 to 4;
start=cats(j);
end=cats(j+1);
%mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
label= cats(ranuni(0));
output;
end;
end;
type='N';
do i=1 to 3;
fmtname=cats('SASJS_NF_',i,'X');
do j=1 to 4;
start=cats(j);
end=cats(j+1);
%mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
label= cats('Numeric Format ',start);
output;
end;
end;
type='C';
do i=1 to 3;
fmtname=cats('SASJS_CF_',i,'X');
do j=1 to 4;
start=cats(j);
end=start;
label= cats('Char Format ',start);
output;
end;
end;
drop i j;
run;
proc format cntlin=work.loadfmts2 library=perm.testcat2;
run;
/* make some test data */
data work.stagedata2;
set work.loadfmts2;
where type in ('I','J');
eexcl='Y';
if type='I' then do;
i+1;
if i<3 then deleteme='Yes';
else if i<7 then label= cats(ranuni(0)*100);
else if i<12 then do;
/* new values */
z=ranuni(0)*1000000;
start=cats(z);
end=cats(z+1);
%mp_aligndecimal(start,width=16)
%mp_aligndecimal(end,width=16)
label= cats(ranuni(0)*100);
end;
if i<12 then output;
end;
else do;
j+1;
if j<3 then deleteme='Yes';
else if j<7 then label= cats(ranuni(0)*100);
else if j<12 then do;
start= cats("NEWVAL",start);
end=start;
label= "NEWVAL "||cats(ranuni(0)*100);
end;
if j<12 then output;
end;
run;
%mp_loadformat(perm.testcat2
,work.stagedata2
,loadtarget=YES
,auditlibds=perm.audit
,locklibds=0
,delete_col=deleteme
,outds_add=add_test2
,outds_del=del_test2
,outds_mod=mod_test2
,mdebug=1
)
%mp_assert(
iftrue=(%mf_nobs(del_test2)=4),
desc=Test 2 - delete obs,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(mod_test2)=8),
desc=Test 2 - mod obs,
outds=work.test_results
)
%mp_assert(
iftrue=(%mf_nobs(add_test2)=10),
desc=Test 2 - add obs,
outds=work.test_results
)

View File

@@ -4,8 +4,10 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_lockanytable.sas @li mp_lockanytable.sas
@li mp_assert.sas
@li mp_assertcols.sas @li mp_assertcols.sas
@li mp_assertcolvals.sas @li mp_assertcolvals.sas
@li mp_assertscope.sas
@li mp_coretable.sas @li mp_coretable.sas
**/ **/
@@ -61,3 +63,18 @@ run;
desc=Ref is captured in unlock, desc=Ref is captured in unlock,
test=ANYVAL test=ANYVAL
) )
/* attempt unlock of a table that was never locked */
%mp_lockanytable(UNLOCK,lib=no,ds=doesnotexist,ref=bye, ctl_ds=work.controller)
%mp_assert(
iftrue=(&syscc=0),
desc=Ability to unlock a table that was never locked,
outds=work.test_results
)
/* test for macro variable scope leakage */
%mp_assertscope(SNAPSHOT)
%mp_lockanytable(LOCK,lib=tmp,ds=testscope,ref=This Ref, ctl_ds=work.controller)
%mp_assertscope(COMPARE)

View File

@@ -4,7 +4,7 @@
@brief Testing mv_jobflow macro @brief Testing mv_jobflow macro
@details One of the remote jobs aborts with syscc>0 - test to @details One of the remote jobs aborts with syscc>0 - test to
make sure this comes back to the calling session make sure this comes back to the calling session
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_assert.sas @li mp_assert.sas
@li mv_createjob.sas @li mv_createjob.sas

View File

@@ -3,7 +3,7 @@
@brief Testing mv_jobflow macro @brief Testing mv_jobflow macro
@details All jobs complete successfully with syscc = 0 - test to @details All jobs complete successfully with syscc = 0 - test to
make sure this comes back to the calling session make sure this comes back to the calling session
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_assert.sas @li mp_assert.sas
@li mv_createjob.sas @li mv_createjob.sas

View File

@@ -4,7 +4,7 @@
@brief Testing mv_registerclient.sas macro @brief Testing mv_registerclient.sas macro
@details Tests for successful registration. For this to work, the test @details Tests for successful registration. For this to work, the test
account must be an admin. account must be an admin.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_getuniquename.sas @li mf_getuniquename.sas
@li mp_assertcolvals.sas @li mp_assertcolvals.sas

View File

@@ -1,10 +1,9 @@
/** /**
@file @file
@brief Testing mv_registerclient.sas macro @brief Testing mv_registerclient.sas macro
@details Tests for unsuccessful registration. To do this, overrides are @details Tests for unsuccessful registration. To do this, overrides are
applied for the mf_loc.sas and mp_abort.sas macros. applied for the mf_loc.sas and mp_abort.sas macros.
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mp_assert.sas @li mp_assert.sas
@li mv_registerclient.sas @li mv_registerclient.sas

View File

@@ -1,60 +1,60 @@
/** /**
@file mfv_existsashdat.sas @file mfv_existsashdat.sas
@brief Checks whether a CAS sashdat dataset exists in persistent storage. @brief Checks whether a CAS sashdat dataset exists in persistent storage.
@details Can be used in open code, eg as follows: @details Can be used in open code, eg as follows:
%if %mfv_existsashdat(libds=casuser.sometable) %then %put yes it does!; %if %mfv_existsashdat(libds=casuser.sometable) %then %put yes it does!;
The function uses `dosubl()` to run the `table.fileinfo` action, for the The function uses `dosubl()` to run the `table.fileinfo` action, for the
specified library, filtering for `*.sashdat` tables. The results are stored specified library, filtering for `*.sashdat` tables. The results are stored
in a WORK table (&outprefix._&lib). If that table already exists, it is in a WORK table (&outprefix._&lib). If that table already exists, it is
queried instead, to avoid the dosubl() performance hit. queried instead, to avoid the dosubl() performance hit.
To force a rescan, just use a new `&outprefix` value, or delete the table(s) To force a rescan, just use a new `&outprefix` value, or delete the table(s)
before running the function. before running the function.
@param libds library.dataset @param libds library.dataset
@param outprefix= (work.mfv_existsashdat) Used to store the current HDATA @param outprefix= (work.mfv_existsashdat) Used to store the current HDATA
tables to improve subsequent query performance. This reference is a prefix tables to improve subsequent query performance. This reference is a prefix
and is converted to `&prefix._{libref}` and is converted to `&prefix._{libref}`
@return output returns 1 or 0 @return output returns 1 or 0
@version 0.2 @version 0.2
@author Mathieu Blauw @author Mathieu Blauw
**/ **/
%macro mfv_existsashdat(libds,outprefix=work.mfv_existsashdat %macro mfv_existsashdat(libds,outprefix=work.mfv_existsashdat
); );
%local rc dsid name lib ds; %local rc dsid name lib ds;
%let lib=%upcase(%scan(&libds,1,'.')); %let lib=%upcase(%scan(&libds,1,'.'));
%let ds=%upcase(%scan(&libds,-1,'.')); %let ds=%upcase(%scan(&libds,-1,'.'));
/* if table does not exist, create it */ /* if table does not exist, create it */
%if %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do; %if %sysfunc(exist(&outprefix._&lib)) ne 1 %then %do;
%let rc=%sysfunc(dosubl(%nrstr( %let rc=%sysfunc(dosubl(%nrstr(
/* Read in table list (once per &lib per session) */ /* Read in table list (once per &lib per session) */
proc cas; proc cas;
table.fileinfo result=source_list /caslib="&lib"; table.fileinfo result=source_list /caslib="&lib";
val=findtable(source_list); val=findtable(source_list);
saveresult val dataout=&outprefix._&lib; saveresult val dataout=&outprefix._&lib;
quit; quit;
/* Only keep name, without file extension */ /* Only keep name, without file extension */
data &outprefix._&lib; data &outprefix._&lib;
set &outprefix._&lib(where=(Name like '%.sashdat') keep=Name); set &outprefix._&lib(where=(Name like '%.sashdat') keep=Name);
Name=upcase(scan(Name,1,'.')); Name=upcase(scan(Name,1,'.'));
run; run;
))); )));
%end; %end;
/* Scan table for hdat existence */ /* Scan table for hdat existence */
%let dsid=%sysfunc(open(&outprefix._&lib(where=(name="&ds")))); %let dsid=%sysfunc(open(&outprefix._&lib(where=(name="&ds"))));
%syscall set(dsid); %syscall set(dsid);
%let rc = %sysfunc(fetch(&dsid)); %let rc = %sysfunc(fetch(&dsid));
%let rc = %sysfunc(close(&dsid)); %let rc = %sysfunc(close(&dsid));
/* Return result */ /* Return result */
%if "%trim(&name)"="%trim(&ds)" %then 1; %if "%trim(&name)"="%trim(&ds)" %then 1;
%else 0; %else 0;
%mend mfv_existsashdat; %mend mfv_existsashdat;