1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-12 15:04:36 +00:00

Compare commits

..

142 Commits

Author SHA1 Message Date
Allan Bowe
22bf8065bb Merge pull request #95 from sasjs/dirlist_enhancement
feat: Recursive Directory Listing
2021-11-26 13:00:15 +00:00
Allan Bowe
7bb089e269 fix: moving cleanup to temp table for efficiency 2021-11-26 11:55:55 +00:00
Allan Bowe
a6e9158814 chore: adding recursive option to the mp_dirlist macro, and updating all.sas 2021-11-26 11:52:48 +00:00
Ivor Townsend
19a1336720 feat: Recursive Directory Listing 2021-11-26 09:29:42 +00:00
Allan Bowe
79d5d42e6b Merge pull request #94 from sasjs/issue93
Macros for locking / unlocking tables
2021-11-25 17:44:37 +00:00
Allan Bowe
2d81de5841 chore: generating all.sas and updating doc links 2021-11-25 16:46:56 +00:00
Allan Bowe
107ab836d6 feat: mp_lockXXX macros f
These macros provide a centralised approach for locking tables where updates can happen in multiple passes.  The mp_lockfilecheck macro will also verify the ability to create a SAS lock (if a v9 library engine, and not a view)
2021-11-25 16:43:39 +00:00
Allan Bowe
5225e74465 chore: adding @vznesh for issue #88 2021-11-18 18:40:32 +00:00
Allan Bowe
39253d2828 chore: merge conflicts 2021-11-18 18:36:32 +00:00
Allan Bowe
171c169537 chore: updating all.sas 2021-11-18 18:36:08 +00:00
Allan Bowe
76af9fa33c Merge pull request #89 from sasjs/issue88
fix: removing notes when running mp_zip, closes #88. .
2021-11-18 18:35:33 +00:00
Allan Bowe
37ccc8a2aa fix: removing notes when running mp_zip, closes #88. .
Also adding 3 tests
2021-11-18 18:21:43 +00:00
Allan Bowe
d0a0274990 Merge pull request #87 from sasjs/mp_wait4file
mp_wait4file macro
2021-11-18 13:58:34 +00:00
Allan Bowe
b3d374f1b1 fix: updating node version to LTS for CLI 2021-11-18 13:44:33 +00:00
Allan Bowe
1c4458faf6 chore: comments to mp_unzip 2021-11-18 13:27:17 +00:00
Allan Bowe
96e1d146f4 chore: updating package.json 2021-11-18 13:07:00 +00:00
Allan Bowe
aadc4fb83d feat: mp_wait4file macro 2021-11-18 13:04:04 +00:00
Allan Bowe
988ee89cdb Merge pull request #85 from sasjs/all-contributors
docs: add all-contributors dependence
2021-10-04 13:56:22 +01:00
Vladislav Parhomchik
51cbfbf4bc docs: add all-contributors dependence 2021-10-04 15:21:28 +03:00
Allan Bowe
4b69e91362 Merge pull request #84 from sasjs/issue83
fix: refactored mp_getconstraints due to apparent bug in dictionary.table_constraints
2021-10-01 13:56:57 +01:00
Allan Bowe
8f9715035a chore: removing gitpod badge and switching Node to LTS 2021-10-01 12:55:54 +00:00
Allan Bowe
35ddccaa16 fix: refactored mp_getconstraints due to apparent bug in dictionary.table_constraints. Added test. Closes #83 2021-10-01 13:04:26 +01:00
Allan Bowe
cb0ddfb61c fix: applying tag wrappers to sasjsAbort response json. The adapter will always extract from these in any case, and it seems there are some situations where it is not possible to avoid errors being thrown in SAS 9 (resulting in a log always appearing in the response). This change will make it much easier to handle on the frontend side. 2021-09-30 18:50:02 +01:00
Allan Bowe
c3b6f06b3a chore: merge commit 2021-09-30 14:48:26 +01:00
Allan Bowe
8046d5a0b1 fix: updating CLI dependency to avoid npm install warnings 2021-09-30 14:47:56 +01:00
Allan Bowe
aed07f2943 Merge pull request #82 from sasjs/checkmsg
fix: adding sysmsg() to failed metadata calls
2021-09-30 14:44:36 +01:00
Allan Bowe
5bf87a78b8 fix: adding sysmsg() to failed metadata calls 2021-09-30 14:32:52 +01:00
Allan Bowe
0851523d18 chore: gitpod settings 2021-09-29 11:28:59 +00:00
Allan Bowe
9e2de81dae Merge pull request #81 from sasjs/issue80
fix: removing nonprintables from cards data.  Closes #80
2021-09-27 22:42:36 +03:00
Allan Bowe
4887f355c8 fix: removing redundant dlm option 2021-09-27 20:14:52 +01:00
Allan Bowe
9b32e6e3f2 fix: removing nonprintables from cards data. Closes #80 2021-09-27 20:12:48 +01:00
Allan Bowe
74790ec80e fix: adding trim to avoid converting trailing blanks 2021-09-27 17:46:53 +01:00
Allan Bowe
afd8a754b4 Merge pull request #79 from sasjs/issue78
feat: adding binary variable support to mp_ds2cards.sas
2021-09-27 18:57:16 +03:00
Allan Bowe
bc1f7b3baa fix: updating test result and making mp_ds2cards header doxygen compliant 2021-09-27 16:39:28 +01:00
Allan Bowe
51690e68dc feat: adding binary variable support to mp_ds2cards.sas, updating documentation, and including two tests. Closes #78 2021-09-27 16:15:25 +01:00
Allan Bowe
0fa076cb73 Merge pull request #77 from sasjs/dictfix
fix: ensuring upcase comparisons for dictionary tables
2021-09-27 15:17:48 +03:00
Allan Bowe
6506993704 fix: ensuring upcase comparisons for dictionary tables 2021-09-27 13:04:32 +01:00
Allan Bowe
a69db2ebfb Merge pull request #76 from sasjs/mp_appendfile
feat: mp_appendfile macro for appending 2 or more files together
2021-09-27 14:50:55 +03:00
Allan Bowe
d72ca7cb24 fix: warning in mp_assertcolvals from outobs sql option 2021-09-27 12:30:28 +01:00
Allan Bowe
52dfa7b8f7 feat: mp_appendfile macro for appending 2 or more files together 2021-09-27 11:46:19 +01:00
Allan Bowe
dae03c5730 fix: adding SYSSCPL to mp_abort and webout macros 2021-09-24 17:31:42 +01:00
Allan Bowe
14efe5d3fd chore: removing copyright notice (copy paste error) 2021-09-22 21:10:00 +01:00
Allan Bowe
653244d737 Merge pull request #75 from sasjs/mp_getcols
Mp getcols macro
2021-09-22 19:34:25 +03:00
Allan Bowe
086831b3f5 chore: updating all.sas 2021-09-22 17:20:02 +01:00
Allan Bowe
6eca585fc1 feat: new mp_getcols macro 2021-09-22 17:19:49 +01:00
Allan Bowe
f6ba36fc28 feat: adding more info to result description in mp_assertcolvals 2021-09-22 17:19:29 +01:00
Allan Bowe
7406288d79 fix: mp_gsubfile() now works with multiline files (and we have a multiline test to go with it) 2021-09-16 18:30:17 +01:00
Allan Bowe
2e7fcbe5b8 fix: prevening truncation of _debug in mp_abort.sas and more reliable way to fetch syswarningtext and syserrortext 2021-09-16 14:04:50 +01:00
Allan Bowe
3e7b9f8c14 chore: fixing example in header for mp_gsubfile() 2021-09-16 13:54:27 +01:00
Allan Bowe
e9189ccc06 Merge pull request #74 from sasjs/gsub
feat: adding mp_gsubfile.sas - a SAS macro that uses Lua to perform a…
2021-09-14 19:13:15 +03:00
Allan Bowe
8c00d715c2 chore: formatting 2021-09-14 16:08:30 +01:00
Allan Bowe
d47a369cdf feat: adding mp_gsubfile.sas - a SAS macro that uses Lua to perform a full text find and replace of an entire file. Not restricted by number of characters (only memory). IIncludes a test. 2021-09-14 16:07:12 +01:00
Allan Bowe
52bf6019fd Merge pull request #73 from sasjs/defaultvars
fix: adding default lengths to vars in mv_getfoldermembers to cover u…
2021-09-11 19:26:06 +03:00
Allan Bowe
25e61fd8ef chore: updating all.sas 2021-09-11 19:25:28 +03:00
Allan Bowe
3038be83a0 fix: adding default lengths to vars in mv_getfoldermembers to cover use case where no members found (and downstream process expects an empty table with those vars. Also fixing mp_webin to cover a case where native temp filerefs (starting with a #hash) are not supported. 2021-09-11 19:24:51 +03:00
Allan Bowe
6e2447c70a fix: missing dependency in mp_webin() 2021-09-11 19:00:04 +03:00
Allan Bowe
486aba84ca Merge pull request #72 from sasjs/webinnings
New mp_webin() macro and performance improvement to mf_getuniquefileref()
2021-09-08 19:51:36 +03:00
Allan Bowe
b5944181e1 chore(dependencies): bumping cli 2021-09-08 19:35:27 +03:00
Allan Bowe
3f69cf506a feat: mp_webin() macro for handling the _webin_xxx macro variables in SAS web services 2021-09-08 19:34:28 +03:00
Allan Bowe
6013897c50 feat: updating mf_getuniquefileref() to use native approach to get a unique fileref (which is 100 times faster than the old approach) 2021-09-08 15:30:51 +03:00
Allan Bowe
27cf2a2532 Merge pull request #71 from sasjs/including
fix: conditional logic around mp_abort(mode=INCLUDE) to cover case wh…
2021-09-05 20:56:40 +03:00
Allan Bowe
d096cbddeb fix: conditional logic around mp_abort(mode=INCLUDE) to cover case when no mp_include was executed 2021-09-05 20:55:44 +03:00
Allan Bowe
117503f214 Merge pull request #70 from sasjs/including
feat: new mp_include() feature for handling %includes with macros
2021-09-03 11:17:21 +03:00
Allan Bowe
5cc5fae750 chore: adding mp_abort to mp_include sas macros list 2021-09-03 11:06:12 +03:00
Allan Bowe
d93032e1a9 chore: docs 2021-09-03 10:54:21 +03:00
Allan Bowe
507557b2cb feat: new mp_include() feature for handling %includes with macros 2021-09-03 00:40:10 +03:00
Allan Bowe
ee5c3c185a chore: merge 2021-08-28 14:22:16 +03:00
Allan Bowe
e5592a2eb2 chore: avoiding the string 'ERROR:' in logs 2021-08-28 14:21:36 +03:00
Allan Bowe
59200a6e73 Merge pull request #69 from sasjs/issue68
feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts
2021-08-24 21:03:09 +03:00
Allan Bowe
f468f60ae1 fix: substr issue in mp_ds2inserts. Closes #68 2021-08-24 20:51:29 +03:00
Allan Bowe
9f60d827b6 feat: supporting postgres timestamps for mp_ds2inserts and mp_lib2inserts 2021-08-24 20:39:02 +03:00
Allan Bowe
5c936ddb65 Merge pull request #67 from sasjs/issue66
feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_b…
2021-08-24 14:04:25 +03:00
Allan Bowe
d0bde62594 feat: adding APPEND option to mp_binarycopy.sas, and a new test (mp_binarycopy.test.sas). Closes #66 2021-08-24 13:43:31 +03:00
Allan Bowe
ada9192337 Merge pull request #65 from sasjs/issue64
fix: increasing limit for mv_getfoldermember.sas, also adding a test,…
2021-08-20 22:50:27 +03:00
Allan Bowe
6161f588a9 fix: increasing limit for mv_getfoldermember.sas, also adding a test, and updating mf_getapploc to search _program by default (and fixing it to work with macro tests, and updating the test for that also) 2021-08-20 22:38:56 +03:00
Allan Bowe
67079d8c17 fix: adding default value for exists in mf_existfunction 2021-08-19 00:20:49 +03:00
Allan Bowe
75bd39adb0 Merge pull request #63 from sasjs/issue62
feat: adding FCMP capability
2021-08-19 00:00:15 +03:00
Allan Bowe
078bdbeecf chore: bumping devDependency (sasjs cli) 2021-08-18 23:47:17 +03:00
Allan Bowe
8ddb86785c feat: new fcmp stpsrv_header function 2021-08-18 23:45:45 +03:00
Allan Bowe
005af0ecf8 feat: new mf_existfunction macro 2021-08-18 23:36:12 +03:00
Allan Bowe
bc410a9135 chore: fixing docs for tests 2021-08-18 19:55:05 +03:00
Allan Bowe
fc8ba2e36c chore: moving files to tidy up docs 2021-08-18 19:43:38 +03:00
Allan Bowe
756441384a feat: adding first fcmp macro, mcf_string2file.sas 2021-08-18 18:35:51 +03:00
Allan Bowe
10f9eecf9e Merge pull request #60 from sasjs/vpn-fix
chore: vpn fix
2021-08-13 14:41:25 +03:00
470ebb50a7 chore: vpn fix 2021-08-13 12:57:31 +02:00
Allan Bowe
26cd5d9d31 Merge pull request #59 from Stefan-Dimitrov-Stoyanov/patch-1
Update README.md
2021-08-12 11:59:25 +03:00
Stefan-Dimitrov-Stoyanov
0b694bb878 Update README.md
Fix the missing space at the end of the first line under the Installation header:  https://github.com/sasjs/core/blob/main/README.md#installation
2021-08-11 13:34:44 +01:00
Allan Bowe
b403c02bba chore: docs for mm_createfolder 2021-08-06 15:36:55 +03:00
Allan Bowe
0b555bb31c Merge pull request #58 from sasjs/apploc
feat: new mf_getapploc macro
2021-08-04 22:04:55 +03:00
Allan Bowe
40b513a9e3 feat: new mf_getapploc macro 2021-08-04 22:00:18 +03:00
Allan Bowe
4eacf4deae Merge pull request #57 from sasjs/mf_existfref
fix: showing filerefs that exist (even when underlying does not) in m…
2021-08-03 14:32:23 +03:00
Allan Bowe
5824423c13 fix: showing filerefs that exist (even when underlying does not) in mf_existfileref, along with 3 tests 2021-08-03 14:16:52 +03:00
Allan Bowe
ce5bfd41dc chore(docs): updating header for mm_gettables 2021-08-02 10:37:01 +03:00
Allan Bowe
0c67a07e42 Merge pull request #56 from sasjs/ddlworkz
feat: new mp_lib2inserts macro.  In addition, modified mp_getddl to i…
2021-07-30 10:11:27 +03:00
Allan Bowe
187504600a chore: fixing test 2021-07-30 00:21:02 +03:00
Allan Bowe
658d67feaa chore: fixing comments 2021-07-30 00:17:44 +03:00
Allan Bowe
5207a77591 feat: new mp_lib2inserts macro. In addition, modified mp_getddl to ignore views, closing #5. Created a test, which highlighted another issue in mp_getddl (labels were being double quoted which caused macro resolution attempts when %including). Changed to single quotes. Switched 'outlib' to 'outschema' in mp_ds2inserts to harmonise with mp_getddl. Added a maxobs option, to speed up testing. 2021-07-30 00:14:29 +03:00
Allan Bowe
4456adf1dc fix: switching default flavour from BASE to SAS to be consistent with mp_getddl 2021-07-29 20:35:57 +03:00
Allan Bowe
03962c2a50 fix: for PGSQL DDL generation, ignore tables with over 1600 columns (as they are not supported in Postgres) 2021-07-29 15:45:16 +03:00
Allan Bowe
6d2fc7e265 fix: removing bug introduced to mp_getddl and adding a test to prevent regression 2021-07-29 13:02:58 +03:00
Allan Bowe
39b2e7c5f9 fix: supporting salts over 32 chars in mp_hashdataset() 2021-07-28 23:22:43 +03:00
Allan Bowe
f99adf5c3e Merge pull request #55 from sasjs/insertforpg
feat: updating mp_ds2inserts to support postgres database
2021-07-28 19:11:38 +03:00
Allan Bowe
69f8e91a2d feat: adding salt as option for mp_hashdataset 2021-07-28 17:04:41 +03:00
Allan Bowe
5b5d01993f fix: updating mp_getddl to enable append to a fileref 2021-07-28 17:01:49 +03:00
Allan Bowe
00fa464a7c feat: updating mp_ds2inserts to support postgres database 2021-07-27 11:07:12 +03:00
Allan Bowe
a5baf46233 chore: removing unnecessary proc format and generating the all.sas file 2021-07-26 22:30:02 +03:00
Allan Bowe
d63d2a4ec1 Merge pull request #54 from sasjs/mp_ds2inserts
feat: mp_ds2inserts macro
2021-07-26 22:10:44 +03:00
Allan Bowe
900f694065 chore: removing extra period 2021-07-26 21:59:48 +03:00
Allan Bowe
838324c15e chore: updating header 2021-07-26 19:06:53 +03:00
Allan Bowe
e3205ec06c feat: mp_ds2inserts macro for creating programs for inserting data (and corresponding test) 2021-07-26 19:04:49 +03:00
Allan Bowe
154a33434e chore: more contributors 2021-07-24 21:06:30 +03:00
Allan Bowe
bfa1bbaeb1 chore: all contributors update in README 2021-07-24 21:03:49 +03:00
Allan Bowe
1f0128aec4 Merge pull request #53 from sasjs/base64doublebytefix
fix: mp_base64copy.sas fixes, removed renegade % symbol and issue wit…
2021-07-18 17:16:58 +03:00
Allan Bowe
69f03f4e14 fix: mp_base64copy.sas fixes, removed renegade % symbol and issue with truncation at character 76. Added two tests, including one to test double byte encoded characters. 2021-07-18 17:05:05 +03:00
Allan Bowe
a932f321d8 Merge pull request #52 from sasjs/sasjs-cli-version-bump
fix: bump sasjs/cli version + 'prepare' support windows CMD/Powershell
2021-07-10 09:40:48 +03:00
Saad Jutt
21200c11c1 fix: bump sasjs/cli version + 'prepare' support windows CMD/Powershell 2021-07-10 03:43:58 +05:00
Allan Bowe
825c97c49c fix: switch postinstall to prepare 2021-06-30 19:50:20 +03:00
Allan Bowe
f301899269 Merge pull request #51 from sasjs/issue50
fix: setting syscc to zero to prevent error state in response.  Close…
2021-06-29 00:09:42 +03:00
Allan Bowe
fc81f62d2f fix: setting syscc to zero to prevent error state in response. Closes #50 2021-06-28 23:52:12 +03:00
Allan Bowe
93aea5ed02 Merge pull request #49 from sasjs/logfix
Context fixes on mv_jobflow and mp_testservice
2021-06-27 00:35:24 +03:00
Allan Bowe
55d4c7238a fix: updating mp_testservice.sas and mv_jobflow to use the provided context. Also updating mv_getjobresult to fetch byte by byte (as some inputs are very wide). 2021-06-27 00:22:53 +03:00
Allan Bowe
cd75bf263a fix: removing redundant parameter from mv_getjoblog 2021-06-26 21:11:26 +03:00
Allan Bowe
929a1a9974 chore: updating docs 2021-06-24 00:39:09 +03:00
Allan Bowe
7cafb4fb36 Merge pull request #48 from sasjs/base64
feat: adding mp_base64copy macro
2021-06-24 00:30:21 +03:00
Allan Bowe
a8d222a0f8 chore: automated commit 2021-06-24 00:29:54 +03:00
Allan Bowe
ac0ddf38b0 chore: automated commit 2021-06-24 00:28:41 +03:00
Allan Bowe
ecd389c935 feat: adding mp_base64copy macro 2021-06-24 00:26:41 +03:00
Allan Bowe
06a5ea06f8 Merge pull request #46 from sasjs/mendfixes
sasjs lint fix for macro name in MEND statement
2021-06-23 22:27:36 +03:00
Allan Bowe
955471ed3c Merge branch 'main' into mendfixes 2021-06-23 21:55:44 +03:00
Allan Bowe
c8d3b43b12 fix: adding lrecl to mv_createfile to support lines 1 million characters wide. Closes #47 2021-06-23 21:53:32 +03:00
Allan Bowe
3e313b06a9 fix: adding mend in python lua build 2021-06-21 17:25:58 +03:00
Allan Bowe
d7371a4505 fix: adding mend to every macro statement using sasjs lint fix 2021-06-21 17:25:01 +03:00
Allan Bowe
32a6d15c2e Merge pull request #44 from sasjs/vpn-connection
chore: added vpn connection
2021-06-21 14:35:19 +03:00
Allan Bowe
b109e7cead fix: bumping core 2021-06-21 11:27:06 +00:00
d291d3e287 chore: added vpn connection 2021-06-21 11:49:37 +02:00
Allan Bowe
5a2968e798 fix: supporting LATIN1 as well as WLATIN1 in mm_webout 2021-06-17 16:31:29 +03:00
Allan Bowe
120ad9a7da fix: supporting bell cand escape characters when creating viya jobs / services with macro 2021-06-11 00:09:16 +03:00
Allan Bowe
67a81b2690 chore: updating the docs for mm_spkexport.sas 2021-06-08 20:09:23 +03:00
Allan Bowe
506cf1812f fix: deal with dashes in sysencoding 2021-06-08 16:59:43 +03:00
Allan Bowe
8cc0eb0dd7 Merge pull request #42 from sasjs/issue41
closes #42 Issue41
2021-06-03 22:44:16 +03:00
Allan Bowe
3f49925d01 Merge pull request #40 from sasjs/dependabot/npm_and_yarn/ws-7.4.6
chore(deps): bump ws from 7.4.5 to 7.4.6
2021-06-01 17:58:49 +03:00
dependabot[bot]
c51c9c2ca9 chore(deps): bump ws from 7.4.5 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.5 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.5...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-31 02:08:52 +00:00
215 changed files with 7776 additions and 1294 deletions

124
.all-contributorsrc Normal file
View File

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

30
.github/vpn/config.ovpn vendored Normal file
View 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

View File

@@ -12,15 +12,40 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: [12.x] node-version: [lts/fermium]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Install Doxygen - name: Install Doxygen
run: sudo apt-get install doxygen run: sudo apt-get install doxygen
@@ -31,16 +56,16 @@ jobs:
run: npm run lint run: npm run lint
- name: Add client - name: Add client
run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya run: echo "CLIENT=${{secrets.CLIENT}}"> .env.viya
- name: Add secret - name: Add secret
run: echo "SECRET=${{secrets.SECRET}}" >> .env.viya 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.ACCESS_TOKEN}}" >> .env.viya
- name: Add refresh token - name: Add refresh token
run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya run: echo "REFRESH_TOKEN=${{secrets.REFRESH_TOKEN}}" >> .env.viya
- name: Build Project - name: Build Project
run: npm run build run: npm run build

