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

Compare commits

..

81 Commits

Author SHA1 Message Date
Allan Bowe
4e564b5409 fix: refreshed the testing toolkit, added debug options, updated documentation, included one new test (mv_getjobcode.sas) 2021-05-08 23:27:55 +03:00
Allan Bowe
298acc4e50 fix: setting default to best. over 8. for mf_getformat with force option 2021-05-07 11:20:01 +03:00
Allan Bowe
af98909753 Merge pull request #19 from sasjs/mp_assert
feat: new (generic) mp_assert macro, and new feature (type filter) fo…
2021-05-06 20:59:49 +03:00
Allan Bowe
b9d33b38bf feat: new (generic) mp_assert macro, and new feature (type filter) for mf_getvarlist. Added/updated tests for mp_filtercheck and mp_validatecol and mf_getvarlist. 2021-05-06 20:58:38 +03:00
Allan Bowe
b61b5f1856 fix: adding dependency 2021-05-06 19:05:14 +03:00
Allan Bowe
805474bb46 Merge pull request #18 from sasjs/jobresult
fix: enabling fileref as output option for sas code obtained via mm_g…
2021-05-06 15:27:18 +03:00
Allan Bowe
61701f3c6a fix: enabling fileref as output option for sas code obtained via mm_getstpcode. Also updated some doc headers and macro footers. 2021-05-06 15:06:13 +03:00
Allan Bowe
f20d7476bf Merge pull request #17 from sasjs/jobresult
feat: new mv_getjobresult.sas macro, corresponding test, and additional fixes
2021-05-06 01:08:56 +03:00
Allan Bowe
04a3189a89 feat: new mv_getjobresult.sas macro, corresponding test, and additional fixes 2021-05-06 01:07:25 +03:00
Allan Bowe
b1380983ec fix: missing comma 2021-05-05 20:16:26 +03:00
Allan Bowe
b4834f9b40 fix: updates following test runs in Studio 2021-05-05 20:12:06 +03:00
Allan Bowe
1b5ad93cad chore: updating all.sas 2021-05-05 11:48:38 +03:00
Allan Bowe
f2942f2032 chore: adding sasjsresults to .gitignore 2021-05-05 01:39:53 +03:00
Allan Bowe
4198448b81 chore: removing temp results folder 2021-05-05 01:39:29 +03:00
Allan Bowe
47a33452e0 Merge pull request #16 from sasjs/testrelease
feat: new macro for validating inputs (mp_validatecol.sas), also a re…
2021-05-05 01:36:38 +03:00
Allan Bowe
fb21a0adfd feat: new macro for validating inputs (mp_validatecol.sas), also a refresh of the tests now that sasjs test is released. All tests are passing 2021-05-05 01:35:00 +03:00
Allan Bowe
e01b06b640 feat: new assertion macro (mp_assertcols.sas) to test for column existence (or not) 2021-05-04 21:53:57 +03:00
Allan Bowe
24380ddf26 Merge branch 'main' of github.com:sasjs/core 2021-05-03 22:44:07 +03:00
Allan Bowe
1ef42d45af fix: wrapping filter query in brackets to allow logic to be encapsulated when using with other logic sources 2021-05-03 22:43:56 +03:00
Allan Bowe
6ee13a2779 Merge pull request #15 from sasjs/removewarns
fix: removing WARNINGs from code logic
2021-05-03 20:30:43 +03:00
Allan Bowe
ffd2e135dc fix: removing WARNINGs from code logic 2021-05-03 20:28:48 +03:00
Allan Bowe
7f2ad5fc66 feat: mp_filtervalidate.sas - to run a proc sql validate against the target table to ensure validity 2021-05-03 13:48:24 +03:00
ff1eb54cc3 chore: updating logo 2021-05-03 10:59:25 +02:00
Allan Bowe
d6235c6357 chore: reducing raw_value size to 4000 for wider DB support 2021-05-03 11:17:20 +03:00
Allan Bowe
98118adb9a feat: new assertion macro for testing the values in a target column. Designed for use with 'sasjs test'. 2021-05-03 01:00:36 +03:00
Allan Bowe
369c4412f3 fix: adding tests for mp_filtergenerate, also updating the corresponding macros following test results 2021-05-02 22:11:44 +03:00
Allan Bowe
7d7608f06c chore: updating all.sas 2021-05-02 19:12:08 +03:00
Allan Bowe
3791cb8a2c feat: two new macros for checking a filter query, and then generating a filter query. One test to cover the generation part. One more macro to provide assertions on the number of rows in a table, compatible with the upcoming 'sasjs test' feature. 2021-05-02 19:10:37 +03:00
Allan Bowe
ff82f7d75c chore: header update in mf_getvarlist 2021-05-01 20:33:45 +03:00
Allan Bowe
fdd566e8ce fix: setting server headers only if STREAM mode enabled to avoid 'Function is only valid for filerefs using the CACHE access method.' error when testing STPs from Studio. Also removing proc json as it cannot handle invalid characters. 2021-05-01 16:27:41 +03:00
Beast
328f8c260b chore: updating all.sas 2021-05-01 15:34:30 +03:00
Beast
029169ac80 feat: adding mf_getxengine macro for determining the engine of a sas fileref 2021-05-01 15:32:45 +03:00
66ff1de7a9 fix: reducing logging 2021-04-23 23:06:37 +02:00
053290c7df chore: updating header of mp_hashdataset 2021-04-23 07:56:33 +02:00
af71a5e53b feat: new macro for hashing a table (mp_hashdataset) 2021-04-23 00:33:11 +02:00
ecdce86287 fix: adding all.sas also 2021-04-17 00:11:33 +02:00
ba1272aaf7 fix: updating mv_createwebservice to support 0x01 hex characters, adding a test (and test scaffolding) as part of this. The test scaffolding will be updated once goes live - for now it is being deployed as a service. 2021-04-17 00:11:06 +02:00
d6056b9397 fix: adding mod statement to _webout to enable sas-side sasjs testing 2021-04-10 12:41:17 +02:00
Allan Bowe
00511c72c2 fix: removing deprecated params from mm_createstp 2021-04-09 11:46:37 +00:00
Allan Bowe
1d6f04fd56 chore: adding macro related lint settings and sasjs as a recommended extension 2021-04-08 15:59:08 +00:00
af4dbb5632 feat: switching to DATASTEP over PROCJSON for json delivery in sasjs/adapter 2021-04-08 09:49:50 +02:00
Allan Bowe
f48c291dce Merge pull request #13 from sasjs/issue12
fix: switching to data step for JSON generation in mp_jsonout and the…
2021-04-08 08:45:54 +01:00
rafgag
18be74a1c2 Update mp_jsonout.sas
mod option added to the file statement in the last %else %if statement (&action=CLOSE) to avoid output file being overwritten
2021-04-08 09:03:05 +02:00
Allan Bowe
456d10a90e fix: switching to data step for JSON generation in mp_jsonout and the sasjs/adapter for improved reliability when data contains special characters. Closes #12 2021-04-07 22:28:42 +00:00
Allan Bowe
a7fdb52231 fix: sasmeta vs basesas results 2021-04-05 18:26:31 +00:00
Allan Bowe
066ed00e44 chore: reducing line length in lint by 5 characters 2021-04-05 13:31:09 +00:00
Allan Bowe
49fbc210ad Update .gitpod.yml 2021-04-05 14:22:58 +01:00
Allan Bowe
951aa474f2 Update .gitpod.dockerfile 2021-04-05 14:16:22 +01:00
Allan Bowe
961dd54ee0 Update .gitpod.dockerfile 2021-04-05 13:29:39 +01:00
Allan Bowe
921354dac7 Update .gitpod.yml 2021-04-05 12:59:31 +01:00
Allan Bowe
48212f8797 Merge pull request #11 from sasjs/sasjslintfixes
sasjs lint fixes
2021-04-03 20:58:58 +01:00
cb8992dade fix: remove .githooks now we have sasjs lint 2021-04-03 21:54:50 +02:00
7dec3120be chore: dependencies 2021-04-03 21:35:44 +02:00
9568b17f20 feat: enabling sasjs lint as a git pre-commit hook when contributing to @sasjs/core. To use, just run
> ghooks@2.0.4 install /home/zah/git/core/node_modules/ghooks
> node ./bin/module-install

> @sasjs/core@1.0.0 postinstall /home/zah/git/core
> node-git-hooks

Installing Git hooks...
added 14 packages from 12 contributors and audited 205 packages in 4.23s

17 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities from the repository.
2021-04-03 21:34:40 +02:00
0a38056c69 fix: linting 2021-04-03 21:30:51 +02:00
Allan Bowe
096bf4fa11 chore: more indentation fixes 2021-04-03 18:21:29 +00:00
Allan Bowe
030c4a4fc1 fix: .sasjslint file and indentation issues 2021-04-03 18:10:41 +00:00
1b70205cab feat: two new macros, mp_mdtablewrite.sas (will create a markdown table from a sas dataset) and mm_getlibmetadiffs.sas (will create a set of datasets to describe the differences between a library in metadata and the physical library) 2021-04-02 13:38:46 +02:00
539447ed06 chore: fixing all.sas 2021-03-30 16:34:11 +02:00
e3c333ea39 feat: adding mm_deletelibrary.sas, a macro to easily delete a SAS Library from metadata. Documentation: https://core.sasjs.io/mm__deletelibrary_8sas.html 2021-03-30 00:02:20 +02:00
ae72446f85 chore: updating all.sas 2021-03-30 00:01:43 +02:00
2b6bf4bd02 chore: doxy fixes 2021-03-30 00:01:08 +02:00
6dbb3760e0 Merge branch 'main' of github.com:sasjs/core into main 2021-03-29 23:02:33 +02:00
200d9a5761 chore: doxy updates 2021-03-29 23:02:17 +02:00
Allan Bowe
01a9a5b823 Update README.md 2021-03-25 20:09:24 +01:00
Allan Bowe
35eadd0e9d Update README.md 2021-03-25 15:45:28 +01:00
5cdca95216 chore: vscode properties 2021-03-24 19:38:02 +01:00
Allan Bowe
81b75a32ed chore: latest sasjs version 2021-03-24 18:34:58 +00:00
b7f5a2ec00 chore: devops rules, updated gitignore and git hook 2021-03-22 10:14:52 +01:00
db859bbf1d chore: adding a git hook to prevent sas files from appearing with capital letters. To install, just run
> @sasjs/core@1.0.0 postinstall
> node-git-hooks

Installing Git hooks...

up to date, audited 2 packages in 1s

found 0 vulnerabilities.
2021-03-21 23:59:21 +01:00
27b56e8efd set executable 2021-03-21 23:33:19 +01:00
28ea218d02 chore: adding pre-commit hook 2021-03-21 22:24:46 +01:00
f356e1f351 chore: updating CONTRIBUTING, preventing files with spaces being added, adding git hooks package 2021-03-21 22:23:50 +01:00
4b375e0b8c fix: leading zero in time component of mf_uid() 2021-03-19 20:34:16 +01:00
7db207dd1c chore: updating all.sas 2021-03-19 20:29:35 +01:00
Allan Bowe
ffdfc57aa6 Merge pull request #9 from tmoody/main
fix: Closes issue #8. Optionally raise SYSCC and exit early on job no…
2021-03-17 17:32:35 +01:00
Trevor Moody
6fc8408988 fix: Syntax correction for raise_err parameter 2021-03-17 16:31:47 +00:00
Trevor Moody
eeb25fa5bc fix: Closes issue #8. Optionally raise SYSCC and exit early on job not completing successfully 2021-03-17 16:27:10 +00:00
Allan Bowe
521d128afe chore: gitpod file 2021-03-17 09:21:16 +00:00
Allan Bowe
0135dd6c8f chore: updating gitpod files 2021-03-17 07:40:48 +00:00
Allan Bowe
1b66c59dc0 feat: adding gitpod.yml with sasjs code extension 2021-03-15 09:21:27 +00:00
160 changed files with 8913 additions and 2122 deletions

10
.gitignore vendored
View File

@@ -1,3 +1,13 @@
node_modules
.DS_Store
sasjsbuild/
sasjsresults/
# avoid filenames with spaces being committed to source control
**\ **
# ignore the mc_* files - containing macros for individual libraries
mc_*
# ignore .env files as they can contain sasjs access tokens
*.env*

6
.gitpod.dockerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM gitpod/workspace-full
RUN sudo apt-get update \
&& sudo apt-get install -y \
doxygen \
&& sudo rm -rf /var/lib/apt/lists/*

8
.gitpod.yml Normal file
View File

@@ -0,0 +1,8 @@
tasks:
- init: npm i -g @sasjs/cli
image:
file: .gitpod.dockerfile
vscode:
extensions:
- sasjs.sasjs-for-vscode

13
.sasjslint Normal file
View File

@@ -0,0 +1,13 @@
{
"noTrailingSpaces": true,
"noEncodedPasswords": true,
"hasDoxygenHeader": true,
"hasMacroNameInMend": false,
"hasMacroParentheses": true,
"noNestedMacros": false,
"noSpacesInFileNames": true,
"maxLineLength": 135,
"lowerCaseFileNames": true,
"noTabIndentation": true,
"indentationMultiple": 2
}

9
.vscode/.editorconfig vendored Normal file
View File

@@ -0,0 +1,9 @@
{
"search.exclude": {
"**/sasjsbuild/**": true,
"**/dist/**":true
},
"editor.insertSpaces": true,
"editor.tabSize": 2,
"trim_trailing_whitespace": true
}

5
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"sasjs.sasjs-for-vscode"
]
}

10
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"editor.detectIndentation": true,
"editor.formatOnSave": true,
"editor.rulers": [
80
],
"files.trimTrailingWhitespace": true
}

View File

@@ -27,6 +27,5 @@ To contribute:
1. Create your feature branch (`git checkout -b myfeature`)
2. Make your change
3. Update the `all.sas` file (`python3 build.py`)
4. Commit the change, using the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0) standard
5. Push and make a PR
4. Push and make a PR

View File

@@ -1,6 +1,6 @@
# Macro Core
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of Application Development on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
Much quality. Many standards. The **Macro Core** library exists to save time and development effort! Herein ye shall find a veritable host of MIT-licenced, production quality SAS macros. These are a mix of tools, utilities, functions and code generators that are useful in the context of [Application Development](https://sasapps.io) on the SAS platform (eg https://datacontroller.io). [Contributions](https://github.com/sasjs/core/blob/main/CONTRIBUTING.md) are welcomed.
You can download and compile them all in just two lines of SAS code:
@@ -9,7 +9,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
```
Documentation: https://sasjs.github.io/core.github.io/files.html
Documentation: https://core.sasjs.io
# Components
@@ -84,7 +84,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
## File Properties
- filenames much match macro names
- filenames must be lowercase
- filenames must be lowercase, without spaces
- macro names must be lowercase
- one macro per file
- prefixes:
@@ -99,7 +99,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- unix style line endings (lf)
- individual lines should be no more than 80 characters long
- UTF-8
- no trailing white space
## Header Properties
@@ -118,7 +118,7 @@ All macros must be commented in the doxygen format, to enable the [online docume
### Dependencies
SAS code can contain one of two types of dependency - SAS Macros, and SAS Programs. When compiling projects using the [SASjs CLI](https://cli.sasjs.io) the doxygen header is scanned for ` @li` items under the following headers:
```
```sas
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mm_assignlib.sas
@@ -136,13 +136,15 @@ When contributing to this library, it is therefore important to ensure that all
## Coding Standards
- Indentation = 2 spaces. No tabs!
- no trailing white space
- no invisible characters, other than spaces. If invisibles are needed, use hex literals.
- Macro variables should not have the trailing dot (`&var` not `&var.`) unless necessary to prevent incorrect resolution
- The closing `%mend;` should not contain the macro name.
- The closing `%mend;` should **not** contain the macro name.
- All macros should be defined with brackets, even if no variables are needed - ie `%macro x();` not `%macro x;`
- Mandatory parameters should be positional, all optional parameters should be keyword (var=) style.
- All dataset references must be 2 level (eg `work.blah`, not `blah`). This is to avoid contention when options [DATASTMTCHK](https://support.sas.com/documentation/cdl/en/lrdict/64316/HTML/default/viewer.htm#a000279064.htm)=ALLKEYWORDS is in effect.
- Avoid naming collisions! All macro variables should be local scope. Use system generated work tables where possible - eg `data ; set sashelp.class; run; data &output; set &syslast; run;`
- If you have a long-running SQL query, the use of a `quit;` statement is recommended in order to benefit from the timing statistics.
- The use of `quit;` for `proc sql` is optional unless you are looking to benefit from the timing statistics.
# General Notes

2977
all.sas

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,10 @@
input; putlog _infile_;
i=1;
retain logonce 0;
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0
then do;
call symputx('logline',_n_);
logonce+1;
end;
@@ -112,7 +115,8 @@
%let syscc=0;
%if %symexist(SYS_JES_JOB_URI) %then %do;
/* refer web service output to file service in one hit */
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name="_webout.json";
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI"
name="_webout.json";
%let rc=%sysfunc(fcopy(_web,_webout));
%end;
%else %do;