View File

@@ -1,8 +1,25 @@
tasks: tasks:
- init: nvm install --latest-npm && npm i -g @sasjs/cli - init: nvm install --lts && npm i -g @sasjs/cli
image: image:
file: .gitpod.dockerfile file: .gitpod.dockerfile
vscode: vscode:
extensions: extensions:
- sasjs.sasjs-for-vscode - sasjs.sasjs-for-vscode
github:
prebuilds:
# enable for the master/default branch (defaults to true)
master: true
# enable for all branches in this repo (defaults to false)
branches: false
# enable for pull requests coming from this repo (defaults to true)
pullRequests: true
# enable for pull requests coming from forks (defaults to false)
pullRequestsFromForks: true
# add a "Review in Gitpod" button as a comment to pull requests (defaults to true)
addComment: true
# add a "Review in Gitpod" button to pull requests (defaults to false)
addBadge: false
# add a label once the prebuild is ready to pull requests (defaults to false)
addLabel: prebuilt-in-gitpod

View File

@@ -9,3 +9,4 @@ sasjs/
main.dox main.dox
make_singlefile.sh make_singlefile.sh
*.md *.md
.all-contributorsrc

View File

@@ -2,7 +2,7 @@
"noTrailingSpaces": true, "noTrailingSpaces": true,
"noEncodedPasswords": true, "noEncodedPasswords": true,
"hasDoxygenHeader": true, "hasDoxygenHeader": true,
"hasMacroNameInMend": false, "hasMacroNameInMend": true,
"hasMacroParentheses": true, "hasMacroParentheses": true,
"noNestedMacros": false, "noNestedMacros": false,
"noSpacesInFileNames": true, "noSpacesInFileNames": true,