View File

@@ -4,7 +4,7 @@
@details Returns 0 if ANY of the variables do not exist, or 1 if they ALL do.
Usage:
%put %mf_existVarList(sashelp.class, age sex name dummyvar)
%put %mf_existVarList(sashelp.class, age sex name dummyvar);
<h4> SAS Macros </h4>
@li mf_abort.sas
@@ -29,7 +29,7 @@
%let dsid=%sysfunc(open(&libds,is));
%if &dsid=0 %then %do;
%put WARNING: unable to open &libds in mf_existvarlist (&dsid);
%put %str(WARN)ING: unable to open &libds in mf_existvarlist (&dsid);
%end;
%if %sysfunc(attrn(&dsid,NVARS))=0 %then %do;

View File

@@ -23,7 +23,7 @@
%local dsid rc;
%let dsid=%sysfunc(open(&libds,is));
%if &dsid = 0 %then %do;
%put WARNING: Cannot open %trim(&libds), system message below;
%put %str(WARN)ING: Cannot open %trim(&libds), system message below;
%put %sysfunc(sysmsg());
-1
%end;

View File

@@ -22,6 +22,9 @@
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getxengine.sas
**/
/** @cond */
@@ -32,7 +35,9 @@
/* in case the parameter is a libref.tablename, pull off just the libref */
%let libref = %upcase(%scan(&libref, 1, %str(.)));
%let dsid=%sysfunc(open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i));
%let dsid=%sysfunc(
open(sashelp.vlibnam(where=(libname="%upcase(&libref)")),i)
);
%if (&dsid ^= 0) %then %do;
%let engnum=%sysfunc(varnum(&dsid,ENGINE));
%let rc=%sysfunc(fetch(&dsid));

View File

@@ -16,7 +16,7 @@
%macro mf_getkeyvalue(key,libds=work.mp_setkeyvalue
)/*/STORE SOURCE*/;
%local ds dsid key valc valn type rc;
%local ds dsid key valc valn type rc;
%let dsid=%sysfunc(open(&libds(where=(key="&key"))));
%syscall set(dsid);
%let rc = %sysfunc(fetch(&dsid));

View File

@@ -27,21 +27,23 @@
or "&sysprocessmode"= "SAS Compute Server" %then %do;
SASVIYA
%end;
%else %if "&sysprocessmode"="SAS Stored Process Server" %then %do;
%else %if "&sysprocessmode"="SAS Stored Process Server"
or "&sysprocessmode"="SAS Workspace Server"
%then %do;
SASMETA
%return;
%end;
%else %do;
SAS
BASESAS
%return;
%end;
%end;
%else %if %symexist(_metaport) %then %do;
%else %if %symexist(_metaport) or %symexist(_metauser) %then %do;
SASMETA
%return;
%end;
%else %do;
SAS
BASESAS
%return;
%end;
%end;

View File

@@ -23,9 +23,9 @@
8.
NOTE: Variable renegade does not exist in test
@param libds Two part dataset (or view) reference.
@param var Variable name for which a format should be returned
@param force Set to 1 to supply a default if the variable has no format
@param [in] libds Two part dataset (or view) reference.
@param [in] var Variable name for which a format should be returned
@param [in] force=(0) Set to 1 to supply a default if the variable has no format
@returns outputs format
@author Allan Bowe
@@ -60,7 +60,7 @@
%let vlen = %sysfunc(varlen(&dsid, &vnum));
%let vtype = %sysfunc(vartype(&dsid, &vnum.));
%if &vtype=C %then %let vformat=$&vlen..;
%else %let vformat=8.;
%else %let vformat=best.;
%end;
@@ -68,4 +68,4 @@
%let rc = %sysfunc(close(&dsid));
/* Return variable format */
&vformat
%mend;
%mend mf_getVarFormat;

View File

@@ -10,14 +10,21 @@
returns:
> List of Variables=Name Sex Age Height Weight
For a seperated list of column values:
%put %mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
returns:
> "Name","Sex","Age","Height","Weight"
@param libds Two part dataset (or view) reference.
@param dlm= provide a delimiter (eg comma or space) to separate the vars
@param quote= use either DOUBLE or SINGLE to quote the results
@param [in] libds Two part dataset (or view) reference.
@param [in] dlm= ( ) Provide a delimiter (eg comma or space) to separate the
variables
@param [in] quote= (none) use either DOUBLE or SINGLE to quote the results
@param [in] typefilter= (A) Filter for certain types of column. Valid values:
@li A Return All columns
@li C Return Character columns
@li N Return Numeric columns
@version 9.2
@author Allan Bowe
@@ -27,9 +34,10 @@
%macro mf_getvarlist(libds
,dlm=%str( )
,quote=no
,typefilter=A
)/*/STORE SOURCE*/;
/* declare local vars */
%local outvar dsid nvars x rc dlm q var;
%local outvar dsid nvars x rc dlm q var vtype;
/* credit Rowland Hale - byte34 is double quote, 39 is single quote */
%if %upcase(&quote)=DOUBLE %then %let q=%qsysfunc(byte(34));
@@ -37,23 +45,24 @@
/* open dataset in macro */
%let dsid=%sysfunc(open(&libds));
%if &dsid %then %do;
%let nvars=%sysfunc(attrn(&dsid,NVARS));
%if &nvars>0 %then %do;
/* add first dataset variable to global macro variable */
%let outvar=&q.%sysfunc(varname(&dsid,1))&q.;
/* add remaining variables with supplied delimeter */
/* add variables with supplied delimeter */
%do x=1 %to &nvars;
/* get variable type */
%let vtype=%sysfunc(vartype(&dsid,&x));
%if &vtype=&typefilter or &typefilter=A %then %do;
%let var=&q.%sysfunc(varname(&dsid,&x))&q.;
%if &var=&q&q %then %do;
%put &sysmacroname: Empty column found in &libds!;
%let var=&q. &q.;
%end;
%if &x=1 %then %let outvar=&var;
%if %quote(&outvar)=%quote() %then %let outvar=&var;
%else %let outvar=&outvar.&dlm.&var.;
%end;
%end;
%end;
%let rc=%sysfunc(close(&dsid));
%end;
%else %do;
@@ -61,4 +70,4 @@
%let rc=%sysfunc(close(&dsid));
%end;
&outvar
%mend;
%mend mf_getvarlist;

View File

@@ -45,4 +45,4 @@ Usage:
%let rc = %sysfunc(close(&dsid));
/* Return variable type */
&vtype
%mend;
%mend mf_getvartype;

43
base/mf_getxengine.sas Normal file
View File

@@ -0,0 +1,43 @@
/**
@file
@brief Returns the engine type of a SAS fileref
@details Queries sashelp.vextfl to get the xengine value.
Usage:
filename feng temp;
%put %mf_getxengine(feng);
returns:
> TEMP
@param fref The fileref to check
@returns The XENGINE value in sashelp.vextfl or 0 if not found.
@version 9.2
@author Allan Bowe
<h4> Related Macros </h4>
@li mf_getengine.sas
**/
%macro mf_getxengine(fref
)/*/STORE SOURCE*/;
%local dsid engnum rc engine;
%let dsid=%sysfunc(
open(sashelp.vextfl(where=(fileref="%upcase(&fref)")),i)
);
%if (&dsid ^= 0) %then %do;
%let engnum=%sysfunc(varnum(&dsid,XENGINE));
%let rc=%sysfunc(fetch(&dsid));
%let engine=%sysfunc(getvarc(&dsid,&engnum));
%* put &fref. ENGINE is &engine.;
%let rc= %sysfunc(close(&dsid));
%end;
%else %let engine=0;
&engine
%mend;

View File

@@ -9,7 +9,8 @@
%put mf_isblank(&var);
inspiration: https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
inspiration:
https://support.sas.com/resources/papers/proceedings09/022-2009.pdf
@param param VALUE to be checked

View File

@@ -6,7 +6,8 @@
%let isdir=%mf_isdir(/tmp);
With thanks and full credit to Andrea Defronzo - https://www.linkedin.com/in/andrea-defronzo-b1a47460/
With thanks and full credit to Andrea Defronzo -
https://www.linkedin.com/in/andrea-defronzo-b1a47460/
@param path full path of the file/directory to be checked

View File

@@ -1,7 +1,8 @@
/**
@file mf_mval.sas
@brief Returns a macro variable value if the variable exists
@details Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
@details
Use this macro to avoid repetitive use of `%if %symexist(MACVAR) %then`
type logic.
Usage:

View File

@@ -12,7 +12,8 @@
@param basestr The string to be modified
@param trimstr The string to be removed from the end of `basestr`, if it exists
@param trimstr The string to be removed from the end of `basestr`, if it
exists
@return output returns result with the value of `trimstr` removed from the end

View File

@@ -1,11 +1,11 @@
/**
@file
@brief Creates a Unique ID based on system time in a friendly format
@brief Creates a unique ID based on system time in friendly format
@details format = YYYYMMDD_HHMMSSmmm_<sysjobid>_<3randomDigits>
%put %mf_uid();
@version 9.2
@version 9.3
@author Allan Bowe
**/
@@ -14,8 +14,8 @@
)/*/STORE SOURCE*/;
%local today now;
%let today=%sysfunc(today(),yymmddn8.);
%let now=%sysfunc(compress(%sysfunc(time(),time12.3),:.));
%let now=%sysfunc(compress(%sysfunc(time(),tod12.3),:.));
&today._&now._&sysjobid._%sysevalf(%sysfunc(ranuni(0))*999,CEIL)
%mend;
%mend mf_uid;

View File

@@ -30,7 +30,7 @@
%local count_base count_extr i i2 extr_word base_word match outvar;
%if %length(&str1)=0 or %length(&str2)=0 %then %do;
%put WARNING: empty string provided!;
%put %str(WARN)ING: empty string provided!;
%put base string (str1)= &str1;
%put compare string (str2) = &str2;
%return;

View File

@@ -6,11 +6,11 @@
results back to the client in an STP Web App context, or completely stop
in the case of a batch run.
Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored Process
environments. This macro takes a unique approach - we set the SAS syscc to 0,
run `stpsrvset('program error', 0)` (if SAS 9) and then - we open a macro
but don't close it! This provides a graceful abort for SAS web services in all
web enabled environments.
Using SAS Abort Cancel mechanisms can cause hung sessions in some Stored
Process environments. This macro takes a unique approach - we set the SAS
syscc to 0, run `stpsrvset('program error', 0)` (if SAS 9) and then - we open
a macro but don't close it! This provides a graceful abort for SAS web
services in all web enabled environments.
@param mac= to contain the name of the calling macro
@param msg= message to be returned
@@ -48,7 +48,10 @@
input; putlog _infile_;
i=1;
retain logonce 0;
if (_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR") and logonce=0 then do;
if (
_infile_=:"%str(WARN)ING" or _infile_=:"%str(ERR)OR"
) and logonce=0 then
do;
call symputx('logline',_n_);
logonce+1;
end;

56
base/mp_assert.sas Normal file
View File

@@ -0,0 +1,56 @@
/**
@file
@brief Generic assertion
@details Useful in the context of writing sasjs tests. The results of the
test are _appended_ to the &outds. table.
Example usage:
%mp_assert(iftrue=(1=1),
desc=Obviously true
)
%mp_assert(iftrue=(1=0),
desc=Will fail
)
@param [in] iftrue= (1=1) A condition where, if true, the test is a PASS.
Else, the test is a fail.
@param [in] desc= (Testing observations) The user provided test description
@param [out] outds= (work.test_results) The output dataset to contain the
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns|
@version 9.2
@author Allan Bowe
**/
%macro mp_assert(iftrue=(1=1),
desc=0,
outds=work.test_results
)/*/STORE SOURCE*/;
data ;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_comments="&sysmacroname: Test result of "!!symget('iftrue');
%if %eval(%unquote(&iftrue)) %then %do;
test_result='PASS';
%end;
%else %do;
test_result='FAIL';
%end;
run;
%local ds ;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%mend mp_assert;

145
base/mp_assertcols.sas Normal file
View File

@@ -0,0 +1,145 @@
/**
@file
@brief Asserts the existence (or not) of columns
@details Useful in the context of writing sasjs tests. The results of the
test are _appended_ to the &outds. table.
Example usage:
%mp_assertcols(sashelp.class,
cols=name age sex,
test=ALL,
desc=check all columns exist
)
%mp_assertcols(sashelp.class,
cols=a b c,
test=NONE
)
%mp_assertcols(sashelp.class,
cols=age depth,
test=ANY
)
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mf_existvarlist.sas
@li mf_getvarlist.sas
@li mf_wordsinstr1butnotstr2.sas
@li mp_abort.sas
@param [in] inds The input library.dataset to test for values
@param [in] cols= The list of columns to check for
@param [in] desc= (Testing observations) The user provided test description
@param [in] test= (ALL) The test to apply. Valid values are:
@li ALL - Test is a PASS if ALL columns exist in &inds
@li ANY - Test is a PASS if ANY of the columns exist in &inds
@li NONE - Test is a PASS if NONE of the columns exist in &inds
@param [out] outds= (work.test_results) The output dataset to contain the
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---|
|User Provided description|PASS|Column &inds contained ALL columns|
<h4> Related Macros </h4>
@li mp_assertdsobs.sas
@li mp_assertcolvals.sas
@li mp_assertdsobs.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_assertcols(inds,
cols=0,
test=ALL,
desc=0,
outds=work.test_results
)/*/STORE SOURCE*/;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
%local lib ds ;
%let lib=%scan(&inds,1,%str(.));
%let ds=%scan(&inds,2,%str(.));
%let cols=%upcase(&cols);
%mp_abort(iftrue= (%mf_existds(&lib..&ds)=0)
,mac=&sysmacroname
,msg=%str(&lib..&ds not found!)
)
%mp_abort(iftrue= (&cols=0)
,mac=&sysmacroname
,msg=%str(No cols provided)
)
%let test=%upcase(&test);
%if &test ne ANY and &test ne ALL and &test ne NONE %then %do;
%mp_abort(
mac=&sysmacroname,
msg=%str(Invalid test - &test)
)
%end;
/**
* now do the actual test!
*/
%local result;
%if %mf_existVarList(&inds,&cols)=1 %then %let result=ALL;
%else %do;
%local targetcols compare;
%let targetcols=%upcase(%mf_getvarlist(&inds));
%let compare=%mf_wordsinstr1butnotstr2(
Str1=&cols,
Str2=&targetcols
);
%if %cmpres(&compare)=%cmpres(&cols) %then %let result=NONE;
%else %let result=SOME;
%end;
data;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
if test_description='0'
then test_description="Testing &inds for existence of &test of: &cols";
test_result='FAIL';
test_comments="&sysmacroname: &inds has &result columns ";
%if &test=ALL %then %do;
%if &result=ALL %then %do;
test_result='PASS';
%end;
%end;
%else %if &test=ANY %then %do;
%if &result=SOME %then %do;
test_result='PASS';
%end;
%end;
%else %if &test=NONE %then %do;
%if &result=NONE %then %do;
test_result='PASS';
%end;
%end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;
run;
%local ds;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%mend;

147
base/mp_assertcolvals.sas Normal file
View File

@@ -0,0 +1,147 @@
/**
@file
@brief Asserts the values in a column
@details Useful in the context of writing sasjs tests. The results of the
test are _appended_ to the &outds. table.
Example usage:
data work.checkds;
do checkval='Jane','James','Jill';
output;
end;
run;
%mp_assertcolvals(sashelp.class.name,
checkvals=work.checkds.checkval,
desc=At least one value has a match,
test=ANYVAL
)
data work.check;
do val='M','F';
output;
end;
run;
%mp_assertcolvals(sashelp.class.sex,
checkvals=work.check.val,
desc=All values have a match,
test=ALLVALS
)
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mf_nobs.sas
@li mp_abort.sas
@param [in] indscol The input library.dataset.column to test for values
@param [in] checkvals= A library.dataset.column value containing a UNIQUE
list of values to be compared against the source (indscol).
@param [in] desc= (Testing observations) The user provided test description
@param [in] test= (ALLVALS) The test to apply. Valid values are:
@li ALLVALS - Test is a PASS if ALL values have a match in checkvals
@li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals
@param [out] outds= (work.test_results) The output dataset to contain the
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---|
|User Provided description|PASS|Column &indscol contained ALL target vals|
<h4> Related Macros </h4>
@li mp_assertdsobs.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_assertcolvals(indscol,
checkvals=0,
test=ALLVALS,
desc=mp_assertcolvals - no desc provided,
outds=work.test_results
)/*/STORE SOURCE*/;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
%local lib ds col clib cds ccol nobs;
%let lib=%scan(&indscol,1,%str(.));
%let ds=%scan(&indscol,2,%str(.));
%let col=%scan(&indscol,3,%str(.));
%mp_abort(iftrue= (%mf_existds(&lib..&ds)=0)
,mac=&sysmacroname
,msg=%str(&lib..&ds not found!)
)
%mp_abort(iftrue= (&checkvals=0)
,mac=&sysmacroname
,msg=%str(Set CHECKVALS to a library.dataset.column containing check vals)
)
%let clib=%scan(&checkvals,1,%str(.));
%let cds=%scan(&checkvals,2,%str(.));
%let ccol=%scan(&checkvals,3,%str(.));
%mp_abort(iftrue= (%mf_existds(&clib..&cds)=0)
,mac=&sysmacroname
,msg=%str(&clib..&cds not found!)
)
%let nobs=%mf_nobs(&clib..&cds);
%mp_abort(iftrue= (&nobs=0)
,mac=&sysmacroname
,msg=%str(&clib..&cds is empty!)
)
%let test=%upcase(&test);
%if &test ne ALLVALS and &test ne ANYVAL %then %do;
%mp_abort(
mac=&sysmacroname,
msg=%str(Invalid test - &test)
)
%end;
%local result orig;
%let result=-1;
%let orig=-1;
proc sql noprint;
select count(*) into: result
from &lib..&ds
where &col not in (
select &ccol from &clib..&cds
);
select count(*) into: orig from &lib..&ds;
quit;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc after macro query)
)
data;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_result='FAIL';
test_comments="&sysmacroname: &lib..&ds..&col has &result values "
!!"not in &clib..&cds..&ccol ";
%if &test=ANYVAL %then %do;
if &result < &orig then test_result='PASS';
%end;
%else %if &test=ALLVALS %then %do;
if &result=0 then test_result='PASS';
%end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;
run;
%local ds;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%mend;