View File

@@ -41,6 +41,13 @@ Documentation: https://core.sasjs.io
- No X command - No X command
- Prefixes: _mf_, _mp_ - Prefixes: _mf_, _mp_
**fcmp** library (SAS9/Viya)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or
without the `proc fcmp` wrapper.
**meta** library (SAS9 only) **meta** library (SAS9 only)
- OS independent - OS independent
@@ -84,7 +91,7 @@ run;
# Installation # Installation
First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available,eg: First, download the repo to a location your SAS system can access. Then update your sasautos path to include the components you wish to have available, eg:
```sas ```sas
options insert=(sasautos="/your/path/macrocore/base"); options insert=(sasautos="/your/path/macrocore/base");
@@ -179,3 +186,36 @@ If you find this library useful, please leave a [star](https://github.com/sasjs/
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="#business-allanbowe" title="Business development">💼</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Code">💻</a> <a href="#content-allanbowe" title="Content">🖋</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Documentation">📖</a> <a href="#infra-allanbowe" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#question-allanbowe" title="Answering Questions">💬</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/core/commits?author=allanbowe" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/rafgag"><img src="https://avatars.githubusercontent.com/u/69139928?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rafgag</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=rafgag" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tmoody"><img src="https://avatars.githubusercontent.com/u/79837106?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Trevor Moody</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=tmoody" title="Code">💻</a></td>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=saadjutt01" title="Code">💻</a> <a href="#ideas-saadjutt01" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/kkchandok"><img src="https://avatars.githubusercontent.com/u/46090627?v=4?s=100" width="100px;" alt=""/><br /><sub><b>kkchandok</b></sub></a><br /><a href="#ideas-kkchandok" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vladislav Parhomchik</b></sub></a><br /><a href="https://github.com/sasjs/core/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/core/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/vznesh"><img src="https://avatars.githubusercontent.com/u/28916792?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vignesh T.</b></sub></a><br /><a href="https://github.com/sasjs/core/issues?q=author%3Avznesh" title="Bug reports">🐛</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

2504
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,6 @@
-1 -1
%put &sysmacroname: &feature not found; %put &sysmacroname: &feature not found;
%end; %end;
%mend; %mend mf_existfeature;
/** @endcond */ /** @endcond */

View File

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

37
base/mf_existfunction.sas Normal file
View File

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

View File

@@ -30,6 +30,6 @@
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_existvar;
/** @endcond */ /** @endcond */

View File

@@ -54,6 +54,6 @@
0 0
%put Vars not found: &found; %put Vars not found: &found;
%end; %end;
%mend; %mend mf_existvarlist;
/** @endcond */ /** @endcond */

73
base/mf_getapploc.sas Normal file
View File

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

View File

@@ -31,4 +31,4 @@
%sysfunc(attrc(&dsid,&attr)) %sysfunc(attrc(&dsid,&attr))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_getattrc;

View File

@@ -31,4 +31,4 @@
%sysfunc(attrn(&dsid,&attr)) %sysfunc(attrn(&dsid,&attr))
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
%mend; %mend mf_getattrn;

View File

@@ -48,6 +48,6 @@
&engine &engine
%mend; %mend mf_getengine;
/** @endcond */ /** @endcond */

View File

@@ -44,4 +44,4 @@
%sysfunc(INPUTN(&bytes, best.),sizekmg.) %sysfunc(INPUTN(&bytes, best.),sizekmg.)
%end; %end;
%mend ; %mend mf_getfilesize ;

View File

@@ -29,4 +29,4 @@
&valc &valc
%end; %end;
%else %put %str(ERR)OR: Unable to find key &key in ds &libds; %else %put %str(ERR)OR: Unable to find key &key in ds &libds;
%mend; %mend mf_getkeyvalue;

View File

@@ -62,4 +62,4 @@
%else %if &switch=VIYARESTAPI %then %do; %else %if &switch=VIYARESTAPI %then %do;
%mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/) %mf_trimstr(%sysfunc(getoption(servicesbaseurl)),/)
%end; %end;
%mend; %mend mf_getplatform;

View File

@@ -50,4 +50,4 @@
&buffer &buffer
%mend; %mend mf_getquotedstr;

View File

@@ -38,6 +38,6 @@
&schema &schema
%mend; %mend mf_getschema;
/** @endcond */ /** @endcond */

View File

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

View File

@@ -37,4 +37,4 @@
%end; %end;
%end; %end;
%put unable to find available libref in range &prefix.0-&maxtries; %put unable to find available libref in range &prefix.0-&maxtries;
%mend; %mend mf_getuniquelibref;

View File

@@ -39,4 +39,4 @@
%quote(&user) %quote(&user)
%mend; %mend mf_getuser;

View File

@@ -30,4 +30,4 @@
%trim(&&&variable) %trim(&&&variable)
%end; %end;
%mend; %mend mf_getvalue;

View File

@@ -29,4 +29,4 @@
%let rc=%sysfunc(close(&dsid)); %let rc=%sysfunc(close(&dsid));
%end; %end;
&nvars &nvars
%mend; %mend mf_getvarcount;

View File

@@ -51,7 +51,8 @@
%end; %end;
%end; %end;
%else %do; %else %do;
%put dataset &libds not opened! (rc=&dsid); %put &sysmacroname: dataset &libds not opened! (rc=&dsid);
%put &sysmacroname: %sysfunc(sysmsg());
%return; %return;
%end; %end;

View File

@@ -43,10 +43,14 @@
%let vlen = %str( ); %let vlen = %str( );
%end; %end;
%end; %end;
%else %put dataset &libds not opened! (rc=&dsid); %else %do;
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
%put &sysmacroname: %sysfunc(sysmsg());
%return;
%end;
/* Close dataset */ /* Close dataset */
%let rc = %sysfunc(close(&dsid)); %let rc = %sysfunc(close(&dsid));
/* Return variable format */ /* Return variable format */
&vlen &vlen
%mend; %mend mf_getVarLen;

View File

@@ -43,7 +43,11 @@ returns:
%let vnum = %str( ); %let vnum = %str( );
%end; %end;
%end; %end;
%else %put dataset &ds not opened! (rc=&dsid); %else %do;
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
%put &sysmacroname: %sysfunc(sysmsg());
%return;
%end;
/* Close dataset */ /* Close dataset */
%let rc = %sysfunc(close(&dsid)); %let rc = %sysfunc(close(&dsid));
@@ -51,4 +55,4 @@ returns:
/* Return variable number */ /* Return variable number */
&vnum. &vnum.
%mend; %mend mf_getVarNum;

View File

@@ -39,7 +39,11 @@ Usage:
%let vtype = %str( ); %let vtype = %str( );
%end; %end;
%end; %end;
%else %put dataset &libds not opened! (rc=&dsid); %else %do;
%put &sysmacroname: dataset &libds not opened! (rc=&dsid);
%put &sysmacroname: %sysfunc(sysmsg());
%return;
%end;
/* Close dataset */ /* Close dataset */
%let rc = %sysfunc(close(&dsid)); %let rc = %sysfunc(close(&dsid));

View File

@@ -40,4 +40,4 @@
&engine &engine
%mend; %mend mf_getxengine;

View File

@@ -24,4 +24,4 @@
%sysevalf(%superq(param)=,boolean) %sysevalf(%superq(param)=,boolean)
%mend; %mend mf_isblank;

View File

@@ -31,4 +31,4 @@
&is_directory &is_directory
%mend; %mend mf_isdir;

View File

@@ -26,4 +26,4 @@
&root &root
%end; %end;
%mend; %mend mf_loc;

View File

@@ -64,4 +64,4 @@ Usage:
%end; %end;
%end; %end;
/* exit quietly if directory did exist.*/ /* exit quietly if directory did exist.*/
%mend; %mend mf_mkdir;

View File

@@ -16,4 +16,4 @@
%if %symexist(&var) %then %do; %if %symexist(&var) %then %do;
%superq(&var) %superq(&var)
%end; %end;
%mend; %mend mf_mval;

View File

@@ -23,4 +23,4 @@
%macro mf_nobs(libds %macro mf_nobs(libds
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%mf_getattrn(&libds,NLOBS) %mf_getattrn(&libds,NLOBS)
%mend; %mend mf_nobs;

View File

@@ -47,4 +47,4 @@
&basestr &basestr
%end; %end;
%mend; %mend mf_trimstr;

View File

@@ -62,4 +62,4 @@
%else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg); %else %mf_abort(mac=mf_verifymacvars,type=&mabort,msg=&abortmsg);
%exit_success: %exit_success:
%mend; %mend mf_verifymacvars;

View File

@@ -50,5 +50,5 @@
&outvar &outvar
%mend; %mend mf_wordsInStr1ButNotStr2;

View File

@@ -15,24 +15,47 @@
recognise this and fetch the log of the parent session instead) recognise this and fetch the log of the parent session instead)
@li STP environments must finish cleanly to avoid the log being sent to @li STP environments must finish cleanly to avoid the log being sent to
_webout. To assist with this, we also run stpsrvset('program error', 0) _webout. To assist with this, we also run stpsrvset('program error', 0)
and set SYSCC=0. For 9.4m3 we take a unique approach - we open a macro and set SYSCC=0. We take a unique "soft abort" approach - we open a macro
but don't close it! This provides a graceful abort, EXCEPT when called but don't close it! This works everywhere EXCEPT inside a \%include inside
called within a %include within a macro (and that macro contains additional a macro. For that, we recommend you use mp_include.sas to perform the
logic). See mp_abort.test.nofix.sas for the example case. include, and then call \%mp_abort(mode=INCLUDE) from the source program (ie,
If you know of another way to gracefully abort a 9.4m3 STP session, we'd OUTSIDE of the top-parent macro).
love to hear about it!
@param mac= to contain the name of the calling macro @param mac= to contain the name of the calling macro
@param msg= message to be returned @param msg= message to be returned
@param iftrue= supply a condition under which the macro should be executed. @param iftrue= supply a condition under which the macro should be executed.
@param errds= (work.mp_abort_errds) There is no clean way to end a process
within a %include called within a macro. Furthermore, there is no way to
test if a macro is called within a %include. To handle this particular
scenario, the %include should be switched for the mp_include.sas macro.
This provides an indicator that we are running a macro within a \%include
(`_SYSINCLUDEFILEDEVICE`) and allows us to provide a dataset with the abort
values (msg, mac).
We can then run an abort cancel FILE to stop the include running, and pass
the dataset back to the calling program to run a regular \%mp_abort().
The dataset will contain the following fields:
@li iftrue (1=1)
@li msg (the message)
@li mac (the mac param)
@version 9.4M3 @param mode= (REGULAR) If mode=INCLUDE then the &errds dataset is checked for
an abort status.
Valid values:
@li REGULAR (default)
@li INCLUDE
<h4> Related Macros </h4>
@li mp_include.sas
@version 9.4
@author Allan Bowe @author Allan Bowe
@cond @cond
**/ **/
%macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1) %macro mp_abort(mac=mp_abort.sas, type=, msg=, iftrue=%str(1=1)
, errds=work.mp_abort_errds
, mode=REGULAR
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%global sysprocessmode sysprocessname; %global sysprocessmode sysprocessname;
@@ -43,9 +66,44 @@
%if %length(&mac)>0 %then %put NOTE- called by &mac; %if %length(&mac)>0 %then %put NOTE- called by &mac;
%put NOTE - &msg; %put NOTE - &msg;
%if %symexist(_SYSINCLUDEFILEDEVICE) %then %do;
%if "*&_SYSINCLUDEFILEDEVICE*" ne "**" %then %do;
data &errds;
iftrue='1=1';
length mac $100 msg $5000;
mac=symget('mac');
msg=symget('msg');
run;
data _null_;
abort cancel FILE;
run;
%return;
%end;
%end;
/* Stored Process Server web app context */ /* Stored Process Server web app context */
%if %symexist(_metaperson) or "&SYSPROCESSNAME "="Compute Server " %then %do; %if %symexist(_metaperson)
or "&SYSPROCESSNAME "="Compute Server "
or &mode=INCLUDE
%then %do;
options obs=max replace nosyntaxcheck mprint; options obs=max replace nosyntaxcheck mprint;
%if &mode=INCLUDE %then %do;
%if %sysfunc(exist(&errds))=1 %then %do;
data _null_;
set &errds;
call symputx('iftrue',iftrue,'l');
call symputx('mac',mac,'l');
call symputx('msg',msg,'l');
putlog (_all_)(=);
run;
%if (&iftrue)=0 %then %return;
%end;
%else %do;
%put &sysmacroname: No include errors found;
%return;
%end;
%end;
/* extract log errs / warns, if exist */ /* extract log errs / warns, if exist */
%local logloc logline; %local logloc logline;
%global logmsg; /* capture global messages */ %global logmsg; /* capture global messages */
@@ -100,7 +158,7 @@
/* send response in SASjs JSON format */ /* send response in SASjs JSON format */
data _null_; data _null_;
file _webout mod lrecl=32000 encoding='utf-8'; file _webout mod lrecl=32000 encoding='utf-8';
length msg $32767 debug $8; length msg $32767 ;
sasdatetime=datetime(); sasdatetime=datetime();
msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg')); msg=cats(symget('msg'),'\n\nLog Extract:\n',symget('logmsg'));
/* escape the quotes */ /* escape the quotes */
@@ -111,7 +169,7 @@
msg=cats('"',msg,'"'); msg=cats('"',msg,'"');
if symexist('_debug') then debug=quote(trim(symget('_debug'))); if symexist('_debug') then debug=quote(trim(symget('_debug')));
else debug='""'; else debug='""';
if debug ge '"131"' then put '>>weboutBEGIN<<'; put '>>weboutBEGIN<<';
put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"'; put '{"START_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '"';
put ',"sasjsAbort" : [{'; put ',"sasjsAbort" : [{';
put ' "MSG":' msg ; put ' "MSG":' msg ;
@@ -131,29 +189,45 @@
_PROGRAM=quote(trim(resolve(symget('_PROGRAM')))); _PROGRAM=quote(trim(resolve(symget('_PROGRAM'))));
put ',"_PROGRAM" : ' _PROGRAM ; put ',"_PROGRAM" : ' _PROGRAM ;
put ",""SYSCC"" : ""&syscc"" "; put ",""SYSCC"" : ""&syscc"" ";
put ",""SYSERRORTEXT"" : ""&syserrortext"" "; syserrortext=quote(trim(symget('syserrortext')));
put ",""SYSERRORTEXT"" : " syserrortext;
put ",""SYSHOSTNAME"" : ""&syshostname"" ";
put ",""SYSJOBID"" : ""&sysjobid"" "; put ",""SYSJOBID"" : ""&sysjobid"" ";
put ",""SYSSCPL"" : ""&sysscpl"" ";
put ",""SYSSITE"" : ""&syssite"" ";
sysvlong=quote(trim(symget('sysvlong'))); sysvlong=quote(trim(symget('sysvlong')));
put ',"SYSVLONG" : ' sysvlong; put ',"SYSVLONG" : ' sysvlong;
put ",""SYSWARNINGTEXT"" : ""&syswarningtext"" "; syswarningtext=quote(trim(symget('syswarningtext')));
put ",""SYSWARNINGTEXT"" : " syswarningtext;
put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" '; put ',"END_DTTM" : "' "%sysfunc(datetime(),datetime20.3)" '" ';
put "}" @; put "}" @;
if debug ge '"131"' then put '>>weboutEND<<'; put '>>weboutEND<<';
run; run;
%put _all_; %put _all_;
%if "&sysprocessmode " = "SAS Stored Process Server " %then %do; %if "&sysprocessmode " = "SAS Stored Process Server " %then %do;
data _null_; data _null_;
putlog 'stpsrvset program error and syscc'; putlog 'stpsrvset program err and syscc';
rc=stpsrvset('program error', 0); rc=stpsrvset('program error', 0);
call symputx("syscc",0,"g"); call symputx("syscc",0,"g");
run; run;
%if "%substr(&sysvlong.xxxxxxxxx,1,9)" ne "9.04.01M3" %then %do; /**
%put NOTE: Ending SAS session due to:; * endsas kills 9.4m3 deployments by orphaning multibridges.
%put NOTE- &msg; * Abort variants are ungraceful (non zero return code)
endsas; * This approach lets SAS run silently until the end :-)
%end; * Caution - fails when called within a %include within a macro
* Use mp_include() to handle this.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end; %end;
%else %if "&sysprocessmode " = "SAS Compute Server " %then %do; %else %if "&sysprocessmode " = "SAS Compute Server " %then %do;
/* endsas kills the session making it harder to fetch results */ /* endsas kills the session making it harder to fetch results */
@@ -165,27 +239,10 @@
sysuserid=symget('sysuserid'); sysuserid=symget('sysuserid');
iftrue=symget('iftrue'); iftrue=symget('iftrue');
put (_all_)(/=); put (_all_)(/=);
call symputx('syscc',0);
abort cancel nolist; abort cancel nolist;
run; run;
%end; %end;
%else %if "%substr(&sysvlong.xxxxxxxxx,1,9)" = "9.04.01M3" %then %do;
/**
* endsas kills 9.4m3 deployments by orphaning multibridges.
* Abort variants are ungraceful (non zero return code)
* This approach lets SAS run silently until the end :-)
* Caution - fails when called within a %include within a macro
* See tests/mp_abort.test.1 for an example case.
*/
filename skip temp;
data _null_;
file skip;
put '%macro skip();';
comment '%mend skip; -> fix lint ';
put '%macro skippy();';
comment '%mend skippy; -> fix lint ';
run;
%inc skip;
%end;
%else %do; %else %do;
%abort cancel; %abort cancel;
%end; %end;

57
base/mp_appendfile.sas Normal file
View File

@@ -0,0 +1,57 @@
/**
@file
@brief Append (concatenate) two or more files.
@details Will append one more more `appendrefs` (filerefs) to a `baseref`.
Uses a binary mechanism, so will work with any file type. For that reason -
use with care! And supply your own trailing carriage returns in each file..
Usage:
filename tmp1 temp;
filename tmp2 temp;
filename tmp3 temp;
data _null_; file tmp1; put 'base file';
data _null_; file tmp2; put 'append1';
data _null_; file tmp3; put 'append2';
run;
%mp_appendfile(baseref=tmp1, appendrefs=tmp2 tmp3)
@param [in] baseref= Fileref of the base file (should exist)
@param [in] appendrefs= One or more filerefs to be appended to the base
fileref. Space separated.
@version 9.2
@author Allan Bowe, source: https://github.com/sasjs/core
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mp_binarycopy.sas
**/
%macro mp_appendfile(
baseref=0,
appendrefs=0
)/*/STORE SOURCE*/;
%mp_abort(iftrue= (&baseref=0)
,mac=&sysmacroname
,msg=%str(Baseref NOT specified!)
)
%mp_abort(iftrue= (&appendrefs=0)
,mac=&sysmacroname
,msg=%str(Appendrefs NOT specified!)
)
%local i;
%do i=1 %to %sysfunc(countw(&appendrefs));
%mp_abort(iftrue= (&syscc>0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc)
)
%mp_binarycopy(inref=%scan(&appendrefs,&i), outref=&baseref, mode=APPEND)
%end;
%mend mp_appendfile;

View File

@@ -142,4 +142,4 @@
proc sql; proc sql;
drop table &ds; drop table &ds;
%mend; %mend mp_assertcols;

View File

@@ -30,6 +30,7 @@
<h4> SAS Macros </h4> <h4> SAS Macros </h4>
@li mf_existds.sas @li mf_existds.sas
@li mf_getuniquename.sas
@li mf_nobs.sas @li mf_nobs.sas
@li mp_abort.sas @li mp_abort.sas
@@ -115,6 +116,26 @@
select count(*) into: orig from &lib..&ds; select count(*) into: orig from &lib..&ds;
quit; quit;
%local notfound tmp1 tmp2;
%let tmp1=%mf_getuniquename();
%let tmp2=%mf_getuniquename();
/* this is a bit convoluted - but using sql outobs=10 throws warnings */
proc sql noprint;
create view &tmp1 as
select distinct &col
from &lib..&ds
where &col not in (
select &ccol from &clib..&cds
);
data &tmp2;
set &tmp1;
if _n_>10 then stop;
run;
proc sql;
select distinct &col into: notfound separated by ' ' from &tmp2;
%mp_abort(iftrue= (&syscc ne 0) %mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname ,mac=&sysmacroname
,msg=%str(syscc=&syscc after macro query) ,msg=%str(syscc=&syscc after macro query)
@@ -125,7 +146,7 @@
test_description=symget('desc'); test_description=symget('desc');
test_result='FAIL'; test_result='FAIL';
test_comments="&sysmacroname: &lib..&ds..&col has &result values " test_comments="&sysmacroname: &lib..&ds..&col has &result values "
!!"not in &clib..&cds..&ccol "; !!"not in &clib..&cds..&ccol.. First 10 vals:"!!symget('notfound');
%if &test=ANYVAL %then %do; %if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS'; if &result < &orig then test_result='PASS';
%end; %end;
@@ -144,4 +165,4 @@
proc sql; proc sql;
drop table &ds; drop table &ds;
%mend; %mend mp_assertcolvals;

117
base/mp_base64copy.sas Normal file
View File

@@ -0,0 +1,117 @@
/**
@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 recfm=N;
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 + (-1) @;
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);
length raw $4;
do i=1 to 4;
rc=fget(filein,char,1);
substr(raw,i,1)=char;
end;
rc = fput(fileout,input(raw,$base64X4.));
rc = fwrite(fileout);
end;
rc = fclose(filein);
rc = fclose(fileout);
run;
%end;
%mend mp_base64copy;

View File

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

View File

@@ -67,5 +67,5 @@
else put inchar $char1.; else put inchar $char1.;
end; end;
run; run;
%mend; %mend mp_cleancsv;
/** @endcond */ /** @endcond */

View File

@@ -64,4 +64,4 @@ data &outds;
output; output;
run; run;
%mend; %mend mp_createconstraints;

View File

@@ -80,4 +80,4 @@ Usage:
) )
%end; %end;
%mend; %mend mp_createwebservice;