89
base/mp_assertdsobs.sas Normal file
View File

@@ -0,0 +1,89 @@
/**
@file
@brief Asserts the number of observations in a dataset
@details Useful in the context of writing sasjs tests. The results of the
test are _appended_ to the &outds. table.
Example usage:
%mp_assertdsobs(sashelp.class) %* tests if any observations are present;
<h4> SAS Macros </h4>
@li mf_nobs.sas
@li mp_abort.sas
@param [in] inds input dataset to test for presence of observations
@param [in] desc= (Testing observations) The user provided test description
@param [in] test= (HASOBS) The test to apply. Valid values are:
@li HASOBS - Test is a PASS if the input dataset has any observations
@li EMPTY - Test is a PASS if input dataset is empty
@li EQUALS [integer] - Test passes if obs count matches the provided integer
@param [out] outds= (work.test_results) The output dataset to contain the
results. If it does not exist, it will be created, with the following format:
|TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256|
|---|---|---|
|User Provided description|PASS|Dataset &inds has XX obs|
<h4> Related Macros </h4>
@li mp_assertcolvals.sas
@version 9.2
@author Allan Bowe
**/
%macro mp_assertdsobs(inds,
test=HASOBS,
desc=Testing observations,
outds=work.test_results
)/*/STORE SOURCE*/;
%local nobs;
%let nobs=%mf_nobs(&inds);
%let test=%upcase(&test);
%if %substr(&test.xxxxx,1,6)=EQUALS %then %do;
%let val=%scan(&test,2,%str( ));
%mp_abort(iftrue= (%DATATYP(&val)=CHAR)
,mac=&sysmacroname
,msg=%str(Invalid test - &test, expected EQUALS [integer])
)
%let test=EQUALS;
%end;
%else %if &test ne HASOBS and &test ne EMPTY %then %do;
%mp_abort(
mac=&sysmacroname,
msg=%str(Invalid test - &test)
)
%end;
data;
length test_description $256 test_result $4 test_comments $256;
test_description=symget('desc');
test_result='FAIL';
test_comments="&sysmacroname: Dataset &inds has &nobs observations";
%if &test=HASOBS %then %do;
if &nobs>0 then test_result='PASS';
%end;
%else %if &test=EMPTY %then %do;
if &nobs=0 then test_result='PASS';
%end;
%else %if &test=EQUALS %then %do;
if &nobs=&val then test_result='PASS';
%end;
%else %do;
test_comments="&sysmacroname: Unsatisfied test condition - &test";
%end;
run;
%local ds;
%let ds=&syslast;
proc append base=&outds data=&ds;
run;
proc sql;
drop table &ds;
%mend;

View File

@@ -4,7 +4,8 @@
@details Reads in a file byte by byte and writes it back out. Is an
os-independent method to copy files. In case of naming collision, the
default filerefs can be modified.
Based on http://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
Based on:
https://stackoverflow.com/questions/13046116/using-sas-to-copy-a-text-file
%mp_binarycopy(inloc="/home/me/blah.txt", outref=_webout)

View File

@@ -23,7 +23,7 @@
%macro mp_cleancsv(in=NOTPROVIDED,out=NOTPROVIDED,qchar='22'x);
%if "&in"="NOTPROVIDED" or "&out"="NOTPROVIDED" %then %do;
%put %str(ERR)OR: Please provide valid input (&in) and output (&out) locations;
%put %str(ERR)OR: Please provide valid input (&in) & output (&out) locations;
%return;
%end;

View File

@@ -57,8 +57,11 @@
)/*/STORE SOURCE*/;
%let getattrs=%upcase(&getattrs)XX;
data &outds (compress=no keep=file_or_folder filepath filename ext msg directory);
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80 ext $20 msg $200;
data &outds(compress=no
keep=file_or_folder filepath filename ext msg directory
);
length directory filepath $500 fref fref2 $8 file_or_folder $6 filename $80
ext $20 msg $200;
%if &fref=0 %then %do;
rc = filename(fref, "&path");
%end;
@@ -98,7 +101,7 @@ data &outds (compress=no keep=file_or_folder filepath filename ext msg directory
if index(fmsg,'File is in use') or index(dmsg,'is not a directory')
then file_or_folder='file';
else if index(fmsg, 'Insufficient authorization') then file_or_folder='file';
else if index(fmsg,'Insufficient authorization') then file_or_folder='file';
else if file_or_folder='' then file_or_folder='locked';
if file_or_folder='file' then do;

View File

@@ -14,16 +14,18 @@
- explicity setting a unix LF
- constraints / indexes etc
@param [in] base_ds= Should be two level - eg work.blah. This is the table that
is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create. Optional -
if omitted, will be same as BASE_DS.
@param [in] base_ds= Should be two level - eg work.blah. This is the table
that is converted to a cards file.
@param [in] tgt_ds= Table that the generated cards file would create.
Optional - if omitted, will be same as BASE_DS.
@param [out] cards_file= Location in which to write the (.sas) cards file
@param [in] maxobs= to limit output to the first <code>maxobs</code> observations
@param [in] showlog= whether to show generated cards file in the SAS log (YES/NO)
@param [in] maxobs= to limit output to the first <code>maxobs</code>
observations
@param [in] showlog= whether to show generated cards file in the SAS log
(YES/NO)
@param [in] outencoding= provide encoding value for file statement (eg utf-8)
@param [in] append= If NO then will rebuild the cards file if it already exists,
otherwise will append to it. Used by the mp_lib2cards.sas macro.
@param [in] append= If NO then will rebuild the cards file if it already
exists, otherwise will append to it. Used by the mp_lib2cards.sas macro.
@version 9.2
@@ -41,7 +43,7 @@
%local i setds nvars;
%if not %sysfunc(exist(&base_ds)) %then %do;
%put WARNING: &base_ds does not exist;
%put %str(WARN)ING: &base_ds does not exist;
%return;
%end;
@@ -59,14 +61,16 @@ select count(*) into: nvars from dictionary.columns
where libname="%scan(%upcase(&base_ds),1)"
and memname="%scan(%upcase(&base_ds),2)";
%if &nvars=0 %then %do;
%put WARNING: Dataset &base_ds has no variables! It will not be converted.;
%put %str(WARN)ING: Dataset &base_ds has no variables, will not be converted.;
%return;
%end;
/* get indexes */
proc sort data=sashelp.vindex
(where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
and upcase(memname)="%scan(%upcase(&base_ds),2)"))
proc sort
data=sashelp.vindex(
where=(upcase(libname)="%scan(%upcase(&base_ds),1)"
and upcase(memname)="%scan(%upcase(&base_ds),2)")
)
out=_data_;
by indxname indxpos;
run;

View File

@@ -19,7 +19,7 @@
)/*/STORE SOURCE*/;
%if not %sysfunc(exist(&ds)) %then %do;
%put WARNING: &ds does not exist;
%put %str(WARN)ING: &ds does not exist;
%return;
%end;

188
base/mp_filtercheck.sas Normal file
View File

@@ -0,0 +1,188 @@
/**
@file
@brief Checks an input filter table for validity
@details Performs checks on the input table to ensure it arrives in the
correct format. This is necessary to prevent code injection. Will update
SYSCC to 1008 if bad records are found, and call mp_abort.sas for a
graceful service exit (configurable).
Used for dynamic filtering in [Data Controller for SAS&reg;](https://datacontroller.io).
Usage:
%mp_filtercheck(work.filter,targetds=sashelp.class,outds=work.badrecords)
The input table should have the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|---|---|---|---|---|---|
|AND|AND|1|AGE|=|12|
|AND|AND|1|SEX|<=|'M'|
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|AND|OR|2|Weight|>=|7|
Rules applied:
@li GROUP_LOGIC - only AND/OR
@li SUBGROUP_LOGIC - only AND/OR
@li SUBGROUP_ID - only integers
@li VARIABLE_NM - must be in the target table
@li OPERATOR_NM - only =/>/</<=/>=/BETWEEN/IN/NOT IN/NE/CONTAINS
@li RAW_VALUE - no unquoted values except integers, commas and spaces.
@returns The &outds table containing any bad rows, plus a REASON_CD column.
@param [in] inds The table to be checked, with the format above
@param [in] targetds= The target dataset against which to verify VARIABLE_NM
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
@param [out] outds= The output table, which is a copy of the &inds. table
plus a REASON_CD column, containing only bad records. If bad records found,
the SYSCC value will be set to 1008 (general data problem). Downstream
processes should check this table (and return code) before continuing.
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_getuniquefileref.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@li mf_nobs.sas
@li mp_filtergenerate.sas
@li mp_filtervalidate.sas
<h4> Related Macros </h4>
@li mp_filtergenerate.sas
@li mp_filtervalidate.sas
@version 9.3
@author Allan Bowe
@todo Support date / hex / name literals and exponents in RAW_VALUE field
**/
%macro mp_filtercheck(inds,targetds=,outds=work.badrecords,abort=YES);
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
/* Validate input column */
%local vtype;
%let vtype=%mf_getvartype(&inds,RAW_VALUE);
%mp_abort(iftrue=(&abort=YES and &vtype ne C),
mac=&sysmacroname,
msg=%str(%str(ERR)OR: RAW_VALUE must be character)
)
%if &vtype ne C %then %do;
%put &sysmacroname: RAW_VALUE must be character;
%let syscc=42;
%return;
%end;
/**
* Sanitise the values based on valid value lists, then strip out
* quotes, commas, periods and spaces.
* Only numeric values should remain
*/
%local reason_cd;
data &outds;
/*length GROUP_LOGIC SUBGROUP_LOGIC $3 SUBGROUP_ID 8 VARIABLE_NM $32
OPERATOR_NM $10 RAW_VALUE $4000;*/
set &inds;
length reason_cd $32;
/* closed list checks */
if GROUP_LOGIC not in ('AND','OR') then do;
REASON_CD='GROUP_LOGIC should be either AND or OR';
putlog REASON_CD= GROUP_LOGIC=;
output;
end;
if SUBGROUP_LOGIC not in ('AND','OR') then do;
REASON_CD='SUBGROUP_LOGIC should be either AND or OR';
putlog REASON_CD= SUBGROUP_LOGIC=;
output;
end;
if mod(SUBGROUP_ID,1) ne 0 then do;
REASON_CD='SUBGROUP_ID should be integer';
putlog REASON_CD= SUBGROUP_ID=;
output;
end;
if upcase(VARIABLE_NM) not in
(%upcase(%mf_getvarlist(&targetds,dlm=%str(,),quote=SINGLE)))
then do;
REASON_CD="VARIABLE_NM not in &targetds";
putlog REASON_CD= VARIABLE_NM=;
output;
end;
if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
then do;
REASON_CD='Invalid OPERATOR_NM';
putlog REASON_CD= OPERATOR_NM=;
output;
end;
/* special logic */
if OPERATOR_NM='BETWEEN' then raw_value1=tranwrd(raw_value,' AND ','');
else if OPERATOR_NM in ('IN','NOT IN') then do;
if substr(raw_value,1,1) ne '('
or substr(cats(reverse(raw_value)),1,1) ne ')'
then do;
REASON_CD='Missing brackets in RAW_VALUE';
putlog REASON_CD= OPERATOR_NM= raw_value= raw_value1= ;
output;
end;
else raw_value1=substr(raw_value,2,max(length(raw_value)-2,0));
end;
else raw_value1=raw_value;
/* remove nested literals eg '' */
raw_value1=tranwrd(raw_value1,"''",'');
/* now match string literals (always single quotes) */
raw_value2=raw_value1;
regex = prxparse("s/(\').*?(\')//");
call prxchange(regex,-1,raw_value2);
/* remove commas and periods*/
raw_value3=compress(raw_value2,',.');
/* output records that contain values other than digits and spaces */
if notdigit(compress(raw_value3,' '))>0 then do;
putlog raw_value3= $hex32.;
REASON_CD='Invalid RAW_VALUE';
putlog REASON_CD= raw_value= raw_value1= raw_value2= raw_value3=;
output;
end;
run;
data _null_;
set &outds;
call symputx('REASON_CD',reason_cd,'l');
stop;
run;
%mp_abort(iftrue=(&abort=YES and %mf_nobs(&outds)>0),
mac=&sysmacroname,
msg=%str(Filter issues in &inds, reason: &reason_cd, details in &outds)
)
%if %mf_nobs(&outds)>0 %then %do;
%let syscc=1008;
%return;
%end;
/**
* syntax checking passed but it does not mean the filter is valid
* for that we can run a proc sql validate query
*/
%local fref1;
%let fref1=%mf_getuniquefileref();
%mp_filtergenerate(&inds,outref=&fref1)
/* this macro will also set syscc to 1008 if any issues found */
%mp_filtervalidate(&fref1,&targetds,outds=&outds,abort=&abort)
%mend mp_filtercheck;