View File

@@ -141,4 +141,4 @@ data &outds
%end; %end;
run; run;
%mend; %mend mp_csv2ds;

View File

@@ -49,4 +49,4 @@ data &outds;
end; end;
run; run;
%mend; %mend mp_deleteconstraints;

View File

@@ -3,20 +3,13 @@
@brief Returns all files and subdirectories within a specified parent @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
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
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: 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: usage:
%mp_dirlist(path=/some/location,outds=myTable) %mp_dirlist(path=/some/location, outds=myTable, maxdepth=MAX)
%mp_dirlist(outds=cwdfileprops, getattrs=YES) %mp_dirlist(outds=cwdfileprops, getattrs=YES)
@@ -30,11 +23,19 @@
X CMD) do please raise an issue! X CMD) do please raise an issue!
@param path= for which to return contents @param [in] path= for which to return contents
@param fref= Provide a DISK engine fileref as an alternative to PATH @param [in] fref= Provide a DISK engine fileref as an alternative to PATH
@param outds= the output dataset to create @param [in] maxdepth= (0) Set to a positive integer to indicate the level of
@param getattrs= YES/NO (default=NO). Uses doptname and foptname to return subdirectory scan recursion - eg 3, to go `./3/levels/deep`. For unlimited
all attributes for each file / folder. recursion, set to MAX.
@param [out] outds= the output dataset to create
@param [out] getattrs= (NO) 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 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 whether a file or folder, but some
files/folders cannot be accessed, and so not all properties can / will be
populated.
@returns outds contains the following variables: @returns outds contains the following variables:
@@ -44,8 +45,12 @@
- filename (just the file name) - filename (just the file name)
- ext (.extension) - ext (.extension)
- msg (system message if any issues) - msg (system message if any issues)
- level (depth of folder)
- OS SPECIFIC variables, if <code>getattrs=</code> is used. - OS SPECIFIC variables, if <code>getattrs=</code> is used.
<h4> SAS Macros </h4>
@li mp_dropmembers.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
**/ **/
@@ -54,14 +59,27 @@
, fref=0 , fref=0
, outds=work.mp_dirlist , outds=work.mp_dirlist
, getattrs=NO , getattrs=NO
, maxdepth=0
, level=0 /* The level of recursion to perform. For internal use only. */
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%let getattrs=%upcase(&getattrs)XX; %let getattrs=%upcase(&getattrs)XX;
data &outds(compress=no /* temp table */
keep=file_or_folder filepath filename ext msg directory %local out_ds;
data;run;
%let out_ds=%str(&syslast);
/* drop main (top) table if it exists */
%if &level=0 %then %do;
%mp_dropmembers(&outds, libref=WORK)
%end;
data &out_ds(compress=no
keep=file_or_folder filepath filename ext msg directory level
); );
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80 length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
ext $20 msg $200; ext $20 msg $200;
retain level &level;
%if &fref=0 %then %do; %if &fref=0 %then %do;
rc = filename(fref, "&path"); rc = filename(fref, "&path");
%end; %end;
@@ -119,8 +137,8 @@ data &outds(compress=no
run; run;
%if %substr(&getattrs,1,1)=Y %then %do; %if %substr(&getattrs,1,1)=Y %then %do;
data &outds; data &out_ds;
set &outds; set &out_ds;
length infoname infoval $60 fref $8; length infoname infoval $60 fref $8;
rc=filename(fref,filepath); rc=filename(fref,filepath);
drop rc infoname fid i close fref; drop rc infoname fid i close fref;
@@ -161,10 +179,35 @@ run;
run; run;
proc sort; proc sort;
by filepath sasname; by filepath sasname;
proc transpose data=&outds out=&outds(drop=_:); proc transpose data=&out_ds out=&out_ds(drop=_:);
id sasname; id sasname;
var infoval; var infoval;
by filepath file_or_folder filename ext ; by filepath file_or_folder filename ext ;
run; run;
%end; %end;
%mend;
data &out_ds;
set &out_ds(where=(filepath ne ''));
run;
/* update main table */
proc append base=&outds data=&out_ds;
run;
/* recursive call */
%if &maxdepth>&level or &maxdepth=MAX %then %do;
data _null_;
set &out_ds;
where file_or_folder='folder';
code=cats('%nrstr(%mp_dirlist(path=',filepath,",outds=&outds"
,",getattrs=&getattrs,level=%eval(&level+1),maxdepth=&maxdepth))");
put code=;
call execute(code);
run;
%end;
/* tidy up */
proc sql;
drop table &out_ds;
%mend mp_dirlist;

View File

@@ -47,4 +47,4 @@
%end; %end;
as &outvar length=&varlen as &outvar length=&varlen
from &libds; from &libds;
%mend; %mend mp_distinctfmtvalues;

View File

@@ -2,12 +2,21 @@
@file @file
@brief Create a CARDS file from a SAS dataset. @brief Create a CARDS file from a SAS dataset.
@details Uses dataset attributes to convert all data into datalines. @details Uses dataset attributes to convert all data into datalines.
Running the generated file will rebuild the original dataset. Running the generated file will rebuild the original dataset. Includes
support for large decimals, binary data, PROCESSED_DTTM columns, and
alternative encoding. If the input dataset is empty, the cards file will
still be created.
Additional support to generate a random sample and max rows.
Usage: Usage:
%mp_ds2cards(base_ds=sashelp.class %mp_ds2cards(base_ds=sashelp.class
, tgt_ds=work.class
, cards_file= "C:\temp\class.sas" , cards_file= "C:\temp\class.sas"
, maxobs=5) , showlog=NO
, maxobs=5
)
TODO: TODO:
- labelling the dataset - labelling the dataset
@@ -18,15 +27,24 @@
that is converted to a cards file. that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create. @param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS. Optional - if omitted, will be same as BASE_DS.
@param [out] cards_file= Location in which to write the (.sas) cards file @param [out] cards_file= ("%sysfunc(pathname(work))/cardgen.sas") Location in
@param [in] maxobs= to limit output to the first <code>maxobs</code> which to write the (.sas) cards file
observations @param [in] maxobs= (max) To limit output to the first <code>maxobs</code>
@param [in] showlog= whether to show generated cards file in the SAS log observations, enter an integer here.
(YES/NO) @param [in] random_sample= (NO) Set to YES to generate a random sample of
@param [in] outencoding= provide encoding value for file statement (eg utf-8) data. Can be quite slow.
@param [in] append= If NO then will rebuild the cards file if it already @param [in] showlog= (YES) Whether to show generated cards file in the SAS
log. Valid values:
@li YES
@li NO
@param [in] outencoding= Provide encoding value for file statement (eg utf-8)
@param [in] append= (NO) If NO then will rebuild the cards file if it already
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro. exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
<h4> Related Macros </h4>
@li mp_lib2cards.sas
@li mp_ds2inserts.sas
@li mp_mdtablewrite.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@@ -51,15 +69,15 @@
%if (&tgt_ds = ) %then %let tgt_ds=&base_ds; %if (&tgt_ds = ) %then %let tgt_ds=&base_ds;
%if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.); %if %index(&tgt_ds,.)=0 %then %let tgt_ds=WORK.%scan(&base_ds,2,.);
%if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding"; %if ("&outencoding" ne "") %then %let outencoding=encoding="&outencoding";
%if ("&append" = "") %then %let append=; %if ("&append" = "" or "&append" = "NO") %then %let append=;
%else %let append=mod; %else %let append=mod;
/* get varcount */ /* get varcount */
%let nvars=0; %let nvars=0;
proc sql noprint; proc sql noprint;
select count(*) into: nvars from dictionary.columns select count(*) into: nvars from dictionary.columns
where libname="%scan(%upcase(&base_ds),1)" where upcase(libname)="%scan(%upcase(&base_ds),1)"
and memname="%scan(%upcase(&base_ds),2)"; and upcase(memname)="%scan(%upcase(&base_ds),2)";
%if &nvars=0 %then %do; %if &nvars=0 %then %do;
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.; %put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
%return; %return;
@@ -115,8 +133,8 @@ proc sql
reset outobs=max; reset outobs=max;
create table datalines1 as create table datalines1 as
select name,type,length,varnum,format,label from dictionary.columns select name,type,length,varnum,format,label from dictionary.columns
where libname="%upcase(%scan(&base_ds,1))" where upcase(libname)="%upcase(%scan(&base_ds,1))"
and memname="%upcase(%scan(&base_ds,2))"; and upcase(memname)="%upcase(%scan(&base_ds,2))";
/** /**
Due to long decimals cannot use best. format Due to long decimals cannot use best. format
@@ -137,7 +155,18 @@ data datalines_2;
,put(',name,',best32.-l) ,put(',name,',best32.-l)
,substrn(put(',name,',bestd32.-l),1 ,substrn(put(',name,',bestd32.-l),1
,findc(put(',name,',bestd32.-l),"0","TBK")))'); ,findc(put(',name,',bestd32.-l),"0","TBK")))');
else dataline=name; /**
* binary data must be converted, to store in text format. It is identified
* by the presence of the $HEX keyword in the format.
*/
else if upcase(format)=:'$HEX' then
dataline=cats('put(trim(',name,'),',format,')');
/**
* There is no easy way to store line breaks in a cards file.
* To discuss this, use: https://github.com/sasjs/core/issues/80
* Removing all nonprintables with kw (keep writeable)
*/
else dataline=cats('compress(',name,', ,"kw")');
run; run;
proc sql noprint; proc sql noprint;
@@ -162,7 +191,8 @@ data _null_;
/* Build input statement */ /* Build input statement */
if type='char' then type3=':$char.'; if upcase(format)=:'$HEX' then type3=':'!!format;
else if type='char' then type3=':$char.';
str2=put(name,$33.)||type3; str2=put(name,$33.)||type3;
@@ -184,11 +214,12 @@ data _null_;
file &cards_file. &outencoding lrecl=32767 termstr=nl &append; file &cards_file. &outencoding lrecl=32767 termstr=nl &append;
length __attrib $32767; length __attrib $32767;
if _n_=1 then do; if _n_=1 then do;
put '/*******************************************************************'; put '/**';
put " Datalines for %upcase(%scan(&base_ds,2)) dataset "; put ' @file';
put " Generated by %nrstr(%%)mp_ds2cards()"; put " @brief Datalines for %upcase(%scan(&base_ds,2)) dataset";
put " Available on github.com/sasjs/core"; put " @details Generated by %nrstr(%%)mp_ds2cards()";
put '********************************************************************/'; put " Available on github.com/sasjs/core";
put '**/';
put "data &tgt_ds &indexes;"; put "data &tgt_ds &indexes;";
put "attrib "; put "attrib ";
%do i = 1 %to &nvars; %do i = 1 %to &nvars;
@@ -212,11 +243,11 @@ data _null_;
put 'run;'; put 'run;';
end; end;
else do; else do;
put "infile cards dsd delimiter=',';"; put "infile cards dsd;";
put "input "; put "input ";
%do i = 1 %to &nvars.; %do i = 1 %to &nvars.;
%if(%length(&&input_stmt_&i..)) %then %if(%length(&&input_stmt_&i..)) %then
put " &&input_stmt_&i.."; put " &&input_stmt_&i..";
; ;
%end; %end;
put ";"; put ";";
@@ -251,4 +282,4 @@ quit;
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%put NOTE- %sysfunc(dequote(&cards_file.)); %put NOTE- %sysfunc(dequote(&cards_file.));
%put NOTE-;%put NOTE-; %put NOTE-;%put NOTE-;
%mend; %mend mp_ds2cards;

View File

@@ -55,4 +55,4 @@ data _null_;
run; run;
%mend; %mend mp_ds2csv;

179
base/mp_ds2inserts.sas Normal file
View File

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

View File

@@ -99,4 +99,4 @@ filename &outref temp;
run; run;
%end; %end;
%mend; %mend mp_filtergenerate;

View File

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

65
base/mp_getcols.sas Normal file
View File

@@ -0,0 +1,65 @@
/**
@file
@brief Creates a dataset with column metadata.
@details This macro takes the `proc contents` output and "tidies it up" in the
following ways:
@li Blank labels are filled in with column names
@li Formats are reconstructed with default values
@li Types such as DATE / TIME / DATETIME are inferred from the formats
Example usage:
%mp_getcols(sashelp.airline,outds=work.myds)
@param ds The dataset from which to obtain column metadata
@param outds= (work.cols) The output dataset to create. Sample data:
|NAME $|LENGTH 8|VARNUM 8|LABEL $|FORMAT $49|TYPE $1 |DDTYPE $|
|---|---|---|---|---|---|---|
|AIR|8|2|international airline travel (thousands)|8.|N|NUMERIC|
|DATE|8|1|DATE|MONYY.|N|DATE|
|REGION|3|3|REGION|$3.|C|CHARACTER|
<h4> Related Macros </h4>
@li mf_getvarlist.sas
@li mm_getcols.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_getcols(ds, outds=work.cols);
proc contents noprint data=&ds
out=_data_ (keep=name type length label varnum format:);
run;
data &outds(keep=name type length varnum format label ddtype);
set &syslast(rename=(format=format2 type=type2));
name=upcase(name);
if type2=2 then do;
length format $49.;
if format2='' then format=cats('$',length,'.');
else if formatl=0 then format=cats(format2,'.');
else format=cats(format2,formatl,'.');
type='C';
ddtype='CHARACTER';
end;
else do;
if format2='' then format=cats(length,'.');
else if formatl=0 then format=cats(format2,'.');
else if formatd=0 then format=cats(format2,formatl,'.');
else format=cats(format2,formatl,'.',formatd);
type='N';
if format=:'DATETIME' then ddtype='DATETIME';
else if format=:'DATE' or format=:'DDMMYY' or format=:'MMDDYY'
or format=:'YYMMDD' or format=:'E8601DA' or format=:'B8601DA'
or format=:'MONYY'
then ddtype='DATE';
else if format=:'TIME' then ddtype='TIME';
else ddtype='NUMERIC';
end;
if label='' then label=name;
run;
%mend mp_getcols;

View File

@@ -39,22 +39,25 @@
/* must use SQL as proc datasets does not support length changes */ /* must use SQL as proc datasets does not support length changes */
proc sql noprint; proc sql noprint;
create table &outds as create table &outds as
select a.TABLE_CATALOG as libref select upcase(a.TABLE_CATALOG) as libref
,a.TABLE_NAME ,upcase(a.TABLE_NAME) as TABLE_NAME
,a.constraint_type ,a.constraint_type
,a.constraint_name ,a.constraint_name
,b.column_name ,b.column_name
from dictionary.TABLE_CONSTRAINTS a from dictionary.TABLE_CONSTRAINTS a
left join dictionary.constraint_column_usage b left join dictionary.constraint_column_usage b
on a.TABLE_CATALOG=b.TABLE_CATALOG on upcase(a.TABLE_CATALOG)=upcase(b.TABLE_CATALOG)
and a.TABLE_NAME=b.TABLE_NAME and upcase(a.TABLE_NAME)=upcase(b.TABLE_NAME)
and a.constraint_name=b.constraint_name and a.constraint_name=b.constraint_name
where a.TABLE_CATALOG="&lib" /**
and b.TABLE_CATALOG="&lib" * We cannot apply this clause to the underlying dictionary table. See:
* https://communities.sas.com/t5/SAS-Programming/Unexpected-Where-Clause-behaviour-in-dictionary-TABLE/m-p/771554#M244867
*/
where calculated libref="&lib"
%if "&ds" ne "" %then %do; %if "&ds" ne "" %then %do;
and a.TABLE_NAME="&ds" and upcase(a.TABLE_NAME)="&ds"
and b.TABLE_NAME="&ds" and upcase(b.TABLE_NAME)="&ds"
%end; %end;
; ;
%mend; %mend mp_getconstraints;

View File

@@ -332,4 +332,4 @@ run;
run; run;
%end; %end;
%mend; %mend mp_getdbml;

View File

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

View File

@@ -70,4 +70,4 @@ create table &outds (rename=(
out=&outds(rename=(_name_=NAME COL1=MAXLEN)); out=&outds(rename=(_name_=NAME COL1=MAXLEN));
run; run;
%mend; %mend mp_getmaxvarlengths;

53
base/mp_gsubfile.sas Normal file
View File

@@ -0,0 +1,53 @@
/**
@file
@brief Performs a text substitution on a file
@details Makes use of the GSUB function in LUA to perform a text substitution
in a file - either in-place, or writing to a new location. The benefit of
using LUA is that the entire file can be loaded into a single variable,
thereby side stepping the 32767 character limit in a data step.
Usage:
%let file=%sysfunc(pathname(work))/file.txt;
%let str=replace/me;
%let rep=with/this;
data _null_;
file "&file";
put "&str";
run;
%mp_gsubfile(file=&file, patternvar=str, replacevar=rep)
data _null_;
infile "&file";
input;
list;
run;
@param file= (0) The file to perform the substitution on
@param patternvar= A macro variable containing the Lua
[pattern](https://www.lua.org/pil/20.2.html) to search for. Due to the use
of special (magic) characters in Lua patterns, it is safer to pass the NAME
of the macro variable containing the string, rather than the value itself.
@param replacevar= The name of the macro variable containing the replacement
_string_.
@param outfile= (0) The file to write the output to. If zero, then the file
is overwritten in-place.
<h4> SAS Macros </h4>
@li ml_gsubfile.sas
<h4> Related Macros </h4>
@li mp_gsubfile.test.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_gsubfile(file=0,
patternvar=,
replacevar=,
outfile=0
)/*/STORE SOURCE*/;
%ml_gsubfile()
%mend mp_gsubfile;

View File

@@ -301,4 +301,4 @@
%return; %return;
%end; %end;
%mend; %mend mp_guesspk;

View File

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

104
base/mp_include.sas Normal file
View File

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

View File

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

View File

@@ -75,4 +75,4 @@ select distinct lowcase(memname)
) )
%end; %end;
%mend; %mend mp_lib2cards;

77
base/mp_lib2inserts.sas Normal file
View File

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

255
base/mp_lockanytable.sas Normal file
View File