102
base/mp_filtergenerate.sas Normal file
View File

@@ -0,0 +1,102 @@
/**
@file
@brief Generates a filter clause from an input table, to a fileref
@details Uses the input table to generate an output filter clause.
This feature is used to create dynamic dropdowns in [Data Controller for SAS&reg](
https://datacontroller.io). The input table should be in the format below:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$4000|
|---|---|---|---|---|---|
|AND|AND|1|AGE|=|12|
|AND|AND|1|SEX|<=|'M'|
|AND|OR|2|Name|NOT IN|('Jane','Alfred')|
|AND|OR|2|Weight|>=|7|
Note - if the above table is received from an external client, the values
should first be validated using the mp_filtercheck.sas macro to avoid risk
of SQL injection.
To generate the filter, run the following code:
data work.filtertable;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtergenerate(work.filtertable,outref=myfilter)
data _null_;
infile myfilter;
input;
put _infile_;
run;
Will write the following query to the log:
> (
> AGE = 12
> AND
> SEX <= 'M'
> ) AND (
> Name NOT IN ('Jane','Alfred')
> OR
> Weight >= 7
> )
@param [in] inds The input table with query values
@param [out] outref= The output fileref to contain the filter clause. Will
be created (or replaced).
<h4> Related Macros </h4>
@li mp_filtercheck.sas
@li mp_filtervalidate.sas
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mf_nobs.sas
@version 9.3
@author Allan Bowe
**/
%macro mp_filtergenerate(inds,outref=filter);
%mp_abort(iftrue= (&syscc ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc - on macro entry)
)
filename &outref temp;
%if %mf_nobs(&inds)=0 %then %do;
/* ensure we have a default filter */
data _null_;
file &outref;
put '1=1';
run;
%end;
%else %do;
data _null_;
file &outref lrecl=32800;
set &inds end=last;
by SUBGROUP_ID;
if _n_=1 then put '((';
else if first.SUBGROUP_ID then put +1 GROUP_LOGIC '(';
else put +2 SUBGROUP_LOGIC;
put +4 VARIABLE_NM OPERATOR_NM RAW_VALUE;
if last.SUBGROUP_ID then put ')'@;
if last then put ')';
run;
%end;
%mend;

104
base/mp_filtervalidate.sas Normal file
View File

@@ -0,0 +1,104 @@
/**
@file
@brief Checks a generated filter query for validity
@details Runs a generated filter in proc sql with the validate option.
Used in mp_filtercheck.sas in an fcmp container.
Built to support dynamic filtering in
[Data Controller for SAS&reg;](https://datacontroller.io).
Usage:
data work.filtertable;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$10. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,AGE,=,12
AND,AND,1,SEX,<=,"'M'"
AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
AND,OR,2,Weight,>=,7
;;;;
run;
%mp_filtergenerate(work.filtertable,outref=myfilter)
%mp_filtervalidate(myfilter,sashelp.class)
@returns The SYSCC value will be 1008 if there are validation issues.
@param [in] inref The input fileref to validate (generated by
mp_filtergenerate.sas)
@param [in] targetds The target dataset against which to verify the query
@param [out] abort= (YES) If YES will call mp_abort.sas on any exceptions
@param [out] outds= (work.mp_filtervalidate) Output dataset containing the
error / warning message, if one exists. If this table contains any rows,
there are problems!
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mf_nobs.sas
@li mp_abort.sas
<h4> Related Macros </h4>
@li mp_filtercheck.sas
@li mp_filtergenerate.sas
@version 9.3
@author Allan Bowe
**/
%macro mp_filtervalidate(inref,targetds,abort=YES,outds=work.mp_filtervalidate);
%mp_abort(iftrue= (&syscc ne 0 or &syserr ne 0)
,mac=&sysmacroname
,msg=%str(syscc=&syscc / syserr=&syserr - on macro entry)
)
%local fref1;
%let fref1=%mf_getuniquefileref();
data _null_;
file &fref1;
infile &inref end=eof;
if _n_=1 then do;
put "proc sql;";
put "validate select * from &targetds";
put "where " ;
end;
input;
put _infile_;
putlog _infile_;
if eof then put ";quit;";
run;
%inc &fref1;
data &outds;
if &sqlrc or &syscc or &syserr then do;
REASON_CD=coalescec(symget('SYSERRORTEXT'),symget('SYSWARNINGTEXT'));
output;
end;
else stop;
run;
filename &fref1 clear;
%if %mf_nobs(&outds)>0 %then %do;
%if &abort=YES %then %do;
data _null_;
set &outds;
call symputx('REASON_CD',reason_cd,'l');
stop;
run;
%mp_abort(
mac=&sysmacroname,
msg=%str(Filter issues in &inref: %quote(&reason_cd))
)
%end;
%let syscc=1008;
%end;
%mend;

View File

@@ -133,7 +133,9 @@ run;
if notnull='yes' then notnul=' not null';
if notnull='no' and missing(label) then put ' ' name typ;
else if notnull='yes' and missing(label) then put ' ' name typ '[' notnul ']';
else if notnull='yes' and missing(label) then do;
put ' ' name typ '[' notnul ']';
end;
else if notnull='no' then put ' ' name typ '[' lab ']';
else put ' ' name typ '[' notnul ',' lab ']';
@@ -166,7 +168,7 @@ run;
call symputx('constcheck',1);
end;
if last then call symputx('constraints_used',cats(upcase(constraints_used)));
if last then call symput('constraints_used',cats(upcase(constraints_used)));
length curds const col $39;
curds="&curds";
@@ -176,7 +178,8 @@ run;
proc append base=&pkds data=&syslast;run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _data_(keep=curds const col);
set &idxinfo (where=(
libname="%scan(&curds,1,.)"
@@ -187,7 +190,7 @@ run;
file &outref mod;
by idxusage indxname;
name=upcase(name);
if &constcheck=1 then stop; /* in fact we only care about PKs so stop if we have */
if &constcheck=1 then stop; /* we only care about PKs so stop if we have */
if _n_=1 and &constcheck=0 then put / ' indexes {';
length cols $5000;
@@ -261,7 +264,11 @@ run;
line='Ref: "'!!"&curds"
!!cats('".(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')')
!!' - '
!!cats(quote(trim(curds)),'.(',"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))",')');
!!cats(quote(trim(curds))
,'.('
,"%mf_getquotedstr(&pkcols,dlm=%str(,),quote=%str( ))"
,')'
);
put line;
run;
@@ -282,7 +289,9 @@ run;
create table &pkds.5b as
select curds,count(*) as cnt
from &pkds.5a
where curds not in (select curds from &pkds.2 where cols="&pkcols") /* not a one to one match */
where curds not in (
select curds from &pkds.2 where cols="&pkcols"
) /* not a one to one match */
and curds ne "&curds" /* exclude self */
group by 1;
create table &pkds.6 as

View File

@@ -86,7 +86,9 @@ create table _data_ as
%global constraints_used;
data _null_;
length ctype $11 constraint_name_orig $256 constraints_used $5000;
set &colconst (where=(table_name="&curds" and constraint_type in ('PRIMARY','UNIQUE'))) end=last;
set &colconst(
where=(table_name="&curds" and constraint_type in ('PRIMARY','UNIQUE'))
) end=last;
file &fref mod;
by constraint_type constraint_name;
retain constraints_used;
@@ -161,10 +163,19 @@ run;
put ');';
run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_;
*length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(","))))));
set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod;
by idxusage indxname;
/* ds=cats(libname,'.',memname); */
@@ -228,10 +239,19 @@ run;
/* Extra step for data constraints */
%addConst()
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_;
*length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(","))))));
set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod;
by idxusage indxname;
*ds=cats(libname,'.',memname);
@@ -320,15 +340,24 @@ run;
put ');';
run;
/* Create Unique Indexes, but only if they were not already defined within the Constraints section. */
/* Create Unique Indexes, but only if they were not already defined within
the Constraints section. */
data _null_;
*length ds $128;
set &idxinfo (where=(memname="&curds" and unique='yes' and indxname not in (%sysfunc(tranwrd("&constraints_used",%str( ),%str(","))))));
set &idxinfo(
where=(
memname="&curds"
and unique='yes'
and indxname not in (
%sysfunc(tranwrd("&constraints_used",%str( ),%str(",")))
)
)
);
file &fref mod;
by idxusage indxname;
/* ds=cats(libname,'.',memname); */
if first.indxname then do;
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds (" ;
put 'CREATE UNIQUE INDEX "' indxname +(-1) '" ' "ON &schema..&curds(";
put ' "' name +(-1) '"' ;
end;
else put ' ,"' name +(-1) '"';

View File

@@ -8,10 +8,11 @@
- NAME Name of the base dataset column
- MAXLEN Maximum length of the data contained therein.
Character fields may be allocated very large widths (eg 32000) of which the maximum
value is likely to be much narrower. This macro was designed to enable a HTML
table to be appropriately sized however this could be used as part of a data
audit to ensure we aren't over-sizing our tables in relation to the data therein.
Character fields may be allocated very large widths (eg 32000) of which the
maximum value is likely to be much narrower. This macro was designed to
enable a HTML table to be appropriately sized however this could be used as
part of a data audit to ensure we aren't over-sizing our tables in relation to
the data therein.
Numeric fields are converted using the relevant format to determine the width.
Usage:

75
base/mp_hashdataset.sas Normal file
View File

@@ -0,0 +1,75 @@
/**
@file
@brief Returns a unique hash for a dataset
@details Ignores metadata attributes, used only to hash values. Compared
datasets must be in the same order.
%mp_hashdataset(sashelp.class,outds=myhash)
data _null_;
set work.myhash;
put hashkey=;
run;
![sas md5 hash dataset log results](https://i.imgur.com/MqF98vk.png)
<h4> SAS Macros </h4>
@li mf_getattrn.sas
@li mf_getuniquename.sas
@li mf_getvarlist.sas
@li mf_getvartype.sas
@param [in] libds dataset to hash
@param [out] outds= (work.mf_hashdataset) The output dataset to create. This
will contain one column (hashkey) with one observation (a hex32.
representation of the input hash)
|hashkey:$32.|
|---|
|28ABC74ABFC45F50794237BA5566E6CA|
@version 9.2
@author Allan Bowe
**/
%macro mp_hashdataset(
libds,
outds=
)/*/STORE SOURCE*/;
%if %mf_getattrn(&libds,NLOBS)=0 %then %do;
%put %str(WARN)ING: Dataset &libds is empty;, or is not a dataset;
%end;
%else %if %mf_getattrn(&libds,NLOBS)<0 %then %do;
%put %str(ERR)OR: Dataset &libds is not a dataset;
%end;
%else %do;
%local keyvar /* roll up the md5 */
prevkeyvar /* retain prev record md5 */
lastvar /* last var in input ds */
varlist var i;
/* avoid naming conflict for hash key vars */
%let keyvar=%mf_getuniquename();
%let prevkeyvar=%mf_getuniquename();
%let lastvar=%mf_getuniquename();
%let varlist=%mf_getvarlist(&libds);
data &outds(rename=(&keyvar=hashkey) keep=&keyvar);
length &prevkeyvar &keyvar $32;
retain &prevkeyvar;
set &libds end=&lastvar;
/* hash should include previous row */
&keyvar=put(md5(&prevkeyvar
/* loop every column, hashing every individual value */
%do i=1 %to %sysfunc(countw(&varlist));
%let var=%scan(&varlist,&i,%str( ));
%if %mf_getvartype(&libds,&var)=C %then %do;
!!put(md5(trim(&var)),$hex32.)
%end;
%else %do;
!!put(md5(trim(put(&var*1,binary64.))),$hex32.)
%end;
%end;
),$hex32.);
&prevkeyvar=&keyvar;
if &lastvar then output;
run;
%end;
%mend;

View File

@@ -4,8 +4,11 @@
@details PROC JSON is faster but will produce errs like the ones below if
special chars are encountered.
>An object or array close is not valid at this point in the JSON text.
>Date value out of range
> ERROR: Some code points did not transcode.
> An object or array close is not valid at this point in the JSON text.
> Date value out of range
If this happens, try running with ENGINE=DATASTEP.
@@ -14,7 +17,9 @@
filename tmp temp;
data class; set sashelp.class;run;
%mp_jsonout(OPEN,jref=tmp)
%mp_jsonout(OBJ,class,jref=tmp)
%mp_jsonout(CLOSE,jref=tmp)
data _null_;
infile tmp;
@@ -27,18 +32,18 @@
For more information see https://sasjs.io
@param action Valid values:
* OPEN - opens the JSON
* OBJ - sends a table with each row as an object
* ARR - sends a table with each row in an array
* CLOSE - closes the JSON
@li OPEN - opens the JSON
@li OBJ - sends a table with each row as an object
@li ARR - sends a table with each row in an array
@li CLOSE - closes the JSON
@param ds the dataset to send. Must be a work table.
@param jref= the fileref to which to send the JSON
@param dslabel= the name to give the table in the exported JSON
@param fmt= Whether to keep or strip formats from the table
@param engine= Which engine to use to send the JSON, options are:
* PROCJSON (default)
* DATASTEP
@param engine= Which engine to use to send the JSON, valid options are:
@li PROCJSON (default)
@li DATASTEP (more reliable when data has non standard characters)
@param dbg= DEPRECATED - was used to conditionally add PRETTY to
proc json but this can cause line truncation in large files.
@@ -83,7 +88,8 @@
%end;
data _null_;file &jref mod ;
put "["; call symputx('cols',0,'l');
proc sort data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
proc sort
data=sashelp.vcolumn(where=(libname='WORK' & memname="%upcase(&ds)"))
out=_data_;
by varnum;
@@ -122,7 +128,8 @@
%end;
%end;
run;
/* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */
/* write to temp loc to avoid _webout truncation
- https://support.sas.com/kb/49/325.html */
filename _sjs temp lrecl=131068 encoding='utf-8';
data _null_; file _sjs lrecl=131068 encoding='utf-8' mod;
set &tempds;
@@ -158,7 +165,7 @@
%end;
%else %if &action=CLOSE %then %do;
data _null_;file &jref encoding='utf-8';
data _null_;file &jref encoding='utf-8' mod;
put "}";
run;
%end;

98
base/mp_mdtablewrite.sas Normal file
View File

@@ -0,0 +1,98 @@
/**
@file
@brief Create a Markdown Table from a dataset
@details A markdown table is a simple table representation for use in
documents written in markdown format.
An online generator is available here:
https://www.tablesgenerator.com/markdown_tables
This structure is also used by the Macro Core library for documenting input/
output datasets, as well as the sasjs/cli tool for documenting inputs/outputs
for web services.
We take the standard definition one step further by embedding the informat
in the table header row, like so:
|var1:$|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Which resolves to:
|var1:$|var2:best.|var3:date9.|
|---|---|---|
|some text|42|01JAN1960|
|blah|1|31DEC1999|
Usage:
%mp_mdtablewrite(libds=sashelp.class,showlog=YES)
<h4> SAS Macros </h4>
@li mf_getvarlist.sas
@li mf_getvarformat.sas
@param [in] libds= the library / dataset to create or read from.
@param [out] fref= Fileref to contain the markdown. Default=mdtable.
@param [out] showlog= set to YES to show the markdown in the log. Default=NO.
@version 9.3
@author Allan Bowe
**/
%macro mp_mdtablewrite(
libds=,
fref=mdtable,
showlog=NO
)/*/STORE SOURCE*/;
/* check fileref is assigned */
%if %sysfunc(fileref(&fref)) > 0 %then %do;
filename &fref temp;
%end;
%local vars;
%let vars=%mf_getvarlist(&libds);
/* create the header row */
data _null_;
file &fref;
length line $32767;
put '|'
%local i var fmt;
%do i=1 %to %sysfunc(countw(&vars));
%let var=%scan(&vars,&i);
%let fmt=%mf_getvarformat(&libds,&var,force=1);
"&var:&fmt|"
%end;
;
put '|'
%do i=1 %to %sysfunc(countw(&vars));
"---|"
%end;
;
run;
/* write out the data */
data _null_;
file &fref mod dlm='|' lrecl=32767;
set &libds ;
length line $32767;
line=cats('|',%mf_getvarlist(&libds,dlm=%str(,'|',)),'|');
put line;
run;
%if %upcase(&showlog)=YES %then %do;
options ps=max;
data _null_;
infile &fref;
input;
putlog _infile_;
run;
%end;
%mend mp_mdtablewrite;

View File

@@ -79,7 +79,7 @@
%end;
%end;
%else %if &action=FETCH %then %do;
if &record > &prefix._key then putlog "Not enough records in &Prefix._hash yet";
if &record>&prefix._key then putlog "Not enough records in &Prefix._hash yet";
else &prefix._rc=&prefix._HASH.find(key: &prefix._KEY - &record);
if &prefix._rc then putlog &prefix._rc= " when fetching " &prefix._KEY=
"with record &record and " _n_=;

View File

@@ -1,7 +1,8 @@
/**
@file
@brief Reset an option to original value
@details Inspired by the SAS Jedi - https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options/
@details Inspired by the SAS Jedi -
https://blogs.sas.com/content/sastraining/2012/08/14/jedi-sas-tricks-reset-sas-system-options
Called as follows:
options obs=30;

View File

@@ -20,9 +20,10 @@
@param ds= the dataset to search (leave blank to search entire library)
@param string= the string value to search
@param numval= the numeric value to search (must be exact)
@param outloc= the directory in which to create the output datasets with matching
rows. Will default to a subfolder in the WORK library.
@param outobs= set to a positive integer to restrict the number of observations
@param outloc= the directory in which to create the output datasets with
matching rows. Will default to a subfolder in the WORK library.
@param outobs= set to a positive integer to restrict the number of
observations
@param filter_text= add a (valid) filter clause to further filter the results
<h4> SAS Macros </h4>
@@ -44,7 +45,8 @@
,filter_text=%str(1=1)
)/*/STORE SOURCE*/;
%local table_list table table_num table colnum col start_tm check_tm vars type coltype;
%local table_list table table_num table colnum col start_tm check_tm vars type
coltype;
%put process began at %sysfunc(datetime(),datetime19.);
%if &syscc ge 4 %then %do;
@@ -101,7 +103,8 @@ proc sql
%end;
%end;
);
%put Search query for &table took %sysevalf(%sysfunc(datetime())-&check_tm) seconds;
%put Search query for &table took
%sysevalf(%sysfunc(datetime())-&check_tm) seconds;
%if &sqlrc ne 0 %then %do;
%put %str(WAR)NING: SQLRC=&sqlrc when processing &table;
%return;