@@ -0,0 +1,255 @@
/**
@file
@brief Mechanism for locking tables to prevent parallel modifications
@details Uses a control table to enable ANY table to be locked for updates.
Only useful if every update uses the macro! Used heavily within
[Data Controller for SAS](https://datacontroller.io).
The underlying table is structured as per the MAKETABLE action.
@param [in] action The action to be performed. Valid values:
@li LOCK - Sets the lock flag, also confirms if a SAS lock is available
@li UNLOCK - Unlocks the table
@li MAKETABLE - creates the control table (ctl_ds)
@param [in] lib= (WORK) The libref of the table to lock. Should already be
assigned.
@param [in] ds= The dataset to lock
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking.
Should already be assigned and available.
@param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mp_lockfilecheck.sas
@li mf_getuser.sas
<h4> Related Macros </h4>
@li mp_lockanytable.test.sas
@version 9.2
**/
%macro mp_lockanytable(
action
,lib= WORK
,ds=0
,ref=
,ctl_ds=0
,loops=25
,loop_secs=1
);
data _null_;
if _n_=1 then putlog "&sysmacroname entry vars:";
set sashelp.vmacro;
where scope="&sysmacroname";
put name '=' value;
run;
%mp_abort(iftrue= (&ds=0 and &action ne MAKETABLE)
,mac=&sysmacroname
,msg=%str(dataset was not provided)
)
%mp_abort(iftrue= (&ctl_ds=0)
,mac=&sysmacroname
,msg=%str(Control dataset was not provided)
)
/* set up lib & mac vars */
%let lib=%upcase(&lib);
%let ds=%upcase(&ds);
%let action=%upcase(&action);
%local user x trans msg abortme;
%let user=%mf_getuser();
%let abortme=0;
%mp_abort(iftrue= (&action ne LOCK & &action ne UNLOCK & &action ne MAKETABLE)
,mac=&sysmacroname
,msg=%str(Invalid action (&action) provided)
)
/* if an err condition exists, exit before we even begin */
%mp_abort(iftrue= (&syscc>0 and &action=LOCK)
,mac=&sysmacroname
,msg=%str(aborting due to syscc=&syscc on LOCK entry)
)
/* do not bother locking work tables (else may affect all WORK libraries) */
%if (%upcase(&lib)=WORK or %str(&lib)=%str()) & &action ne MAKETABLE %then %do;
%put NOTE: WORK libraries will not be registered in the locking system.;
%return;
%end;
/* do not proceed if no observations can be processed */
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
,mac=&sysmacroname
,msg=%str(options obs = 0. syserrortext=&syserrortext)
)
%if &ACTION=LOCK %then %do;
/* abort if a SAS lock is already in place, or cannot be applied */
%mp_lockfilecheck(&lib..&ds)
/* next, check there is a record for this table */
%local record_exists_check;
proc sql noprint;
select count(*) into: record_exists_check from &ctl_ds
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit;
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
%if &record_exists_check=0 %then %do;
data _null_;
putlog "&sysmacroname: adding record to lock table..";
run;
data ;
if 0 then set &ctl_ds;
LOCK_LIB ="&lib";
LOCK_DS="&ds";
LOCK_STATUS_CD='LOCKED';
LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
LOCK_USER_NM="&user";
LOCK_PID="&sysjobid";
LOCK_REF="&ref";
output;stop;
run;
%let trans=&syslast;
proc append base=&ctl_ds data=&trans;
run;
%end;
/* if record does exist, perform lock attempts */
%else %do x=1 %to &loops;
data _null_;
putlog "&sysmacroname: attempting lock (iteration &x) "@;
putlog "at %sysfunc(datetime(),datetime19.) ..";
run;
proc sql;
update &ctl_ds
set LOCK_STATUS_CD='LOCKED'
, LOCK_START_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
, LOCK_USER_NM="&user"
, LOCK_PID="&sysjobid"
, LOCK_REF="&ref"
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit;
/**
* NOTE - occasionally SQL server will return an err code (deadlocked
* transaction). If so, ignore it, keep calm, and carry on..
*/
%if &syscc>0 %then %do;
data _null_;
putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: Update failed. "@;
putlog "Resetting err conditions and re-attempting.";
putlog "NOTE- syscc=&syscc syserr=&syserr sqlrc=&sqlrc";
putlog 'NOTE-' / 'NOTE-';
run;
%let syscc=0;
%let sqlrc=0;
%end;
/* now check if the record was successfully updated */
%local success_check;
proc sql noprint;
select count(*) into: success_check from &ctl_ds
where LOCK_LIB ="&lib" and LOCK_DS="&ds"
and LOCK_PID="&sysjobid" and LOCK_STATUS_CD='LOCKED';
quit;
%if &success_check=0 %then %do;
%if &x < &loops %then %do;
/* pause before next check */
data _null_;
putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: table locked, waiting "@;
putlog "%sysfunc(sleep(&loop_inc)) seconds.. ";
putlog "NOTE- (iteration &x of &loops)";
putlog 'NOTE-' / 'NOTE-';
run;
%end;
%else %do;
%let msg=Unable to lock &lib..&ds via &ctl_ds after &loops attempts.\n
Please ask your administrator to investigate!;
%let abortme=1;
%end;
%end;
%else %do;
data _null_;
putlog 'NOTE-' / 'NOTE-';
putlog "NOTE- &sysmacroname: Table &lib..&ds locked at "@
putlog " %sysfunc(datetime(),datetime19.) (iteration &x)"@;
putlog 'NOTE-' / 'NOTE-';
run;
%if &syscc>0 %then %do;
%put setting syscc(&syscc) back to 0;
%let syscc=0;
%end;
%let x=&loops; /* no more iterations needed */
%end;
%end;
%end;
%else %if &ACTION=UNLOCK %then %do;
%local status;
proc sql noprint;
select LOCK_STATUS_CD into: status from &ctl_ds
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit;
%if &syscc>0 %then %put syscc=&syscc sqlrc=&sqlrc;
%if &status=LOCKED %then %do;
data _null_;
putlog "&sysmacroname: unlocking &lib..&ds:";
run;
proc sql;
update &ctl_ds
set LOCK_STATUS_CD='UNLOCKED'
, LOCK_END_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt
, LOCK_USER_NM="&user"
, LOCK_PID="&sysjobid"
, LOCK_REF="&ref"
where LOCK_LIB ="&lib" and LOCK_DS="&ds";
quit;
%end;
%else %if &status=UNLOCKED %then %do;
%put %str(WAR)NING: &lib..&ds is already unlocked!;
%end;
%else %do;
%put NOTE: Unrecognised STATUS_CD (&status) in &ctl_ds;
%let abortme=1;
%end;
%end;
%else %if &action=MAKETABLE %then %do;
proc sql;
create table &ctl_ds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%end;
%else %do;
%let msg=lock_anytable given unsupported action (&action);
%let abortme=1;
%end;
/* catch errors - mp_abort must be called outside of a logic block */
%mp_abort(iftrue=(&abortme=1),
msg=%superq(msg),
mac=&sysmacroname
)
%exit_macro:
data _null_;
put "&sysmacroname: Exit vars: action=&action lib=&lib ds=&ds";
put " syscc=&syscc sqlrc=&sqlrc syserr=&syserr";
run;
%mend mp_lockanytable;

97
base/mp_lockfilecheck.sas Normal file
View File

@@ -0,0 +1,97 @@
/**
@file
@brief Aborts if a SAS lock file is in place, or if one cannot be applied.
@details Used in conjuction with the mp_lockanytable macro.
More info here: https://sasensei.com/flash/24
Usage:
data work.test; a=1;run;
%mp_lockfilecheck(work.test)
@param [in] libds The libref.dataset for which to check the lock status
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getattrc.sas
<h4> Related Macros </h4>
@li mp_lockanytable.sas
@li mp_lockfilecheck.test.sas
@version 9.2
**/
%macro mp_lockfilecheck(
libds
)/*/STORE SOURCE*/;
data _null_;
if _n_=1 then putlog "&sysmacroname entry vars:";
set sashelp.vmacro;
where scope="&sysmacroname";
put name '=' value;
run;
%mp_abort(iftrue= (&syscc>0)
,mac=checklock.sas
,msg=Aborting with syscc=&syscc on entry.
)
%mp_abort(iftrue= (&libds=0)
,mac=&sysmacroname
,msg=%str(libds not provided)
)
%local msg lib ds;
%let lib=%upcase(%scan(&libds,1,.));
%let ds=%upcase(%scan(&libds,2,.));
/* do not proceed if no observations can be processed */
%let msg=options obs = 0. syserrortext=%superq(syserrortext);
%mp_abort(iftrue= (%sysfunc(getoption(OBS))=0)
,mac=checklock.sas
,msg=%superq(msg)
)
data _null_;
putlog "Checking engine & member type";
run;
%local engine memtype;
%let memtype=%mf_getattrc(&libds,MTYPE);
%let engine=%mf_getattrc(&libds,ENGINE);
%if &engine ne V9 and &engine ne BASE %then %do;
data _null_;
putlog "Lib &lib is not assigned using BASE engine - uses &engine instead";
putlog "SAS lock check will not be performed";
run;
%return;
%end;
%else %if &memtype ne DATA %then %do;
%put NOTE: Cannot lock a VIEW!! Memtype=&memtype;
%return;
%end;
data _null_;
putlog "Engine = &engine, memtype=&memtype";
putlog "Attempting lock statement";
run;
lock &libds;
%local abortme;
%let abortme=0;
%if &syscc>0 or &SYSLCKRC ne 0 %then %do;
%let msg=Unable to apply lock on &libds (SYSLCKRC=&SYSLCKRC syscc=&syscc);
%put %str(ERR)OR: &sysmacroname: &msg;
%let abortme=1;
%end;
lock &libds clear;
%mp_abort(iftrue= (&abortme=1)
,mac=&sysmacroname
,msg=%superq(msg)
)
%mend mp_lockfilecheck;

View File

@@ -14,14 +14,14 @@
We take the standard definition one step further by embedding the informat We take the standard definition one step further by embedding the informat
in the table header row, like so: in the table header row, like so:
|var1:$|var2:best.|var3:date9.| |var1:$32|var2:best.|var3:date9.|
|---|---|---| |---|---|---|
|some text|42|01JAN1960| |some text|42|01JAN1960|
|blah|1|31DEC1999| |blah|1|31DEC1999|
Which resolves to: Which resolves to:
|var1:$|var2:best.|var3:date9.| |var1:$32|var2:best.|var3:date9.|
|---|---|---| |---|---|---|
|some text|42|01JAN1960| |some text|42|01JAN1960|
|blah|1|31DEC1999| |blah|1|31DEC1999|

View File

@@ -39,4 +39,4 @@
,dttm=%sysfunc(datetime()); ,dttm=%sysfunc(datetime());
quit; quit;
%mend; %mend mp_perflog;

View File

@@ -85,4 +85,4 @@
"with record &record and " _n_=; "with record &record and " _n_=;
%end; %end;
%mend; %mend mp_prevobs;

View File

@@ -87,4 +87,4 @@ insert into &outds select distinct * from &append_ds;
) )
%end; %end;
%mend; %mend mp_recursivejoin;

View File

@@ -30,4 +30,4 @@ data _null_;
end; end;
run; run;
%mend; %mend mp_resetoption;

View File

@@ -46,4 +46,4 @@
%mend; %mend mp_runddl;

View File

@@ -117,4 +117,4 @@ proc sql
%put process finished at %sysfunc(datetime(),datetime19.); %put process finished at %sysfunc(datetime(),datetime19.);
%mend; %mend mp_searchdata;

View File

@@ -49,4 +49,4 @@
quit; quit;
%mend; %mend mp_setkeyvalue;

View File

@@ -71,4 +71,4 @@
proc append base=&libds data=&syslast nowarn;run; proc append base=&libds data=&syslast nowarn;run;
options &etls_syntaxcheck; options &etls_syntaxcheck;
%mend; %mend mp_stprequests;

View File

@@ -134,4 +134,4 @@ run;
%mp_binarycopy(inloc="&inloc",outref=_webout) %mp_binarycopy(inloc="&inloc",outref=_webout)
%end; %end;
%mend; %mend mp_streamfile;

View File

@@ -89,4 +89,4 @@ quit;
libname &lib clear; libname &lib clear;
%mend; %mend mp_testjob;

View File