View File

@@ -1,7 +1,8 @@
/**
@file
@brief Capture session start / finish times and request details
@details For details, see http://www.rawsas.com/2015/09/logging-of-stored-process-server.html.
@details For details, see
https://rawsas.com/event-logging-of-stored-process-server-sessions.
Requires a base table in the following structure (name can be changed):
proc sql;

View File

@@ -6,7 +6,8 @@
Usage:
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
filename mc url
"https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
%mp_streamfile(contenttype=csv,inloc=/some/where.txt,outname=myfile.txt)
@@ -35,8 +36,20 @@
%let contentype=%upcase(&contenttype);
%local platform; %let platform=%mf_getplatform();
/**
* check engine type to avoid the below err message:
* > Function is only valid for filerefs using the CACHE access method.
*/
%local streamweb;
%let streamweb=0;
data _null_;
set sashelp.vextfl(where=(upcase(fileref)="_WEBOUT"));
if xengine='STREAM' then call symputx('streamweb',1,'l');
run;
%if &contentype=ZIP %then %do;
%if &platform=SASMETA %then %do;
%if &platform=SASMETA and &streamweb=1 %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/zip');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -50,7 +63,7 @@
%end;
%else %if &contentype=EXCEL %then %do;
/* suitable for XLS format */
%if &platform=SASMETA %then %do;
%if &platform=SASMETA and &streamweb=1 %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/vnd.ms-excel');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -63,20 +76,22 @@
%end;
%end;
%else %if &contentype=XLSX %then %do;
%if &platform=SASMETA %then %do;
%if &platform=SASMETA and &streamweb=1 %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
rc=stpsrv_header('Content-type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
run;
%end;
%else %if &platform=SASVIYA %then %do;
filename _webout filesrvc parenturi="&SYS_JES_JOB_URI" name='_webout.xls'
contenttype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
contenttype=
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
contentdisp="attachment; filename=&outname";
%end;
%end;
%else %if &contentype=TEXT %then %do;
%if &platform=SASMETA %then %do;
%if &platform=SASMETA and &streamweb=1 %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/text');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");
@@ -89,7 +104,7 @@
%end;
%end;
%else %if &contentype=CSV %then %do;
%if &platform=SASMETA %then %do;
%if &platform=SASMETA and &streamweb=1 %then %do;
data _null_;
rc=stpsrv_header('Content-type','application/csv');
rc=stpsrv_header('Content-disposition',"attachment; filename=&outname");

259
base/mp_testservice.sas Normal file
View File

@@ -0,0 +1,259 @@
/**
@file mp_testservice.sas
@brief Will execute a test against a SASjs web service on SAS 9 or Viya
@details Prepares the input files and retrieves the resulting datasets from
the response JSON.
%mp_testjob(
duration=60*5
)
Note - the _webout fileref should NOT be assigned prior to running this macro.
@param [in] program The _PROGRAM endpoint to test
@param [in] inputfiles=(0) A list of space seperated fileref:filename pairs as
follows:
inputfiles=inref:filename inref2:filename2
@param [in] inputparams=(0) A dataset containing name/value pairs in the
following format:
|name:$32|value:$1000|
|---|---|
|stpmacname|some value|
|mustbevalidname|can be anything, oops, %abort!!|
@param [in] debug= (log) Provide the _debug value
@param [in] viyaresult=(WEBOUT_JSON) The Viya result type to return. For
more info, see mv_getjobresult.sas
@param [out] outlib= (0) Output libref to contain the final tables. Set to
0 if the service output is not in JSON format.
@param [out] outref= (0) Output fileref to create, to contain the full _webout
response.
<h4> SAS Macros </h4>
@li mf_getplatform.sas
@li mf_getuniquefileref.sas
@li mf_getuniquename.sas
@li mp_abort.sas
@li mp_binarycopy.sas
@li mv_getjobresult.sas
@li mv_jobflow.sas
@version 9.4
@author Allan Bowe
**/
%macro mp_testservice(program,
inputfiles=0,
inputparams=0,
debug=log,
outlib=0,
outref=0,
viyaresult=WEBOUT_JSON
)/*/STORE SOURCE*/;
%local mdebug;
%if &debug ne 0 %then %do;
%let mdebug=1;
%put &sysmacroname entry vars:;
%put _local_;
%end;
%else %let mdebug=0;
/* sanitise inputparams */
%local pcnt;
%let pcnt=0;
%if &inputparams ne 0 %then %do;
data _null_;
set &inputparams;
if not nvalid(name,'v7') then putlog (_all_)(=);
else if name in (
'program','inputfiles','inputparams','debug','outlib','outref'
) then putlog (_all_)(=);
else do;
x+1;
call symputx(name,quote(cats(value)),'l');
call symputx('pval'!!left(x),name,'l');
call symputx('pcnt',x,'l');
end;
run;
%mp_abort(iftrue= (%mf_nobs(&inputparams) ne &pcnt)
,mac=&sysmacroname
,msg=%str(Invalid values in &inputparams)
)
%end;
%local fref1 webref;
%let fref1=%mf_getuniquefileref();
%let webref=%mf_getuniquefileref();
%local platform;
%let platform=%mf_getplatform();
%if &platform=SASMETA %then %do;
/* parse the input files */
%local webcount i var;
%if %quote(&inputfiles) ne 0 %then %do;
%let webcount=%sysfunc(countw(&inputfiles));
%put &=webcount;
%do i=1 %to &webcount;
%let var=%scan(&inputfiles,&i,%str( ));
%local webfref&i webname&i;
%let webref&i=%scan(&var,1,%str(:));
%let webname&i=%scan(&var,2,%str(:));
%put webref&i=&&webref&i;
%put webname&i=&&webname&i;
%end;
%end;
%else %let webcount=0;
proc stp program="&program";
inputparam _program="&program"
%do i=1 %to &webcount;
%if &webcount=1 %then %do;
_webin_fileref="&&webref&i"
_webin_name="&&webname&i"
%end;
%else %do;
_webin_fileref&i="&&webref&i"
_webin_name&i="&&webname&i"
%end;
%end;
_webin_file_count="&webcount"
_debug="&debug"
%do i=1 %to &pcnt;
/* resolve name only, proc stp fetches value */
&&pval&i=&&&&&&pval&i
%end;
;
%do i=1 %to &webcount;
inputfile &&webref&i;
%end;
outputfile _webout=&webref;
run;
data _null_;
infile &webref;
file &fref1;
input;
length line $10000;
if index(_infile_,'>>weboutBEGIN<<') then do;
line=tranwrd(_infile_,'>>weboutBEGIN<<','');
put line;
end;
else if index(_infile_,'>>weboutEND<<') then do;
line=tranwrd(_infile_,'>>weboutEND<<','');
put line;
stop;
end;
else put _infile_;
run;
data _null_;
infile &fref1;
input;
put _infile_;
run;
%if &outlib ne 0 %then %do;
libname &outlib json (&fref1);
%end;
%if &outref ne 0 %then %do;
filename &outref temp;
%mp_binarycopy(inref=&webref,outref=&outref)
%end;
%end;
%else %if &platform=SASVIYA %then %do;
/* prepare inputparams */
%local ds1;
%let ds1=%mf_getuniquename();
%if "&inputparams" ne "0" %then %do;
proc transpose data=&inputparams out=&ds1;
id name;
var value;
run;
%end;
%else %do;
data &ds1;run;
%end;
/* parse the input files - convert to sasjs params */
%local webcount i var sasjs_tables;
%if %quote(&inputfiles) ne 0 %then %do;
%let webcount=%sysfunc(countw(&inputfiles));
%put &=webcount;
%do i=1 %to &webcount;
%let var=%scan(&inputfiles,&i,%str( ));
%local webfref&i webname&i sasjs&i.data;
%let webref&i=%scan(&var,1,%str(:));
%let webname&i=%scan(&var,2,%str(:));
%put webref&i=&&webref&i;
%put webname&i=&&webname&i;
%let sasjs_tables=&sasjs_tables &&webname&i;
data _null_;
infile &&webref&i lrecl=32767;
input;
if _n_=1 then call symputx("sasjs&i.data",_infile_);
else call symputx(
"sasjs&i.data",cats(symget("sasjs&i.data"),'0D0A'x,_infile_)
);
putlog "&sysmacroname infile: " _infile_;
run;
data &ds1;
set &ds1;
length sasjs&i.data $32767 sasjs_tables $1000;
sasjs&i.data=symget("sasjs&i.data");
sasjs_tables=symget("sasjs_tables");
run;
%end;
%end;
%else %let webcount=0;
data &ds1;
retain _program "&program";
set &ds1;
putlog "&sysmacroname inputparams:";
putlog (_all_)(=);
run;
%mv_jobflow(inds=&ds1
,maxconcurrency=1
,outds=work.results
,outref=&fref1
,mdebug=&mdebug
)
/* show the log */
data _null_;
infile &fref1;
input;
putlog _infile_;
run;
/* get the uri to fetch results */
data _null_;
set work.results;
call symputx('uri',uri);
putlog "&sysmacroname: fetching results for " uri;
run;
/* fetch results from webout.json */
%mv_getjobresult(uri=&uri,
result=&viyaresult,
outref=&outref,
outlib=&outlib,
mdebug=&mdebug
)
%end;
%else %do;
%put %str(ERR)OR: Unrecognised platform: &platform;
%end;
%if &mdebug=0 %then %do;
filename &webref clear;
%end;
%else %do;
%put &sysmacroname exit vars:;
%put _local_;
%end;
%mend mp_testservice;

View File

@@ -8,8 +8,11 @@
Credits:
* Roger Deangelis, https://communities.sas.com/t5/SAS-Programming/listing-all-files-within-a-directory-and-subdirectories/m-p/332616/highlight/true#M74887
* Tom, https://communities.sas.com/t5/SAS-Programming/listing-all-files-of-all-types-from-all-subdirectories/m-p/334113/highlight/true#M75419
Roger Deangelis:
https://communities.sas.com/t5/SAS-Programming/listing-all-files-within-a-directory-and-subdirectories/m-p/332616/highlight/true#M74887
Tom:
https://communities.sas.com/t5/SAS-Programming/listing-all-files-of-all-types-from-all-subdirectories/m-p/334113/highlight/true#M75419
@param dir= Directory to be scanned (default=/tmp)

66
base/mp_validatecol.sas Normal file
View File

@@ -0,0 +1,66 @@
/**
@file
@brief Used to validate variables in a dataset
@details Useful when sanitising inputs, to ensure that they arrive with a
certain pattern.
Usage:
data test;
infile datalines4 dsd;
input;
libds=_infile_;
%mp_validatecol(libds,LIBDS,is_libds)
datalines4;
some.libname
!lib.blah
%abort
definite.ok
not.ok!
nineletrs._
;;;;
run;
@param [in] incol The column to be validated
@param [in] rule The rule to apply. Current rules:
@li ISNUM - checks if the variable is numeric
@li LIBDS - matches LIBREF.DATASET format
@param [out] outcol The variable to create, with the results of the match
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@version 9.3
**/
%macro mp_validatecol(incol,rule,outcol);
/* tempcol is given a unique name with every invocation */
%local tempcol;
%let tempcol=%mf_getuniquename();
%if &rule=ISNUM %then %do;
/*
credit SØREN LASSEN
https://sasmacro.blogspot.com/2009/06/welcome-isnum-macro.html
*/
&tempcol=input(&incol,?? best32.);
if missing(&tempcol) then &outcol=0;
else &outcol=1;
drop &tempcol;
%end;
%else %if &rule=LIBDS %then %do;
/* match libref.dataset */
if _n_=1 then do;
retain &tempcol;
&tempcol=prxparse('/^[_a-z]\w{0,7}\.[_a-z]\w{0,31}$/i');
if missing(&tempcol) then do;
putlog "%str(ERR)OR: Invalid expression for LIBDS";
stop;
end;
drop &tempcol;
end;
if prxmatch(&tempcol, trim(&incol)) then &outcol=1;
else &outcol=0;
%end;
%mend mp_validatecol;

View File

@@ -70,7 +70,7 @@ run;
%end;
%if &syscc ge 4 %then %do;
%put WARNING: SYSCC=&syscc, exiting &sysmacroname;
%put %str(WARN)ING: SYSCC=&syscc, exiting &sysmacroname;
%return;
%end;

View File

@@ -117,7 +117,8 @@ run;
%end;
%else %if &engine=REMOTE %then %do;
data x;
length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName Delimiter $256 properties $2048;
length rcCon rcProp rc k 3 uriCon uriProp PropertyValue PropertyName
Delimiter $256 properties $2048;
retain properties;
rcCon = metadata_getnasn("&liburi", "LibraryConnection", 1, uriCon);
@@ -129,7 +130,8 @@ run;
rc = metadata_getattr(uriProp , "DefaultValue",PropertyValue);
rc = metadata_getattr(uriProp , "PropertyName",PropertyName);
rc = metadata_getattr(uriProp , "Delimiter",Delimiter);
properties = trim(properties) !! " " !! trim(PropertyName) !! trim(Delimiter) !! trim(PropertyValue);
properties = trim(properties) !! " " !! trim(PropertyName)
!! trim(Delimiter) !! trim(PropertyValue);
output;
k+1;
rcProp = metadata_getnasn(uriCon, "Properties", k, uriProp);
@@ -170,7 +172,8 @@ run;
else if value='Connection.OLE.Property.PROVIDER.Name.xmlKey.txt' then do;
rc4=metadata_getattr(conprop_uri,'DefaultValue',provider);
end;
else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then do;
else if value='Connection.OLE.Property.PROPERTIES.Name.xmlKey.txt' then
do;
rc5=metadata_getattr(conprop_uri,'DefaultValue',properties);
end;
end;
@@ -329,7 +332,7 @@ run;
run;
%if %length(&open_passthrough)>0 %then %do;
%put WARNING: Passthrough option for postgres not yet supported;
%put %str(WARN)ING: Passthrough option for postgres not yet supported;
%return;
%end;
%else %do;
@@ -357,7 +360,8 @@ run;
call symputx('authdomain',authdomain,'l');
/* path */
rc=metadata_getprop(assocuri1,'Connection.Oracle.Property.PATH.Name.xmlKey.txt',path);
rc=metadata_getprop(assocuri1,
'Connection.Oracle.Property.PATH.Name.xmlKey.txt',path);
call symputx('path',path,'l');
/* schema */
@@ -366,14 +370,16 @@ run;
call symputx('schema',schema,'l');
run;
%put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;
%put NOTE- libname &libref ORACLE path=&path schema=&schema;
%put NOTE- authdomain=&authdomain;
%put NOTE-;
libname &libref ORACLE path=&path schema=&schema authdomain=&authdomain;
%end;
%else %if &engine=SQLSVR %then %do;
%put NOTE: Obtaining &engine library details;
data _null;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid passwd $256;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid
passwd $256;
call missing (of _all_);
rc=metadata_getnasn("&liburi",'DefaultLogin',1,assocuri1);
@@ -384,7 +390,8 @@ run;
/* path */
rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2);
rc=metadata_getprop(assocuri2,'Connection.SQL.Property.Datasrc.Name.xmlKey.txt',path);
rc=metadata_getprop(assocuri2,
'Connection.SQL.Property.Datasrc.Name.xmlKey.txt',path);
call symputx('path',path,'l');
/* schema */
@@ -394,15 +401,17 @@ run;
run;
%put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="XXX";
%put NOTE- libname &libref SQLSVR datasrc=&path schema=&schema ;
%put NOTE- user="&user" pass="XXX";
%put NOTE-;
libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass" ;
libname &libref SQLSVR datasrc=&path schema=&schema user="&user" pass="&pass";
%end;
%else %if &engine=TERADATA %then %do;
%put NOTE: Obtaining &engine library details;
data _null;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid passwd $256;
length assocuri1 assocuri2 assocuri3 authdomain path schema userid
passwd $256;
call missing (of _all_);
/* get auth domain */
@@ -421,7 +430,8 @@ run;
/* path */
rc=metadata_getnasn("&liburi",'LibraryConnection',1,assocuri2);
rc=metadata_getprop(assocuri2,'Connection.Teradata.Property.SERVER.Name.xmlKey.txt',path);
rc=metadata_getprop(assocuri2,
'Connection.Teradata.Property.SERVER.Name.xmlKey.txt',path);
call symputx('path',path,'l');
/* schema */
@@ -431,7 +441,8 @@ run;
run;
%put NOTE: Executing the following:/; %put NOTE-;
%put NOTE- libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain;
%put NOTE- libname &libref TERADATA server=&path schema=&schema ;
%put NOTe- authdomain=&authdomain;
%put NOTE-;
libname &libref TERADATA server=&path schema=&schema authdomain=&authdomain;
@@ -444,8 +455,8 @@ run;
%return;
%end;
%else %do;
%put WARNING: Engine &engine is currently unsupported;
%put WARNING- Please contact your support team.;
%put %str(WARN)ING: Engine &engine is currently unsupported;
%put %str(WARN)ING- Please contact your support team.;
%return;
%end;

View File

@@ -14,7 +14,8 @@
@li mp_abort.sas
@param libref the libref (not name) of the metadata library
@param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will silently return
@param mAbort= If not assigned, HARD will call %mp_abort(), SOFT will
silently return
@returns libname statement

View File

@@ -13,7 +13,9 @@
,params= name1=value1&#x0a;name2=value2&#x0a;emptyvalue=
)
@warning application components do not get deleted when removing the container folder! be sure you have the administrative priviliges to remove this kind of metadata from the SMC plugin (or be ready to do to so programmatically).
@warning application components do not get deleted when removing the container
folder! be sure you have the administrative priviliges to remove this kind of
metadata from the SMC plugin (or be ready to do to so programmatically).
<h4> SAS Macros </h4>
@li mp_abort.sas

View File

@@ -55,7 +55,7 @@ data _null_;
* must have a starting slash ;
if ( substr(folderPath,1,1) ne '/' ) then do;
put "%str(ERR)OR: &sysmacroname PATH parameter value must have starting slash";
put "%str(ERR)OR: &sysmacroname PATH param value must have starting slash";
stop;
end;
@@ -123,9 +123,10 @@ run;
%local inmeta;
%put creating: &path;
%let inmeta=<AddMetadata><Reposid>$METAREPOSITORY</Reposid><Metadata>
<Tree Name='&child' PublicType='Folder' TreeType='BIP Folder' UsageVersion='1000000'>
<ParentTree><Tree ObjRef='&parentFolderObjId'/></ParentTree></Tree></Metadata>
<NS>SAS</NS><Flags>268435456</Flags></AddMetadata>;
<Tree Name='&child' PublicType='Folder' TreeType='BIP Folder'
UsageVersion='1000000'><ParentTree><Tree ObjRef='&parentFolderObjId'/>
</ParentTree></Tree></Metadata><NS>SAS</NS><Flags>268435456</Flags>
</AddMetadata>;
proc metadata in="&inmeta" out=__newdir verbose;
run ;

View File

@@ -88,7 +88,7 @@ data _null_;
putlog (_all_)(=);
run;
%if &checktype = SASLibrary %then %do;
%put WARNING: Library (&liburi) already exists with libname (&libname) ;
%put %str(WARN)ING: Library (&liburi) already exists with libname (&libname);
%return;
%end;
@@ -103,7 +103,7 @@ data _null_;
putlog (_all_)(=);
run;
%if &checktype = SASLibrary %then %do;
%put WARNING: Library (&liburi) already exists with libref (&libref) ;
%put %str(WARN)ING: Library (&liburi) already exists with libref (&libref) ;
%return;
%end;
@@ -123,7 +123,7 @@ data _null_;
call symputx('treeuri',uri,'l');
run;
%if &foldertype ne Tree %then %do;
%put WARNING: Tree &tree does not exist!;
%put %str(WARN)ING: Tree &tree does not exist!;
%return;
%end;
@@ -166,7 +166,7 @@ filename &frefout temp;
putlog (_all_)(=);
run;
%if &checktype ne Prototype %then %do;
%put %str(ERR)OR: Prototype (Library.SAS.Prototype.Name.xmlKey.txt) not found!;
%put %str(ERR)OR: Prototype Library.SAS.Prototype.Name.xmlKey.txt not found;
%return;
%end;
@@ -231,7 +231,7 @@ filename &frefout temp;
* check SAS version
*/
%if %sysevalf(&sysver lt 9.3) %then %do;
%put WARNING: Version 9.3 or later required;
%put %str(WARN)ING: Version 9.3 or later required;
%return;
%end;

View File

@@ -118,7 +118,7 @@ data _null_;
call symputx('treeuri',uri,'l');
run;
%if &foldertype ne Tree %then %do;
%put WARNING: Tree &tree does not exist!;
%put %str(WARN)ING: Tree &tree does not exist!;
%return;
%end;
@@ -133,7 +133,7 @@ data _null_;
call symputx('stpuri',uri,'l');
run;
%if &cmtype = ClassifierMap %then %do;
%put WARNING: Stored Process &stpname already exists in &tree!;
%put %str(WARN)ING: Stored Process &stpname already exists in &tree!;
%return;
%end;
@@ -141,14 +141,14 @@ run;
* Check that the physical file exists
*/
%if %sysfunc(fileexist(&directory/&filename)) ne 1 %then %do;
%put WARNING: FILE *&directory/&filename* NOT FOUND!;
%put %str(WARN)ING: FILE *&directory/&filename* NOT FOUND!;
%return;
%end;
%if &stptype=1 %then %do;
/* type 1 STP - where code is stored on filesystem */
%if %sysevalf(&sysver lt 9.2) %then %do;
%put WARNING: Version 9.2 or later required;
%put %str(WARN)ING: Version 9.2 or later required;
%return;
%end;
@@ -162,7 +162,7 @@ run;
%if &checkdirtype ne Directory %then %do;
%mm_getdirectories(path=&directory,outds=&outds ,mDebug=&mDebug)
%if %mf_nobs(&outds)=0 or %sysfunc(exist(&outds))=0 %then %do;
%put WARNING: The directory object does not exist for &directory;
%put %str(WARN)ING: The directory object does not exist for &directory;
%return;
%end;
%end;
@@ -180,12 +180,12 @@ run;
length id $20 type $256;
__rc=metadata_resolve("&treeuri",type,id);
if type ne 'Tree' then do;
putlog "WARNING: Invalid tree URI: &treeuri";
putlog "%str(WARN)ING: Invalid tree URI: &treeuri";
stopme=1;
end;
__rc=metadata_resolve(directoryuri,type,id);
if type ne 'Directory' then do;
putlog 'WARNING: Invalid directory URI: ' directoryuri;
putlog "%str(WARN)ING: Invalid directory URI: " directoryuri;
stopme=1;
end;
@@ -194,7 +194,7 @@ run;
if type ne 'LogicalServer' then do;
__rc=metadata_getnobj("omsobj:LogicalServer?@Name='&server'",1,serveruri);
if serveruri='' then do;
putlog "WARNING: Invalid server: &server";
putlog "%str(WARN)ING: Invalid server: &server";
stopme=1;
end;
end;
@@ -210,13 +210,14 @@ run;
rc3=METADATA_SETATTR(prompturi, 'GroupType','2');
rc4=METADATA_SETATTR(prompturi, 'Name','Parameters');
rc5=METADATA_SETATTR(prompturi, 'PublicType','Embedded:PromptGroup');
GroupInfo="<PromptGroup promptId='PromptGroup_%sysfunc(datetime())_&sysprocessid'"
GroupInfo=
"<PromptGroup promptId='PromptGroup_%sysfunc(datetime())_&sysprocessid'"
!!" version='1.0'><Label><Text xml:lang='en-GB'>Parameters</Text>"
!!"</Label></PromptGroup>";
rc6 = METADATA_SETATTR(prompturi, 'GroupInfo',groupinfo);
if sum(of rc1-rc6) ne 0 then do;
putlog 'WARNING: Issue creating prompt.';
putlog "%str(WARN)ING: Issue creating prompt.";
if prompturi ne . then do;
putlog ' Removing orphan: ' prompturi;
rc = METADATA_DELOBJ(prompturi);
@@ -231,7 +232,7 @@ run;
rc9=METADATA_SETATTR(fileuri, 'IsARelativeName','1');
rc10=METADATA_SETASSN(fileuri, 'Directories','MODIFY',directoryuri);
if sum(of rc7-rc10) ne 0 then do;
putlog 'WARNING: Issue creating file.';
putlog "%str(WARN)ING: Issue creating file.";
if fileuri ne . then do;
putlog ' Removing orphans:' prompturi fileuri;
rc = METADATA_DELOBJ(prompturi);
@@ -250,7 +251,7 @@ run;
!!"<OutputParameters/></StoredProcess>";
rc14= METADATA_SETATTR(texturi, 'StoredText',storedtext);
if sum(of rc11-rc14) ne 0 then do;
putlog 'WARNING: Issue creating TextStore.';
putlog "%str(WARN)ING: Issue creating TextStore.";
if texturi ne . then do;
putlog ' Removing orphans: ' prompturi fileuri texturi;
rc = METADATA_DELOBJ(prompturi);
@@ -298,7 +299,7 @@ run;
%else %if &stptype=2 %then %do;
/* type 2 stp - code is stored in metadata */
%if %sysevalf(&sysver lt 9.3) %then %do;
%put WARNING: SAS version 9.3 or later required to create type2 STPs;
%put %str(WARN)ING: SAS version 9.3 or later required to create type2 STPs;
%return;
%end;
/* check we have the correct ServerContext */
@@ -310,7 +311,7 @@ run;
call symputx('serveruri',serveruri);
run;
%if &serveruri=NOTFOUND %then %do;
%put WARNING: ServerContext *&server* not found!;
%put %str(WARN)ING: ServerContext *&server* not found!;
%return;
%end;
@@ -375,15 +376,13 @@ run;
*/
%mm_updatestpsourcecode(stp=&tree/&stpname
,stpcode="&directory/&filename"
,frefin=&frefin.
,frefout=&frefout.
,mdebug=&mdebug
,minify=&minify)
%end;
%else %do;
%put WARNING: STPTYPE=*&stptype* not recognised!;
%put %str(WARN)ING: STPTYPE=*&stptype* not recognised!;
%end;
%mend;

View File

@@ -22,7 +22,7 @@ Usage:
%webout(OBJ,example2) * Object format, easier to work with ;
%webout(CLOSE)
;;;;
%mm_createwebservice(path=/Public/app/common,name=appInit,code=ft15f001,replace=YES)
%mm_createwebservice(path=/Public/app/common,name=appInit)
<h4> SAS Macros </h4>
@li mm_createstp.sas
@@ -119,7 +119,8 @@ data _null_;
put ' %end; ';
put ' data _null_;file &jref mod ; ';
put ' put "["; call symputx(''cols'',0,''l''); ';
put ' proc sort data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' proc sort ';
put ' data=sashelp.vcolumn(where=(libname=''WORK'' & memname="%upcase(&ds)")) ';
put ' out=_data_; ';
put ' by varnum; ';
put ' ';
@@ -158,7 +159,8 @@ data _null_;
put ' %end; ';
put ' %end; ';
put ' run; ';
put ' /* write to temp loc to avoid _webout truncation - https://support.sas.com/kb/49/325.html */ ';
put ' /* write to temp loc to avoid _webout truncation ';
put ' - https://support.sas.com/kb/49/325.html */ ';
put ' filename _sjs temp lrecl=131068 encoding=''utf-8''; ';
put ' data _null_; file _sjs lrecl=131068 encoding=''utf-8'' mod; ';
put ' set &tempds; ';
@@ -194,7 +196,7 @@ data _null_;
put '%end; ';
put ' ';
put '%else %if &action=CLOSE %then %do; ';
put ' data _null_;file &jref encoding=''utf-8''; ';
put ' data _null_;file &jref encoding=''utf-8'' mod; ';
put ' put "}"; ';
put ' run; ';
put '%end; ';
@@ -235,8 +237,16 @@ data _null_;
put '%else %if &action=OPEN %then %do; ';
put ' /* fix encoding */ ';
put ' OPTIONS NOBOMFILE; ';
put ' ';
put ' /** ';
put ' * check engine type to avoid the below err message: ';
put ' * > Function is only valid for filerefs using the CACHE access method. ';
put ' */ ';
put ' data _null_; ';
put ' rc = stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' set sashelp.vextfl(where=(fileref="_WEBOUT")); ';
put ' if xengine=''STREAM'' then do; ';
put ' rc=stpsrv_header(''Content-type'',"text/html; encoding=utf-8"); ';
put ' end; ';
put ' run; ';
put ' ';
put ' /* setup json */ ';
@@ -250,16 +260,9 @@ data _null_;
put '%end; ';
put ' ';
put '%else %if &action=ARR or &action=OBJ %then %do; ';
put ' %if &sysver=9.4 %then %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=PROCJSON,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put ' %else %do; ';
put ' %mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt ';
put ' ,engine=DATASTEP,dbg=%str(&_debug) ';
put ' ) ';
put ' %end; ';
put '%end; ';
put '%else %if &action=CLOSE %then %do; ';
put ' %if %str(&_debug) ge 131 %then %do; ';
@@ -275,14 +278,14 @@ data _null_;
put ' i+1; ';
put ' call symputx(''wt''!!left(i),name,''l''); ';
put ' call symputx(''wtcnt'',i,''l''); ';
put ' data _null_; file &fref encoding=''utf-8''; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' put ",""WORK"":{"; ';
put ' %do i=1 %to &wtcnt; ';
put ' %let wt=&&wt&i; ';
put ' proc contents noprint data=&wt ';
put ' out=_data_ (keep=name type length format:); ';
put ' run;%let tempds=%scan(&syslast,2,.); ';
put ' data _null_; file &fref encoding=''utf-8''; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' dsid=open("WORK.&wt",''is''); ';
put ' nlobs=attrn(dsid,''NLOBS''); ';
put ' nvars=attrn(dsid,''NVARS''); ';
@@ -293,10 +296,10 @@ data _null_;
put ' put '',"nvars":'' nvars; ';
put ' %mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP) ';
put ' %mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP) ';
put ' data _null_; file &fref encoding=''utf-8''; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' put "}"; ';
put ' %end; ';
put ' data _null_; file &fref encoding=''utf-8''; ';
put ' data _null_; file &fref mod encoding=''utf-8''; ';
put ' put "}"; ';
put ' run; ';
put ' %end; ';