@@ -22,8 +22,11 @@
|mustbevalidname|can be anything, oops, %abort!!| |mustbevalidname|can be anything, oops, %abort!!|
@param [in] debug= (log) Provide the _debug value @param [in] debug= (log) Provide the _debug value
@param [in] viyaresult=(WEBOUT_JSON) The Viya result type to return. For @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 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 @param [out] outlib= (0) Output libref to contain the final tables. Set to
0 if the service output is not in JSON format. 0 if the service output is not in JSON format.
@param [out] outref= (0) Output fileref to create, to contain the full _webout @param [out] outref= (0) Output fileref to create, to contain the full _webout
@@ -47,17 +50,18 @@
inputfiles=0, inputfiles=0,
inputparams=0, inputparams=0,
debug=log, debug=log,
mdebug=0,
outlib=0, outlib=0,
outref=0, outref=0,
viyaresult=WEBOUT_JSON viyaresult=WEBOUT_JSON,
viyacontext=SAS Job Execution compute context
)/*/STORE SOURCE*/; )/*/STORE SOURCE*/;
%local mdebug; %local dbg;
%if &debug ne 0 %then %do; %if &mdebug=1 %then %do;
%let mdebug=1;
%put &sysmacroname entry vars:; %put &sysmacroname entry vars:;
%put _local_; %put _local_;
%end; %end;
%else %let mdebug=0; %else %let dbg=*;
/* sanitise inputparams */ /* sanitise inputparams */
%local pcnt; %local pcnt;
@@ -212,6 +216,7 @@
data &ds1; data &ds1;
retain _program "&program"; retain _program "&program";
retain _contextname "&viyacontext";
set &ds1; set &ds1;
putlog "&sysmacroname inputparams:"; putlog "&sysmacroname inputparams:";
putlog (_all_)(=); putlog (_all_)(=);

View File

@@ -56,4 +56,4 @@ data &outds;
duration_seconds=end_dttm-start_dttm; duration_seconds=end_dttm-start_dttm;
run; run;
%mend; %mend mp_testwritespeedlibrary;

View File

@@ -68,4 +68,4 @@ data &outds ;
rc=filename('tmp'); rc=filename('tmp');
run; run;
%mend; %mend mp_tree;

View File

@@ -2,10 +2,10 @@
@file mp_unzip.sas @file mp_unzip.sas
@brief Unzips a zip file @brief Unzips a zip file
@details Opens the zip file and copies all the contents to another directory. @details Opens the zip file and copies all the contents to another directory.
It is not possible to retain permissions / timestamps, also the BOF marker It is not possible to retain permissions / timestamps, also the BOF marker
is lost so it cannot extract binary files. is lost so it cannot extract binary files.
Usage: 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; %inc mc;
@@ -16,8 +16,9 @@
@li mf_mkdir.sas @li mf_mkdir.sas
@li mf_getuniquefileref.sas @li mf_getuniquefileref.sas
@param ziploc= fileref or quoted full path to zip file ("/path/to/file.zip") @param ziploc= Fileref or quoted full path to zip file ("/path/to/file.zip")
@param outdir= directory in which to write the outputs (created if non existant) @param outdir= (%sysfunc(pathname(work))) Directory in which to write the
outputs (created if non existant)
@version 9.4 @version 9.4
@author Allan Bowe @author Allan Bowe
@@ -35,7 +36,8 @@
%let fname2=%mf_getuniquefileref(); %let fname2=%mf_getuniquefileref();
%let fname3=%mf_getuniquefileref(); %let fname3=%mf_getuniquefileref();
filename &fname1 ZIP &ziploc; * Macro variable &datazip would be read from the file*; /* Macro variable &datazip would be read from the file */
filename &fname1 ZIP &ziploc;
/* Read the "members" (files) from the ZIP file */ /* Read the "members" (files) from the ZIP file */
data _data_(keep=memname isFolder); data _data_(keep=memname isFolder);
@@ -63,4 +65,4 @@ data _null_;
!!'filename &fname2 clear; filename &fname3 clear;'); !!'filename &fname2 clear; filename &fname3 clear;');
run; run;
%mend; %mend mp_unzip;

View File

@@ -90,4 +90,4 @@ alter table &libds modify &var char(&len);
%mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES) %mp_createconstraints(inds=&dsconst,outds=&dsconst._addd,execute=YES)
%mend; %mend mp_updatevarlength;

37
base/mp_wait4file.sas Normal file
View File

@@ -0,0 +1,37 @@
/**
@file
@brief Wait until a file arrives before continuing execution
@details Loops with a `sleep()` command until a file arrives or the max wait
period expires.
@example
Wait 3 minutes OR for /tmp/flag.txt to appear
%mp_wait4file(/tmp/flag.txt , maxwait=60*3)
@param [in] file The file to wait for. Must be provided.
@param [in] maxwait= (0) Number of seconds to wait. If set to zero, will
loop indefinitely (to a maximum of 46 days, per SAS [documentation](
https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a001418809.htm
)). Otherwise, execution will proceed upon sleep expiry.
@param [in] interval= (1) The wait period between sleeps, in seconds
**/
%macro mp_wait4file(file, maxwait=0, interval=1);
%if %str(&file)=%str() %then %do;
%put %str(ERR)OR: file not provided;
%end;
data _null_;
maxwait=&maxwait;
if maxwait=0 then maxwait=60*60*24*46;
do until (fileexist("&file") or slept>maxwait );
slept=sum(slept,sleep(&interval,1));
end;
run;
%mend mp_wait4file;

59
base/mp_webin.sas Normal file
View File

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

View File

@@ -16,11 +16,18 @@
@li mp_dirlist.sas @li mp_dirlist.sas
@param in= unquoted filepath, dataset of files or directory to zip @param in= unquoted filepath, dataset of files or directory to zip
@param type= FILE, DATASET, DIRECTORY. (FILE / DATASET not ready yet) @param type= (FILE) Valid values:
@param outname= output file to create, without .zip extension @li FILE - /full/path/and/filename.extension to a particular file
@param outpath= location for output zip file @li DATASET - a dataset containing a list of files to zip (see `incol`)
@li DIRECTORY - a directory to zip
@param outname= (FILE) Output file to create, _without_ .zip extension
@param outpath= (%sysfunc(pathname(WORK))) Parent folder for output zip file
@param incol= if DATASET input, say which column contains the filepath @param incol= if DATASET input, say which column contains the filepath
<h4> Related Macros </h4>
@li mp_unzip.sas
@li mp_zip.test.sas
@version 9.2 @version 9.2
@author Allan Bowe @author Allan Bowe
@source https://github.com/sasjs/core @source https://github.com/sasjs/core
@@ -51,9 +58,9 @@ ods package open nopf;
set &ds; set &ds;
length __command $4000; length __command $4000;
if file_or_folder='file'; if file_or_folder='file';
command=cats('ods package add file="',filepath __command=cats('ods package add file="',filepath
,'" mimetype="application/x-compress";'); ,'" mimetype="application/x-compress";');
call execute(command); call execute(__command);
run; run;
/* tidy up */ /* tidy up */
%if &debug=NO %then %do; %if &debug=NO %then %do;
@@ -64,11 +71,10 @@ ods package open nopf;
data _null_; data _null_;
set &in; set &in;
length __command $4000; length __command $4000;
command=cats('ods package add file="',&incol __command=cats('ods package add file="',&incol
,'" mimetype="application/x-compress";'); ,'" mimetype="application/x-compress";');
call execute(command); call execute(__command);
run; run;
ods package add file="&in" mimetype="application/x-compress";
%end; %end;
@@ -76,4 +82,4 @@ ods package publish archive properties
(archive_name="&outname..zip" archive_path="&outpath"); (archive_name="&outname..zip" archive_path="&outpath");
ods package close; ods package close;
%mend; %mend mp_zip;

View File

@@ -22,8 +22,8 @@ for file in files:
for line in infile: for line in infile:
ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n") ml.write(" put '" + line.rstrip().replace("'","''") + " ';\n")
ml.write("run;\n\n") ml.write("run;\n\n")
ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\";\n\n") ml.write("%inc \"%sysfunc(pathname(work))/" + name + ".lua\" /source2;\n\n")
ml.write("%mend;\n") ml.write("%mend " + name + ";\n")
ml.close() ml.close()
@@ -84,7 +84,7 @@ options noquotelenmax;
""" """
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header) f.write(header)
folders=['base','meta','metax','viya','lua'] folders=['base','meta','metax','viya','lua','fcmp']
for folder in folders: for folder in folders:
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")] filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
filenames.sort() filenames.sort()

View File

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

79
fcmp/mcf_string2file.sas Normal file
View File

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

25
lua/gsubfile.lua Normal file
View File

@@ -0,0 +1,25 @@
local fpath, outpath, file, fcontent
-- configure in / out paths
fpath = sas.symget("file")
outpath = sas.symget("outfile")
if ( outpath == 0 )
then
outpath=fpath
end
-- open file and perform the substitution
file = io.open(fpath,"r")
fcontent = file:read("*all")
file:close()
fcontent = string.gsub(
fcontent,
sas.symget(sas.symget("patternvar")),
sas.symget(sas.symget("replacevar"))
)
-- write the file back out
file = io.open(outpath, "w+")
io.output(file)
io.write(fcontent)
io.close(file)

44
lua/ml_gsubfile.sas Normal file
View File

@@ -0,0 +1,44 @@
/**
@file ml_gsubfile.sas
@brief Compiles the gsubfile.lua lua file
@details Writes gsubfile.lua to the work directory
and then includes it.
Usage:
%ml_gsubfile()
**/
%macro ml_gsubfile();
data _null_;
file "%sysfunc(pathname(work))/ml_gsubfile.lua";
put 'local fpath, outpath, file, fcontent ';
put ' ';
put '-- configure in / out paths ';
put 'fpath = sas.symget("file") ';
put 'outpath = sas.symget("outfile") ';
put 'if ( outpath == 0 ) ';
put 'then ';
put ' outpath=fpath ';
put 'end ';
put ' ';
put '-- open file and perform the substitution ';
put 'file = io.open(fpath,"r") ';
put 'fcontent = file:read("*all") ';
put 'file:close() ';
put 'fcontent = string.gsub( ';
put ' fcontent, ';
put ' sas.symget(sas.symget("patternvar")), ';
put ' sas.symget(sas.symget("replacevar")) ';
put ') ';
put ' ';
put '-- write the file back out ';
put 'file = io.open(outpath, "w+") ';
put 'io.output(file) ';
put 'io.write(fcontent) ';
put 'io.close(file) ';
run;
%inc "%sysfunc(pathname(work))/ml_gsubfile.lua" /source2;
%mend ml_gsubfile;

View File

@@ -389,6 +389,6 @@ data _null_;
put '-- JSON.LUA ENDS HERE '; put '-- JSON.LUA ENDS HERE ';
run; run;
%inc "%sysfunc(pathname(work))/ml_json.lua"; %inc "%sysfunc(pathname(work))/ml_json.lua" /source2;
%mend; %mend ml_json;

View File

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

View File

@@ -95,4 +95,4 @@ run;
filename __us2grp clear; filename __us2grp clear;
%mend; %mend mm_adduser2group;

Some files were not shown because too many files have changed in this diff Show More