View File

@@ -32,7 +32,7 @@ data _null_;
call symputx('stpuri',uri,'l');
run;
%if &type ne Document %then %do;
%put WARNING: No Document found at &target;
%put %str(WARN)ING: No Document found at &target;
%return;
%end;

92
meta/mm_deletelibrary.sas Normal file
View File

@@ -0,0 +1,92 @@
/**
@file
@brief Deletes a library by Name
@details Used to delete a library.
Usage:
%* create a library in the home directory ;
%mm_createlibrary(
libname=My Temp Library,
libref=XXTEMPXX,
tree=/User Folders/&sysuserid,
directory=%sysfunc(pathname(work))
)
%* delete the library ;
%mm_deletelibrary(name=My Temp Library)
After running the above, the following will be shown in the log:
![](https://i.imgur.com/Y4Tog24.png)
@param [in] name= the name (not libref) of the library to be deleted
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@li mp_abort.sas
@version 9.4
@author Allan Bowe
**/
%macro mm_deletelibrary(
name=
)/*/STORE SOURCE*/;
/**
* Check if library exists and get uri
*/
data _null_;
length type uri $256;
rc=metadata_resolve("omsobj:SASLibrary?@Name='&name'",type,uri);
call symputx('checktype',type,'l');
call symputx('liburi',uri,'l');
putlog (_all_)(=);
run;
%if &checktype ne SASLibrary %then %do;
%put &sysmacroname: Library (&name) was not found, and so will not be deleted;
%return;
%end;
%local fname1 fname2;
%let fname1=%mf_getuniquefileref();
%let fname2=%mf_getuniquefileref();
filename &fname1 temp lrecl=10000;
filename &fname2 temp lrecl=10000;
data _null_ ;
file &fname1 ;
put "<DeleteMetadata><Metadata><SASLibrary Id='&liburi'/>";
put "</Metadata><NS>SAS</NS><Flags>268436480</Flags><Options/>";
put "</DeleteMetadata>";
run ;
proc metadata in=&fname1 out=&fname2 verbose;run;
/* list the result */
data _null_;infile &fname2; input; list; run;
filename &fname1 clear;
filename &fname2 clear;
/**
* Check deletion
*/
%local isgone;
data _null_;
length type uri $256;
rc=metadata_resolve("omsobj:SASLibrary?@Id='&liburi'",type,uri);
call symputx('isgone',type,'l');
run;
%mp_abort(iftrue=(&isgone = SASLibrary)
,mac=&sysmacroname
,msg=%str(Library (&name) NOT deleted)
)
%put &sysmacroname: Library &name (&liburi) was successfully deleted;
%mend;

View File

@@ -21,8 +21,8 @@
)/*/STORE SOURCE*/;
%if %length(&outds)>30 %then %do;
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which therefore
needs to be 30 characters or less;
%put %str(ERR)OR: Temp tables are created with the &outds prefix, which
therefore needs to be 30 characters or less;
%return;
%end;
%if %index(&outds,'.')>0 %then %do;

View File

@@ -39,8 +39,10 @@ data &outds (keep=directoryuri name directoryname directorydesc );
do while
(metadata_getnobj("omsobj:Directory?@Id contains '.'",__i,directoryuri)>0);
%end; %else %do;
do while
(metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)>0);
do while(
metadata_getnobj("omsobj:Directory?@DirectoryName='&path'",__i,directoryuri)
>0
);
%end;
__rc1=metadata_getattr(directoryuri, "Name", name);
__rc2=metadata_getattr(directoryuri, "DirectoryName", directoryname);

View File

@@ -125,7 +125,7 @@ data _null_;
when ('&#x0d;') rec='0D'x;
when ('&#36;' ) rec='$' ;
when ('&#x09;') rec='09'x;
otherwise putlog "WARNING: missing value for " entity=;
otherwise putlog "%str(WARN)ING: missing value for " entity=;
end;
rc =fput(fileid, substr(rec,1,1));
rc =fwrite(fileid);

View File

@@ -11,7 +11,8 @@
%mm_getfoldermembers(root=/User Folders/&sysuserid, outds=usercontent)
@param [in] root= the parent folder under which to return all contents
@param [out] outds= the dataset to create that contains the list of directories
@param [out] outds= the dataset to create that contains the list of
directories
@param [in] mDebug= set to 1 to show debug messages in the log
<h4> Data Outputs </h4>

View File

@@ -13,7 +13,8 @@
options notes source;
@param [in] root= the parent folder under which to return all contents
@param [out] outds= the dataset to create that contains the list of directories
@param [out] outds= the dataset to create that contains the list of
directories
@param [in] mDebug= set to 1 to show debug messages in the log
<h4> SAS Macros </h4>

View File

@@ -11,7 +11,8 @@
@param [in] user= the metadata user to return groups for. Leave blank for all
groups.
@param [in] repo= the metadata repository that contains the user/group information
@param [in] repo= the metadata repository that contains the user/group
information
@param [in] mDebug= set to 1 to show debug messages in the log
@param [out] outds= the dataset to create that contains the list of groups
@@ -39,7 +40,8 @@
%&mD.put Executing mm_getGroups.sas;
%&mD.put _local_;
/* on some sites, user / group info is in a different metadata repo to the default */
/* on some sites, user / group info is in a different metadata repo to the
default */
%if &oldrepo ne &repo %then %do;
options metarepository=&repo;
%end;

129
meta/mm_getlibmetadiffs.sas Normal file
View File

@@ -0,0 +1,129 @@
/**
@file
@brief Compares the metadata of a library with the physical tables
@details Creates a series of output tables that show the differences between
metadata and physical tables.
Each output can be created with an optional prefix.
Credit - Paul Homes
https://platformadmin.com/blogs/paul/2012/11/sas-proc-metalib-ods-output
Usage:
%* create (and assign) a library for testing purposes ;
%mm_createlibrary(
libname=My Temp Library,
libref=XXTEMPXX,
tree=/User Folders/&sysuserid,
directory=%sysfunc(pathname(work))
)
%* create some tables;
data work.table1 table2 table3;
a=1;b='two';c=3;
run;
%* register the tables;
proc metalib;
omr=(library="My Temp Library");
report(type=detail);
update_rule (delete);
run;
%* modify the tables;
proc sql;
drop table table3;
alter table table2 drop c;
alter table table2 add d num;
%* run the macro;
%mm_getlibmetadiffs(libname=My Temp Library)
%* delete the library ;
%mm_deletelibrary(name=My Temp Library)
The program will create four output tables, with the following structure (and
example data):
#### &prefix.added
|name:$32.|metaID:$17.|SAStabName:$32.|
|---|---|---|
|||DATA1|
#### &prefix.deleted
|name:$32.|metaID:$17.|SAStabName:$32.|
|---|---|---|
|TABLE3|A5XLSNXI.BK0001HO|TABLE3|
#### &prefix.updated
|tabName:$32.|tabMetaID:$17.|SAStabName:$32.|metaName:$32.|metaID:$17.|sasname:$32.|metaType:$16.|change:$64.|
|---|---|---|---|---|---|---|---|
|TABLE2|A5XLSNXI.BK0001HN|TABLE2|c|A5XLSNXI.BM000MA9|c|Column|Deleted|
||||d||d|Column|Added|
#### &prefix.meta
|Label1:$28.|cValue1:$1.|nValue1:D12.3|
|---|---|---|
|Total tables analyzed|4|4|
|Tables to be Updated|1|1|
|Tables to be Deleted|1|1|
|Tables to be Added|1|1|
|Tables matching data source|1|1|
|Tables not processed|0|0|
If you are interested in more functionality like this (checking the health of
SAS metadata and your SAS 9 environment) then do contact [Allan Bowe](
https://www.linkedin.com/in/allanbowe) for details of our SAS 9 Health Check
service.
Our system scan will perform hundreds of checks to identify common issues,
such as dangling metadata, embedded passwords, security issues and more.
@param [in] libname= the metadata name of the library to be compared
@param [out] outlib= The output library in which to store the output tables.
Default=WORK.
@param [out] prefix The prefix for the four tables created. Default=metadiff.
@version 9.3
@author Allan Bowe
**/
%macro mm_getlibmetadiffs(
libname= ,
prefix=metadiff,
outlib=work
)/*/STORE SOURCE*/;
/* create tempds */
data;run;
%local tempds;
%let tempds=&syslast;
/* save options */
proc optsave out=&tempds;
run;
options VALIDVARNAME=ANY VALIDMEMNAME=EXTEND;
ods output
factoid1=&outlib..&prefix.meta
updtab=&outlib..&prefix.updated
addtab=&outlib..&prefix.added
deltab=&outlib..&prefix.deleted
;
proc metalib;
omr=(library="&libname");
noexec;
report(type=detail);
update_rule (delete);
run;
ods output close;
/* restore options */
proc optload data=&tempds;
run;
%mend mm_getlibmetadiffs;

View File

@@ -46,7 +46,8 @@ filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/&type</TABLE-PATH>";
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/&type";
put "</TABLE-PATH>";
put '<COLUMN name="id">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/&type/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";

View File

@@ -1,7 +1,8 @@
/**
@file mm_getpublictypes.sas
@brief Creates a dataset with all deployable public types
@details More info: https://support.sas.com/documentation/cdl/en/bisag/65422/HTML/default/viewer.htm#n1nkrdzsq5iunln18bk2236istkb.htm
@details More info:
https://support.sas.com/documentation/cdl/en/bisag/65422/HTML/default/viewer.htm#n1nkrdzsq5iunln18bk2236istkb.htm
Usage:

View File

@@ -44,61 +44,76 @@ filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASRepos"><TABLE name="SASRepos">';
put "<TABLE-PATH syntax='XPath'>/GetRepositories/Repositories/Repository</TABLE-PATH>";
put "<TABLE-PATH syntax='XPath'>/GetRepositories/Repositories/Repository";
put "</TABLE-PATH>";
put '<COLUMN name="id">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Id</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Id";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="name">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Name</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Name";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="desc">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Desc</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Desc";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="DefaultNS">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@DefaultNS</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@DefaultNS</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>200</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="RepositoryType">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@RepositoryType</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@RepositoryType</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>20</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="RepositoryFormat">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@RepositoryFormat</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@RepositoryFormat</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>10</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="Access">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Access</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@Access</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="CurrentAccess">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@CurrentAccess</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@CurrentAccess</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="PauseState">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@PauseState</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@PauseState</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>16</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="Path">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Path</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Path";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="Engine">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Engine</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Engine";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>8</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="Options">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Options</PATH>";
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@Options";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="MetadataCreated">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@MetadataCreated</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@MetadataCreated</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>";
put '</COLUMN>';
put '<COLUMN name="MetadataUpdated">';
put "<PATH syntax='XPath'>/GetRepositories/Repositories/Repository/@MetadataUpdated</PATH>";
put "<PATH syntax='XPath'>";
put "/GetRepositories/Repositories/Repository/@MetadataUpdated</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>24</LENGTH>";
put '</COLUMN>';
put '</TABLE></SXLEMAP>';

View File

@@ -44,15 +44,19 @@ filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="roles"><TABLE name="roles">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup</TABLE-PATH>";
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup";
put "</TABLE-PATH>";
put '<COLUMN name="roleuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Id</PATH>";
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Id";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";
put '</COLUMN><COLUMN name="rolename">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Name</PATH>";
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Name";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>256</LENGTH>";
put '</COLUMN><COLUMN name="roledesc">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Desc</PATH>";
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/IdentityGroup/@Desc";
put "</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>500</LENGTH>";
put '</COLUMN></TABLE></SXLEMAP>';
run;

View File

@@ -1,20 +1,26 @@
/**
@file
@brief Writes the code of an to an external file, or the log if none provided
@details Get the
@brief Writes the code of an STP to an external file
@details Fetches the SAS code from a Stored Process where the code is stored
in metadata.
usage:
Usage:
%mm_getstpcode(tree=/some/meta/path
,name=someSTP
,outloc=/some/unquoted/filename.ext
)
@param tree= The metadata path of the Stored Process (can also contain name)
@param name= Stored Process name. Leave blank if included above.
@param outloc= full and unquoted path to the desired text file. This will be
overwritten if it already exists. If not provided, the code will be written
to the log.
@param [in] tree= The metadata path of the Stored Process (can also contain
name)
@param [in] name= Stored Process name. Leave blank if included above.
@param [out] outloc= (0) full and unquoted path to the desired text file.
This will be overwritten if it already exists.
@param [out] outref= (0) Fileref to which to write the code.
@param [out] showlog=(NO) Set to YES to print log to the window
<h4> SAS Macros </h4>
@li mf_getuniquefileref.sas
@author Allan Bowe
@@ -23,8 +29,10 @@
%macro mm_getstpcode(
tree=/User Folders/sasdemo/somestp
,name=
,outloc=
,outloc=0
,outref=0
,mDebug=1
,showlog=NO
);
%local mD;
@@ -92,14 +100,18 @@ data _null_;
stop;
%local outeng;
%if %length(&outloc)=0 %then %let outeng=TEMP;
%if "&outloc"="0" %then %let outeng=TEMP;
%else %let outeng="&outloc";
%local fref;
%if &outref=0 %then %let fref=%mf_getuniquefileref();
%else %let fref=&outref;
/* read the content, byte by byte, resolving escaped chars */
filename __outdoc &outeng lrecl=100000;
filename &fref &outeng lrecl=100000;
data _null_;
length filein 8 fileid 8;
filein = fopen("__getdoc","I",1,"B");
fileid = fopen("__outdoc","O",1,"B");
fileid = fopen("&fref","O",1,"B");
rec = "20"x;
length entity $6;
do while(fread(filein)=0);
@@ -140,9 +152,9 @@ data _null_;
rc=fclose(fileid);
run;
%if &outeng=TEMP %then %do;
%if &showlog=YES %then %do;
data _null_;
infile __outdoc lrecl=32767 end=last;
infile &fref lrecl=32767 end=last;
input;
if _n_=1 then putlog '>>stpcodeBEGIN<<';
putlog _infile_;
@@ -151,6 +163,8 @@ run;
%end;
filename __getdoc clear;
filename __outdoc clear;
%if &outref=0 %then %do;
filename &fref clear;
%end;
%mend;
%mend mm_getstpcode;

View File

@@ -23,9 +23,10 @@
combine with the <code>tree=</code> parameter.
@param outds= the dataset to create that contains the list of stps.
@param mDebug= set to 1 to show debug messages in the log
@param showDesc= provide a non blank value to return stored process descriptions
@param showUsageVersion= provide a non blank value to return the UsageVersion. This
is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards).
@param showDesc= provide a non blank value to return stored process
descriptions
@param showUsageVersion= provide a non blank value to return the UsageVersion.
This is either 1000000 (type 1, 9.2) or 2000000 (type2, 9.3 onwards).
@returns outds dataset containing the following columns
- stpuri

View File

@@ -1,7 +1,7 @@
/**
@file mm_gettableid.sas
@brief Get the metadata id for a particular table
@details Provide a libref and table name to return the corresponding metadata id
@details Provide a libref and table name to return the corresponding metadata
in an output datataset.
Usage:

View File

@@ -35,7 +35,7 @@ proc metadata in=
<!-- include <REPOSID> XML element and a repository identifier -->
<Reposid>$METAREPOSITORY</Reposid>
</Options>
</GetTypes>'
</GetTypes>'
out=response;
run;

View File

@@ -48,7 +48,8 @@ filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Person</TABLE-PATH>";
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Person";
put "</TABLE-PATH>";
put '<COLUMN name="uri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Person/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>32</LENGTH>";

View File

@@ -99,7 +99,7 @@ run;
when ('&#x0d;') rec='0D'x;
when ('&#36;' ) rec='$' ;
when ('&#x09;') rec='09'x;
otherwise putlog "WARNING: missing value for " entity=;
otherwise putlog "%str(WARN)ING: missing value for " entity=;
end;
rc =fput(fileid, substr(rec,1,1));
rc =fwrite(fileid);

View File

@@ -12,7 +12,8 @@
Usage:
%* import the macros (or make them available some other way);
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;
%* create sample text file as input to the macro;
@@ -92,7 +93,8 @@
%let port=%sysfunc(getoption(metaport));
%let platform_object_path=%mf_loc(POF);
%let connx_string=%str(-host &host -port &port -user &mmxuser -password &mmxpass);
%let connx_string=%str(-host &host -port &port -user &mmxuser %trim(
)-password &mmxpass);
%mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable)

View File

@@ -15,7 +15,8 @@
Usage:
%* load macros;
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;
%* export everything;
@@ -104,7 +105,8 @@ filename sxlemap temp;
data _null_;
file sxlemap;
put '<SXLEMAP version="1.2" name="SASObjects"><TABLE name="SASObjects">';
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree</TABLE-PATH>";
put "<TABLE-PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree";
put "</TABLE-PATH>";
put '<COLUMN name="pathuri">';
put "<PATH syntax='XPath'>/GetMetadataObjects/Objects/Tree/@Id</PATH>";
put "<TYPE>character</TYPE><DATATYPE>string</DATATYPE><LENGTH>64</LENGTH>";

View File

@@ -70,7 +70,7 @@ data _null_;
run;
%if &appuri=stopifempty %then %do;
%put WARNING: &app.(Application) not found!;
%put %str(WARN)ING: &app.(Application) not found!;
%return;
%end;

View File

@@ -58,12 +58,12 @@ data _null_;
run;
%if &tsuri=stopifempty %then %do;
%put WARNING: &path/&name.(Document) not found!;
%put %str(WARN)ING: &path/&name.(Document) not found!;
%return;
%end;
%if %length(&text)<2 %then %do;
%put WARNING: No text supplied!!;
%put %str(WARN)ING: No text supplied!!;
%return;
%end;

View File

@@ -10,8 +10,8 @@
@param target= full path to the STP being deleted
@param type= Either WKS or STP depending on whether Workspace or Stored Process
type required
@param type= Either WKS or STP depending on whether Workspace or
Stored Process type required
@version 9.4
@author Allan Bowe
@@ -34,7 +34,7 @@ data _null_;
call symputx('stpuri',uri,'l');
run;
%if &cmtype ne ClassifierMap %then %do;
%put WARNING: No Stored Process found at &target;
%put %str(WARN)ING: No Stored Process found at &target;
%return;
%end;
@@ -51,7 +51,8 @@ data _null_;
n+1;
rc=metadata_getattr(uri,"Name",name);
if name='Stored Process' then do;
rc = METADATA_SETATTR(uri,'StoredText','<?xml version="1.0" encoding="UTF-8"?>'
rc = METADATA_SETATTR(uri,'StoredText'
,'<?xml version="1.0" encoding="UTF-8"?>'
!!'<StoredProcess><ServerContext LogicalServerType="'!!"&newtype"
!!'" OtherAllowed="false"/><ResultCapabilities Package="false" '
!!' Streaming="true"/><OutputParameters/></StoredProcess>');

View File

@@ -68,12 +68,12 @@ data _null_;
run;
%if &tsuri=stopifempty %then %do;
%put WARNING: &stp.(StoredProcess) not found!;
%put %str(WARN)ING: &stp.(StoredProcess) not found!;
%return;
%end;
%if %length(&stpcode)<2 %then %do;
%put WARNING: No SAS code supplied!!;
%put %str(WARN)ING: No SAS code supplied!!;
%return;
%end;

View File

@@ -68,8 +68,16 @@
%else %if &action=OPEN %then %do;
/* fix encoding */
OPTIONS NOBOMFILE;
/**
* check engine type to avoid the below err message:
* > Function is only valid for filerefs using the CACHE access method.
*/
data _null_;
rc = stpsrv_header('Content-type',"text/html; encoding=utf-8");
set sashelp.vextfl(where=(fileref="_WEBOUT"));
if xengine='STREAM' then do;
rc=stpsrv_header('Content-type',"text/html; encoding=utf-8");
end;
run;
/* setup json */
@@ -83,16 +91,9 @@
%end;
%else %if &action=ARR or &action=OBJ %then %do;
%if &sysver=9.4 %then %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=PROCJSON,dbg=%str(&_debug)
)
%end;
%else %do;
%mp_jsonout(&action,&ds,dslabel=&dslabel,fmt=&fmt
,engine=DATASTEP,dbg=%str(&_debug)
)
%end;
%end;
%else %if &action=CLOSE %then %do;
%if %str(&_debug) ge 131 %then %do;
@@ -108,14 +109,14 @@
i+1;
call symputx('wt'!!left(i),name,'l');
call symputx('wtcnt',i,'l');
data _null_; file &fref encoding='utf-8';
data _null_; file &fref mod encoding='utf-8';
put ",""WORK"":{";
%do i=1 %to &wtcnt;
%let wt=&&wt&i;
proc contents noprint data=&wt
out=_data_ (keep=name type length format:);
run;%let tempds=%scan(&syslast,2,.);
data _null_; file &fref encoding='utf-8';
data _null_; file &fref mod encoding='utf-8';
dsid=open("WORK.&wt",'is');
nlobs=attrn(dsid,'NLOBS');
nvars=attrn(dsid,'NVARS');
@@ -126,10 +127,10 @@
put ',"nvars":' nvars;
%mp_jsonout(OBJ,&tempds,jref=&fref,dslabel=colattrs,engine=DATASTEP)
%mp_jsonout(OBJ,&wt,jref=&fref,dslabel=first10rows,engine=DATASTEP)
data _null_; file &fref encoding='utf-8';
data _null_; file &fref mod encoding='utf-8';
put "}";
%end;
data _null_; file &fref encoding='utf-8';
data _null_; file &fref mod encoding='utf-8';
put "}";
run;
%end;

View File

@@ -20,7 +20,8 @@ Usage:
run;
filename outref "%sysfunc(pathname(work))";
%mmx_spkexport(metaloc=%str(/30.Projects/3001.Internal/300115.DataController/dc1)
%mmx_spkexport(
metaloc=%str(/30.Projects/3001.Internal/300115.DataController/dc1)
,secureref=tmp
,outspkpath=%str(/tmp)
)
@@ -56,7 +57,8 @@ Usage:
/* get creds */
%inc &secureref/nosource;
%let connx_string=%str(-host &host -port &port -user '&mmxuser' -password '&mmxpass');
%let connx_string=
%str(-host &host -port &port -user '&mmxuser' -password '&mmxpass');
%mm_tree(root=%str(&metaloc) ,types=EXPORTABLE ,outds=exportable)

1688
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@sasjs/core",
"description": "SAS Macro Library for Application Development",
"description": "Production Ready Macros for SAS Application Developers",
"license": "MIT",
"keywords": [
"SAS",
@@ -29,5 +29,14 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"docs": "sasjs doc && ./sasjs/utils/build.sh"
},
"devDependencies": {
"@sasjs/cli": "^2.14.2",
"ghooks": "^2.0.4"
},
"config": {
"ghooks": {
"pre-commit": "sasjs lint"
}
}
}

View File

@@ -14,20 +14,20 @@ HIDE_SCOPE_NAMES = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_MEMBERS = YES
HTML_OUTPUT = $(DOXY_HTML_OUTPUT)
HTML_HEADER = $(DOXY_CONTENT)new_header.html
HTML_FOOTER = $(DOXY_CONTENT)new_footer.html
HTML_EXTRA_STYLESHEET = $(DOXY_CONTENT)new_stylesheet.css
HTML_HEADER = $(HTML_HEADER)
HTML_EXTRA_FILES = $(HTML_EXTRA_FILES)
HTML_FOOTER = $(HTML_FOOTER)
HTML_EXTRA_STYLESHEET = $(HTML_EXTRA_STYLESHEET)
INHERIT_DOCS = NO
INLINE_INFO = NO
INPUT = $(DOXY_CONTENT)../../README.md \
= $(DOXY_CONTENT)../../main.dox \
$(DOXY_INPUT)
INPUT = $(DOXY_INPUT)
INPUT += main.dox
LAYOUT_FILE = $(LAYOUT_FILE)
USE_MDFILE_AS_MAINPAGE = README.md
LAYOUT_FILE = $(DOXY_CONTENT)DoxygenLayout.xml
MAX_INITIALIZER_LINES = 0
PROJECT_NAME = Macro Core
PROJECT_LOGO = $(DOXY_CONTENT)Macro_core_website_1.png
PROJECT_BRIEF = "Production Ready Macros for SAS Application Developers"
PROJECT_NAME = $(PROJECT_NAME)
PROJECT_LOGO = $(PROJECT_LOGO)
PROJECT_BRIEF = $(PROJECT_BRIEF)
RECURSIVE = YES
REPEAT_BRIEF = NO
SHOW_NAMESPACES = NO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 146 KiB

View File

@@ -1,6 +0,0 @@
ECHO on
rmdir /s /q C:\DEVELOPMENT\output
mkdir C:\DEVELOPMENT\output
doxygen.exe Doxyfile

26
sasjs/doxy/doxygen.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
sasjs/doxy/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -8,7 +8,7 @@
<li class="footer">
$generatedby
<a href="https://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen"
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen"
/></a>
$doxygenversion
</li>
@@ -24,9 +24,10 @@
<address class="footer">
<small>
$generatedby &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="$relpath^doxygen.png" alt="doxygen" />
<img class="footer" src="$relpath^doxygen.svg" alt="doxygen" />
</a>
$doxygenversion
</small>
</address>
<!--END !GENERATE_TREEVIEW-->

View File

@@ -9,7 +9,7 @@
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<title>$projectname: $title</title>
<meta name="description" content="$projectbrief" />
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
@@ -19,7 +19,7 @@
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" />
<link rel="icon" href="https://sasjs.io/img/runningman.jpg" />
<link rel="shortcut icon" href="$relpath^favicon.ico" type="image/x-icon" />
$extrastylesheet
</head>
<body>
@@ -33,52 +33,24 @@
<tr style="height: 56px">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo">
<img alt="Logo" src="$relpath^$projectlogo" />
<a href="$relpath^"
><img alt="Logo" src="$relpath^$projectlogo"
/></a>
</td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign" style="padding-left: 0.5em">
<div id="projectname">
<!--BEGIN PROJECT_NUMBER-->&#160;<span id="projectnumber"
>$projectnumber</span
><!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF-->
<div id="projectbrief">
Production Ready Macros for SAS Application Developers<br />
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</div>
<meta name="Description" content="$projectbrief" />
<!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td style="padding-left: 0.5em">
<div id="projectbrief">$projectbrief</div>
<table
style="padding-left: 2em"
cellspacing="0"
cellpadding="0"
>
<tr>
<td>
Production Ready Macros for SAS Application Developers
</td>
</tr>
<tr>
<td>
<a href="https://github.com/sasjs/core">
https://github.com/sasjs/core
</a>
</td>
</tr>
</table>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<td>$searchbox</td>

View File

@@ -1,7 +1,48 @@
{
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
"macroFolders": ["base", "meta", "metax", "viya", "lua"],
"docConfig":{
"displayMacroCore": false
"macroFolders": [
"base",
"meta",
"metax",
"viya",
"lua"
],
"docConfig": {
"displayMacroCore": false,
"enableLineage": false,
"doxyContent": {
"favIcon": "runningman.jpg",
"logo": "Macro_core_website_1.png",
"readMe": "../../README.md"
}
},
"defaultTarget": "viya",
"targets": [
{
"name": "viya",
"serverUrl": "https://sas.analytium.co.uk",
"serverType": "SASVIYA",
"appLoc": "/Public/temp/macrocore",
"deployConfig": {
"deployServicePack": true
},
"macroFolders": [
"base",
"meta",
"metax",
"viya",
"lua",
"tests/base",
"tests/viya"
],
"contextName": "SAS Job Execution compute context",
"testConfig": {
"initProgram": "tests/testinit.sas",
"termProgram": "tests/testterm.sas",
"macroVars": {
"mcTestAppLoc": "/Public/temp/macrocore"
}
}
}
]
}

View File

@@ -0,0 +1,60 @@
/**
@file
@brief Testing mf_getvarlist macro
<h4> SAS Macros </h4>
@li mf_getvarlist.sas
**/
%let test1=%mf_getvarlist(sashelp.class);
%let test2=%mf_getvarlist(sashelp.class,dlm=X);
%let test3=%mf_getvarlist(sashelp.class,dlm=%str(,),quote=double);
%let test4=%mf_getvarlist(sashelp.class,typefilter=N);
%let test5=%mf_getvarlist(sashelp.class,typefilter=C);
data work.test_results;
length test_description $256 test_result $4 test_comments base result $256;
test_description="Basic test";
base=symget('test1');
result='Name Sex Age Height Weight';
if base=result then test_result='PASS';
else test_result='FAIL';
test_comments="Comparing "!!trim(base)!!' vs '!!trim(result);
output;
test_description="DLM test";
base=symget('test2');
result='NameXSexXAgeXHeightXWeight';
if base=result then test_result='PASS';
else test_result='FAIL';
test_comments="Comparing "!!trim(base)!!' vs '!!trim(result);
output;
test_description="DLM + quote test";
base=symget('test3');
result='"Name","Sex","Age","Height","Weight"';
if base=result then test_result='PASS';
else test_result='FAIL';
test_comments="Comparing "!!trim(base)!!' vs '!!trim(result);
output;
test_description="Numeric Filter";
base=symget('test4');
result='Age Height Weight';
if base=result then test_result='PASS';
else test_result='FAIL';
test_comments="Comparing "!!trim(base)!!' vs '!!trim(result);
output;
test_description="Char Filter";
base=symget('test5');
result='Name Sex';
if base=result then test_result='PASS';
else test_result='FAIL';
test_comments="Comparing "!!trim(base)!!' vs '!!trim(result);
output;
drop base result;
run;

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