1
0
mirror of https://github.com/sasjs/core.git synced 2025-12-11 14:34:35 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
Allan Bowe
713f7544cd Merge pull request #167 from sasjs/issue166
feat: adding mddl_xx series of macros (and tests).  Closes #166
2022-02-07 15:42:01 +02:00
munja
de4e96ab01 fix: dependency under wrong header in mp_getformats 2022-02-07 14:41:32 +01:00
munja
3e7b15c7db feat: adding mddl_xx series of macros (and tests). Closes #166 2022-02-07 14:14:25 +01:00
Allan Bowe
eb2ccfbbca Merge pull request #165 from sasjs/fmtstore
feat: adding format catalog capability to mp_filterstore
2022-02-07 00:02:34 +02:00
munja
70e508e583 fix: failing test 2022-02-06 23:01:46 +01:00
munja
8b0acf2eae feat: adding format catalog capability to mp_filterstore 2022-02-06 22:12:00 +01:00
25 changed files with 767 additions and 219 deletions

View File

@@ -31,19 +31,28 @@ Documentation: https://core.sasjs.io
## Components
### BASE library (SAS9/Viya)
### BASE library (All Platforms)
- OS independent
- Not metadata aware
- Works on all SAS Platforms
- No X command
- Prefixes: _mf_, _mp_
#### FCMP library (SAS9/Viya)
### DDL library (All Platforms)
- OS independent
- Works on all SAS Platforms
- No X command
- Prefixes: _mddl_(lib)_ -> where lib can be "SAS" (in relation to a SAS component) or "DC" (in relation to a Data Controller component)
This library will not be used for storing data entries (such as formats or datalines). Where this becomes necessary in the future, a new repo will be created, in order to keep the NPM bundle size down (for the benefit of those looking to embed purely macros in their applications).
#### FCMP library (All Platforms)
- Function and macro names are identical, except for special cases
- Prefixes: _mcf_
The fcmp macros are used to generate fcmp functions, and can be used with or
without the `proc fcmp` wrapper.
The fcmp macros are used to generate fcmp functions, and can be used with or without the `proc fcmp` wrapper.
### META library (SAS9 only)
@@ -125,6 +134,7 @@ filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
- one macro per file
- prefixes:
- _mcf_ for macro compiled functions (proc fcmp)
- _mddl_ for macros containing DDL (Data Definition Language)
- _mf_ for macro functions (can be used in open code).
- _ml_ for macros that are used to compile LUA modules
- _mm_ for metadata macros (interface with the metadata server).
@@ -192,11 +202,13 @@ When contributing to this library, it is therefore important to ensure that all
## Breaking Changes
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major (breaking) release becomes necessary:
We are currently on major release v4. Breaking changes should be marked with the [deprecated](https://www.doxygen.nl/manual/commands.html#cmddeprecated) doxygen tag. The following changes are planned when the next major/breaking release (v5) becomes necessary:
* mp_testservice.sas to be renamed as mp_execute.sas (as it doesn't actually test anything)
* `insert_cmplib` option of mcf_xxx macros will be deprecated (the option is now checked automatically with value inserted only if needed)
* mcf_xxx macros to have `wrap=` option defaulted to YES for convenience
* mcf_xxx macros to have `wrap=` option defaulted to YES for convenience. Set this option explicitly to avoid issues.
* mp_getddl.sas to be renamed to mp_ds2ddl.sas (consistent with other ds2xxx macros). A wrapper macro is already in place, and you are able to use this immediately. The default for SHOWLOG will also be YES instead of NO.
* mp_coretable.sas will be replaced by the standalone macros in the `ddl` folder (which are already available)
## Star Gazing

355
all.sas
View File

@@ -3533,24 +3533,32 @@ run;
make use of permanent tables. To avoid duplication in definitions, this
macro provides a central location for managing the corresponding DDL.
Note - this macro is likely to be deprecated in future in favour of a
dedicated "datamodel" folder (prefix mddl)
Any corresponding data would go in a seperate repo, to avoid this one
ballooning in size!
Example usage:
%mp_coretable(LOCKTABLE,libds=work.locktable)
@param [in] table_ref The type of table to create. Example values:
@li DIFFTABLE - Used to store changes to tables. Used by mp_storediffs.sas
and mp_stackdiffs.sas
@li FILTER_DETAIL - For storing detailed filter values. Used by
mp_filterstore.sas.
@li FILTER_SUMMARY - For storing summary filter values. Used by
mp_filterstore.sas.
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
@li MAXKEYTABLE - For storing the maximum retained key information. Used
by mp_retainedkey.sas
@li DIFFTABLE
@li FILTER_DETAIL
@li FILTER_SUMMARY
@li LOCKANYTABLE
@li MAXKEYTABLE
@param [in] libds= (0) The library.dataset reference used to create the table.
If not provided, then the DDL is simply printed to the log.
<h4> SAS Macros </h4>
@li mddl_dc_difftable.sas
@li mddl_dc_filterdetail.sas
@li mddl_dc_filtersummary.sas
@li mddl_dc_locktable.sas
@li mddl_dc_maxkeytable.sas
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_lockanytable.sas
@@ -3569,76 +3577,21 @@ run;
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
proc sql;
%if &table_ref=DIFFTABLE %then %do;
create table &outds(
load_ref char(36) label='unique load reference',
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
libref char(8) label='Library Reference (8 chars)',
dsn char(32) label='Dataset Name (32 chars)',
key_hash char(32) label=
'MD5 Hash of primary key values (pipe seperated)',
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
is_pk num label='Is Primary Key Field? (1/0)',
is_diff num label=
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
tgtvar_nm char(32) label='Target variable name (32 chars)',
oldval_num num format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) label='New (character) value',
constraint pk_mpe_audit
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
);
%mddl_dc_difftable(libds=&outds)
%end;
%else %if &table_ref=LOCKTABLE %then %do;
create table &outds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%mddl_dc_locktable(libds=&outds)
%end;
%else %if &table_ref=FILTER_SUMMARY %then %do;
create table &outds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk));
%mddl_dc_filtersummary(libds=&outds)
%end;
%else %if &table_ref=FILTER_DETAIL %then %do;
create table &outds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line));
%mddl_dc_filterdetail(libds=&outds)
%end;
%else %if &table_ref=MAXKEYTABLE %then %do;
create table &outds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%mddl_dc_maxkeytable(libds=&outds)
%end;
%if &libds=0 %then %do;
describe table &syslast;
drop table &syslast;
@@ -4888,6 +4841,35 @@ data _null_;
run;
%mend mp_ds2csv;/**
@file
@brief A wrapper for mp_getddl.sas
@details In the next release, this will be the main version.
<h4> SAS Macros </h4>
@li mp_getddl.sas
**/
%macro mp_ds2ddl(libds,fref=getddl,flavour=SAS,showlog=YES,schema=
,applydttm=NO
)/*/STORE SOURCE*/;
%local libref;
%let libds=%upcase(&libds);
%let libref=%scan(&libds,1,.);
%if &libref=&libds %then %let libds=WORK.&libds;
%mp_getddl(%scan(&libds,1,.)
,%scan(&libds,2,.)
,fref=&fref
,flavour=SAS
,showlog=&showlog
,schema=&schema
,applydttm=&applydttm
)
%mend mp_ds2ddl;/**
@file
@brief Converts every value in a dataset to formatted value
@details Converts every value to it's formatted value. All variables will
@@ -5394,7 +5376,8 @@ options varlenchk=&optval;
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).
Used for dynamic filtering in [Data Controller for SAS&reg;](
https://datacontroller.io).
Usage:
@@ -5513,7 +5496,7 @@ data &outds;
output;
end;
if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS','GE','LE')
then do;
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=;
@@ -5701,8 +5684,13 @@ filename &outref temp;
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
https://datacontroller.io).
A more recent feature of this macro is the ability to support filter queries
on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
parameter - where the "ds" in this case is the catalog name.
@param [in] libds= The target dataset to be filtered (lib should be assigned)
@param [in] libds= The target dataset to be filtered (lib should be assigned).
If filtering a format catalog, add the following suffix: `-FC`.
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
be validated. Has the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
@@ -5738,6 +5726,7 @@ filename &outref temp;
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_getuniquename.sas
@li mf_getvalue.sas
@li mf_islibds.sas
@@ -5771,7 +5760,10 @@ filename &outref temp;
%put &sysmacroname entry vars:;
%put _local_;
%local ds1 ds2 ds3 ds4 filter_hash;
%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
%let libds=%upcase(&libds);
%let orig_libds=&libds;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro entry)
@@ -5789,12 +5781,27 @@ filename &outref temp;
,msg=%str(Invalid lock_table value: &lock_table)
)
/* validate query */
/**
* validate query
* use format catalog export, if a format
*/
%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
%let libds=%scan(&libds,1,-); /* chop off -FC extension */
%let ds0=%mf_getuniquename(prefix=fmtds_);
%let libds=&ds0;
/*
There is no need to export the entire format catalog here - the validations
are done against the data model, not the data values. So we can simply
hardcode the structure based on the cntlout dataset.
*/
%mddl_sas_cntlout(libds=&ds0)
%end;
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
/* hash the result */
%let ds1=%mf_getuniquename(prefix=hashds);
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
%if &mdebug=1 %then %do;
data _null_;
@@ -5825,7 +5832,7 @@ run;
%let ds3=%mf_getuniquename(prefix=filtersum);
data work.&ds3;
if 0 then set &filter_summary;
filter_table=symget('libds');
filter_table="&orig_libds";
filter_hash="&filter_hash";
PROCESSED_DTTM=%sysfunc(datetime());
output;
@@ -6961,6 +6968,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|`WHICHPATH `|`**OTHER** `|`**OTHER** `|`big fat problem if not path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|`O `|` `|` `|` `|` `|
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_dedup.sas
@li mf_getfmtlist.sas
@li mf_getfmtname.sas
@@ -7015,30 +7023,7 @@ create table &outsummary as
%if "&outdetail" ne "0" %then %do;
/* ensure base table always exists */
proc sql;
create table &outdetail(
FMTNAME char(32) label='Format name'
,START char(16) label='Starting value for format'
,END char(16) label='Ending value for format'
,LABEL char(256) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
,DEFAULT num length=3 label='Default length'
,LENGTH num length=3 label='Format length'
,FUZZ num label='Fuzz value'
,PREFIX char(2) label='Prefix characters'
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'
,DECSEP char(1) label='Decimal separator'
,DIG3SEP char(1) label='Three-digit separator'
,DATATYPE char(8) label='Date/time/datetime?'
,LANGUAGE char(8) label='Language for date strings'
);
%mddl_sas_cntlout(libds=&outdetail)
/* grab the location of each format */
%let fmtcnt=0;
data _null_;
@@ -7055,6 +7040,10 @@ create table &outsummary as
proc format library=&&fmtloc&i CNTLOUT=&tempds;
select &&fmtname&i;
run;
data &tempds;
length label $256;
set &tempds;
run;
proc append base=&outdetail data=&tempds;
run;
%end;
@@ -11917,6 +11906,170 @@ ods package publish archive properties
ods package close;
%mend mp_zip;/**
@file
@brief Difftable DDL
@details Used to store changes to tables. Used by mp_storediffs.sas
and mp_stackdiffs.sas
**/
%macro mddl_dc_difftable(libds=WORK.DIFFTABLE);
proc sql;
create table &libds(
load_ref char(36) label='unique load reference',
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
libref char(8) label='Library Reference (8 chars)',
dsn char(32) label='Dataset Name (32 chars)',
key_hash char(32) label=
'MD5 Hash of primary key values (pipe seperated)',
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
is_pk num label='Is Primary Key Field? (1/0)',
is_diff num label=
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
tgtvar_nm char(32) label='Target variable name (32 chars)',
oldval_num num format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) label='New (character) value',
constraint pk_mpe_audit
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
);
%mend mddl_dc_difftable;/**
@file
@brief Filtertable DDL
@details For storing detailed filter values. Used by
mp_filterstore.sas.
**/
%macro mddl_dc_filterdetail(libds=WORK.FILTER_DETAIL);
proc sql;
create table &libds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line)
);
%mend mddl_dc_filterdetail;/**
@file
@brief Filtersummary DDL
@details For storing summary filter values. Used by
mp_filterstore.sas.
**/
%macro mddl_dc_filtersummary(libds=WORK.FILTER_SUMMARY);
proc sql;
create table &libds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk)
);
%mend mddl_dc_filtersummary;/**
@file
@brief Locktable DDL
@details For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
**/
%macro mddl_dc_locktable(libds=WORK.LOCKTABLE);
proc sql;
create table &libds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds)
);
%mend mddl_dc_locktable;/**
@file
@brief Maxkeytable DDL
@details For storing the maximum retained key information. Used
by mp_retainedkey.sas
**/
%macro mddl_dc_maxkeytable(libds=WORK.MAXKEYTABLE);
proc sql;
create table &libds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%mend mddl_dc_maxkeytable;/**
@file
@brief The CNTLOUT table generated by proc format
@details This table will actually change format depending on the data values,
therefore the max possible lengths are described here to enable consistency
when dealing with format data.
**/
%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);
proc sql;
create table &libds(
FMTNAME char(32) label='Format name'
,START char(16) label='Starting value for format'
,END char(16) label='Ending value for format'
,LABEL char(256) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
,DEFAULT num length=3 label='Default length'
,LENGTH num length=3 label='Format length'
,FUZZ num label='Fuzz value'
,PREFIX char(2) label='Prefix characters'
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'
,DECSEP char(1) label='Decimal separator'
,DIG3SEP char(1) label='Three-digit separator'
,DATATYPE char(8) label='Date/time/datetime?'
,LANGUAGE char(8) label='Language for date strings'
);
%mend mddl_sas_cntlout;/**
@file mm_adduser2group.sas
@brief Adds a user to a group
@details Adds a user to a metadata group. The macro first checks whether the

View File

@@ -5,24 +5,32 @@
make use of permanent tables. To avoid duplication in definitions, this
macro provides a central location for managing the corresponding DDL.
Note - this macro is likely to be deprecated in future in favour of a
dedicated "datamodel" folder (prefix mddl)
Any corresponding data would go in a seperate repo, to avoid this one
ballooning in size!
Example usage:
%mp_coretable(LOCKTABLE,libds=work.locktable)
@param [in] table_ref The type of table to create. Example values:
@li DIFFTABLE - Used to store changes to tables. Used by mp_storediffs.sas
and mp_stackdiffs.sas
@li FILTER_DETAIL - For storing detailed filter values. Used by
mp_filterstore.sas.
@li FILTER_SUMMARY - For storing summary filter values. Used by
mp_filterstore.sas.
@li LOCKANYTABLE - For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
@li MAXKEYTABLE - For storing the maximum retained key information. Used
by mp_retainedkey.sas
@li DIFFTABLE
@li FILTER_DETAIL
@li FILTER_SUMMARY
@li LOCKANYTABLE
@li MAXKEYTABLE
@param [in] libds= (0) The library.dataset reference used to create the table.
If not provided, then the DDL is simply printed to the log.
<h4> SAS Macros </h4>
@li mddl_dc_difftable.sas
@li mddl_dc_filterdetail.sas
@li mddl_dc_filtersummary.sas
@li mddl_dc_locktable.sas
@li mddl_dc_maxkeytable.sas
<h4> Related Macros </h4>
@li mp_filterstore.sas
@li mp_lockanytable.sas
@@ -41,76 +49,21 @@
%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
proc sql;
%if &table_ref=DIFFTABLE %then %do;
create table &outds(
load_ref char(36) label='unique load reference',
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
libref char(8) label='Library Reference (8 chars)',
dsn char(32) label='Dataset Name (32 chars)',
key_hash char(32) label=
'MD5 Hash of primary key values (pipe seperated)',
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
is_pk num label='Is Primary Key Field? (1/0)',
is_diff num label=
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
tgtvar_nm char(32) label='Target variable name (32 chars)',
oldval_num num format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) label='New (character) value',
constraint pk_mpe_audit
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
);
%mddl_dc_difftable(libds=&outds)
%end;
%else %if &table_ref=LOCKTABLE %then %do;
create table &outds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
%mddl_dc_locktable(libds=&outds)
%end;
%else %if &table_ref=FILTER_SUMMARY %then %do;
create table &outds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk));
%mddl_dc_filtersummary(libds=&outds)
%end;
%else %if &table_ref=FILTER_DETAIL %then %do;
create table &outds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line));
%mddl_dc_filterdetail(libds=&outds)
%end;
%else %if &table_ref=MAXKEYTABLE %then %do;
create table &outds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%mddl_dc_maxkeytable(libds=&outds)
%end;
%if &libds=0 %then %do;
describe table &syslast;
drop table &syslast;

30
base/mp_ds2ddl.sas Normal file
View File

@@ -0,0 +1,30 @@
/**
@file
@brief A wrapper for mp_getddl.sas
@details In the next release, this will be the main version.
<h4> SAS Macros </h4>
@li mp_getddl.sas
**/
%macro mp_ds2ddl(libds,fref=getddl,flavour=SAS,showlog=YES,schema=
,applydttm=NO
)/*/STORE SOURCE*/;
%local libref;
%let libds=%upcase(&libds);
%let libref=%scan(&libds,1,.);
%if &libref=&libds %then %let libds=WORK.&libds;
%mp_getddl(%scan(&libds,1,.)
,%scan(&libds,2,.)
,fref=&fref
,flavour=SAS
,showlog=&showlog
,schema=&schema
,applydttm=&applydttm
)
%mend mp_ds2ddl;

View File

@@ -6,7 +6,8 @@
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).
Used for dynamic filtering in [Data Controller for SAS&reg;](
https://datacontroller.io).
Usage:
@@ -125,7 +126,7 @@ data &outds;
output;
end;
if OPERATOR_NM not in
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS')
('=','>','<','<=','>=','BETWEEN','IN','NOT IN','NE','CONTAINS','GE','LE')
then do;
REASON_CD='Invalid OPERATOR_NM: '!!cats(OPERATOR_NM);
putlog REASON_CD= OPERATOR_NM=;

View File

@@ -8,8 +8,13 @@
https://sasapps.io)). This macro is also used in [Data Controller for SAS](
https://datacontroller.io).
A more recent feature of this macro is the ability to support filter queries
on Format Catalogs. This is achieved by adding a `-FC` suffix to the `libds`
parameter - where the "ds" in this case is the catalog name.
@param [in] libds= The target dataset to be filtered (lib should be assigned)
@param [in] libds= The target dataset to be filtered (lib should be assigned).
If filtering a format catalog, add the following suffix: `-FC`.
@param [in] queryds= (WORK.FILTERQUERY) The temporary input query dataset to
be validated. Has the following format:
|GROUP_LOGIC:$3|SUBGROUP_LOGIC:$3|SUBGROUP_ID:8.|VARIABLE_NM:$32|OPERATOR_NM:$10|RAW_VALUE:$32767|
@@ -45,6 +50,7 @@
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_getuniquename.sas
@li mf_getvalue.sas
@li mf_islibds.sas
@@ -78,7 +84,10 @@
%put &sysmacroname entry vars:;
%put _local_;
%local ds1 ds2 ds3 ds4 filter_hash;
%local ds0 ds1 ds2 ds3 ds4 filter_hash orig_libds;
%let libds=%upcase(&libds);
%let orig_libds=&libds;
%mp_abort(iftrue= (&syscc ne 0)
,mac=mp_filterstore
,msg=%str(syscc=&syscc on macro entry)
@@ -96,12 +105,27 @@
,msg=%str(Invalid lock_table value: &lock_table)
)
/* validate query */
/**
* validate query
* use format catalog export, if a format
*/
%if "%substr(&libds,%length(&libds)-2,3)"="-FC" %then %do;
%let libds=%scan(&libds,1,-); /* chop off -FC extension */
%let ds0=%mf_getuniquename(prefix=fmtds_);
%let libds=&ds0;
/*
There is no need to export the entire format catalog here - the validations
are done against the data model, not the data values. So we can simply
hardcode the structure based on the cntlout dataset.
*/
%mddl_sas_cntlout(libds=&ds0)
%end;
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
/* hash the result */
%let ds1=%mf_getuniquename(prefix=hashds);
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
%mp_hashdataset(&queryds,outds=&ds1,salt=&orig_libds)
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
%if &mdebug=1 %then %do;
data _null_;
@@ -132,7 +156,7 @@ run;
%let ds3=%mf_getuniquename(prefix=filtersum);
data work.&ds3;
if 0 then set &filter_summary;
filter_table=symget('libds');
filter_table="&orig_libds";
filter_hash="&filter_hash";
PROCESSED_DTTM=%sysfunc(datetime());
output;

View File

@@ -40,6 +40,7 @@ https://support.sas.com/documentation/cdl/en/proc/61895/HTML/default/viewer.htm#
|`WHICHPATH `|`**OTHER** `|`**OTHER** `|`big fat problem if not path1 `|`1 `|`40 `|`28 `|`28 `|`1E-12 `|` `|`0 `|` `|`0 `|`N `|`N `|`N `|`O `|` `|` `|` `|` `|
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_dedup.sas
@li mf_getfmtlist.sas
@li mf_getfmtname.sas
@@ -94,30 +95,7 @@ create table &outsummary as
%if "&outdetail" ne "0" %then %do;
/* ensure base table always exists */
proc sql;
create table &outdetail(
FMTNAME char(32) label='Format name'
,START char(16) label='Starting value for format'
,END char(16) label='Ending value for format'
,LABEL char(256) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
,DEFAULT num length=3 label='Default length'
,LENGTH num length=3 label='Format length'
,FUZZ num label='Fuzz value'
,PREFIX char(2) label='Prefix characters'
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'
,DECSEP char(1) label='Decimal separator'
,DIG3SEP char(1) label='Three-digit separator'
,DATATYPE char(8) label='Date/time/datetime?'
,LANGUAGE char(8) label='Language for date strings'
);
%mddl_sas_cntlout(libds=&outdetail)
/* grab the location of each format */
%let fmtcnt=0;
data _null_;
@@ -134,6 +112,10 @@ create table &outsummary as
proc format library=&&fmtloc&i CNTLOUT=&tempds;
select &&fmtname&i;
run;
data &tempds;
length label $256;
set &tempds;
run;
proc append base=&outdetail data=&tempds;
run;
%end;

View File

@@ -84,7 +84,7 @@ options noquotelenmax;
"""
f = open('all.sas', "w") # r / r+ / rb / rb+ / w / wb
f.write(header)
folders=['base','meta','metax','server','viya','lua','fcmp']
folders=['base','ddl','meta','metax','server','viya','lua','fcmp']
for folder in folders:
filenames = [fn for fn in Path('./' + folder).iterdir() if fn.match("*.sas")]
filenames.sort()

34
ddl/mddl_dc_difftable.sas Normal file
View File

@@ -0,0 +1,34 @@
/**
@file
@brief Difftable DDL
@details Used to store changes to tables. Used by mp_storediffs.sas
and mp_stackdiffs.sas
**/
%macro mddl_dc_difftable(libds=WORK.DIFFTABLE);
proc sql;
create table &libds(
load_ref char(36) label='unique load reference',
processed_dttm num format=E8601DT26.6 label='Processed at timestamp',
libref char(8) label='Library Reference (8 chars)',
dsn char(32) label='Dataset Name (32 chars)',
key_hash char(32) label=
'MD5 Hash of primary key values (pipe seperated)',
move_type char(1) label='Either (A)ppended, (D)eleted or (M)odified',
is_pk num label='Is Primary Key Field? (1/0)',
is_diff num label=
'Did value change? (1/0/-1). Always -1 for appends and deletes.',
tgtvar_type char(1) label='Either (C)haracter or (N)umeric',
tgtvar_nm char(32) label='Target variable name (32 chars)',
oldval_num num format=best32. label='Old (numeric) value',
newval_num num format=best32. label='New (numeric) value',
oldval_char char(32765) label='Old (character) value',
newval_char char(32765) label='New (character) value',
constraint pk_mpe_audit
primary key(load_ref,libref,dsn,key_hash,tgtvar_nm)
);
%mend mddl_dc_difftable;

View File

@@ -0,0 +1,27 @@
/**
@file
@brief Filtertable DDL
@details For storing detailed filter values. Used by
mp_filterstore.sas.
**/
%macro mddl_dc_filterdetail(libds=WORK.FILTER_DETAIL);
proc sql;
create table &libds(
filter_hash char(32) not null,
filter_line num not null,
group_logic char(3) not null,
subgroup_logic char(3) not null,
subgroup_id num not null,
variable_nm varchar(32) not null,
operator_nm varchar(12) not null,
raw_value varchar(4000) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_hash,filter_line)
);
%mend mddl_dc_filterdetail;

View File

@@ -0,0 +1,22 @@
/**
@file
@brief Filtersummary DDL
@details For storing summary filter values. Used by
mp_filterstore.sas.
**/
%macro mddl_dc_filtersummary(libds=WORK.FILTER_SUMMARY);
proc sql;
create table &libds(
filter_rk num not null,
filter_hash char(32) not null,
filter_table char(41) not null,
processed_dttm num not null format=E8601DT26.6,
constraint pk_mpe_filteranytable
primary key(filter_rk)
);
%mend mddl_dc_filtersummary;

25
ddl/mddl_dc_locktable.sas Normal file
View File

@@ -0,0 +1,25 @@
/**
@file
@brief Locktable DDL
@details For "locking" tables prior to multipass loads. Used by
mp_lockanytable.sas
**/
%macro mddl_dc_locktable(libds=WORK.LOCKTABLE);
proc sql;
create table &libds(
lock_lib char(8),
lock_ds char(32),
lock_status_cd char(10) not null,
lock_user_nm char(100) not null ,
lock_ref char(200),
lock_pid char(10),
lock_start_dttm num format=E8601DT26.6,
lock_end_dttm num format=E8601DT26.6,
constraint pk_mp_lockanytable primary key(lock_lib,lock_ds)
);
%mend mddl_dc_locktable;

View File

@@ -0,0 +1,24 @@
/**
@file
@brief Maxkeytable DDL
@details For storing the maximum retained key information. Used
by mp_retainedkey.sas
**/
%macro mddl_dc_maxkeytable(libds=WORK.MAXKEYTABLE);
proc sql;
create table &libds(
keytable varchar(41) label='Base table in libref.dataset format',
keycolumn char(32) format=$32.
label='The Retained key field containing the key values.',
max_key num label=
'Integer representing current max RK or SK value in the KEYTABLE',
processed_dttm num format=E8601DT26.6
label='Datetime this value was last updated',
constraint pk_mpe_maxkeyvalues
primary key(keytable));
%mend mddl_dc_maxkeytable;

38
ddl/mddl_sas_cntlout.sas Normal file
View File

@@ -0,0 +1,38 @@
/**
@file
@brief The CNTLOUT table generated by proc format
@details This table will actually change format depending on the data values,
therefore the max possible lengths are described here to enable consistency
when dealing with format data.
**/
%macro mddl_sas_cntlout(libds=WORK.CNTLOUT);
proc sql;
create table &libds(
FMTNAME char(32) label='Format name'
,START char(16) label='Starting value for format'
,END char(16) label='Ending value for format'
,LABEL char(256) label='Format value label'
,MIN num length=3 label='Minimum length'
,MAX num length=3 label='Maximum length'
,DEFAULT num length=3 label='Default length'
,LENGTH num length=3 label='Format length'
,FUZZ num label='Fuzz value'
,PREFIX char(2) label='Prefix characters'
,MULT num label='Multiplier'
,FILL char(1) label='Fill character'
,NOEDIT num length=3 label='Is picture string noedit?'
,TYPE char(1) label='Type of format'
,SEXCL char(1) label='Start exclusion'
,EEXCL char(1) label='End exclusion'
,HLO char(13) label='Additional information'
,DECSEP char(1) label='Decimal separator'
,DIG3SEP char(1) label='Three-digit separator'
,DATATYPE char(8) label='Date/time/datetime?'
,LANGUAGE char(8) label='Language for date strings'
);
%mend mddl_sas_cntlout;

14
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"license": "MIT",
"devDependencies": {
"@sasjs/cli": "3.6.0",
"@sasjs/core": "4.4.4"
"@sasjs/core": "4.4.7"
}
},
"node_modules/@sasjs/adapter": {
@@ -110,9 +110,9 @@
}
},
"node_modules/@sasjs/core": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.4.4.tgz",
"integrity": "sha512-gN6d0fvhaofp7buemS5KIOo5Bz8lbqhsEQD7SuH5FZ02MQurmfu7A0Zg0lIEi0w2/ptI4M/sZdF4D2DRh1D5xA==",
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.4.7.tgz",
"integrity": "sha512-9+HmwspvWu/gH9ElnmRaGdjOCspOidBRUYUfxLzgOy1Ya1JMZ2RErMklCAMg7XI0Us5jecd2FXdo8zlQDhRkWQ==",
"dev": true
},
"node_modules/@sasjs/lint": {
@@ -2830,9 +2830,9 @@
}
},
"@sasjs/core": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.4.4.tgz",
"integrity": "sha512-gN6d0fvhaofp7buemS5KIOo5Bz8lbqhsEQD7SuH5FZ02MQurmfu7A0Zg0lIEi0w2/ptI4M/sZdF4D2DRh1D5xA==",
"version": "4.4.7",
"resolved": "https://registry.npmjs.org/@sasjs/core/-/core-4.4.7.tgz",
"integrity": "sha512-9+HmwspvWu/gH9ElnmRaGdjOCspOidBRUYUfxLzgOy1Ya1JMZ2RErMklCAMg7XI0Us5jecd2FXdo8zlQDhRkWQ==",
"dev": true
},
"@sasjs/lint": {

View File

@@ -34,6 +34,6 @@
},
"devDependencies": {
"@sasjs/cli": "3.6.0",
"@sasjs/core": "4.4.4"
"@sasjs/core": "4.4.7"
}
}

View File

@@ -2,13 +2,15 @@
"$schema": "https://raw.githubusercontent.com/sasjs/utils/main/src/types/sasjsconfig-schema.json",
"macroFolders": [
"base",
"ddl",
"fcmp",
"meta",
"metax",
"server",
"viya",
"lua",
"tests/crossplatform"
"tests/crossplatform",
"tests/ddl"
],
"docConfig": {
"displayMacroCore": false,

View File

@@ -0,0 +1,112 @@
/**
@file
@brief Testing mp_filterstore macro with a format catalog
<h4> SAS Macros </h4>
@li mp_assert.sas
@li mp_assertdsobs.sas
@li mp_assertscope.sas
@li mp_coretable.sas
@li mp_filterstore.sas
**/
libname permlib (work);
%mp_coretable(LOCKTABLE,libds=permlib.locktable)
%mp_coretable(FILTER_SUMMARY,libds=permlib.filtsum)
%mp_coretable(FILTER_DETAIL,libds=permlib.filtdet)
%mp_coretable(MAXKEYTABLE,libds=permlib.maxkey)
/* valid filter */
data work.inds;
infile datalines4 dsd;
input GROUP_LOGIC:$3. SUBGROUP_LOGIC:$3. SUBGROUP_ID:8. VARIABLE_NM:$32.
OPERATOR_NM:$12. RAW_VALUE:$4000.;
datalines4;
AND,AND,1,Start,>,"'2'"
AND,AND,1,Fmtname,=,"'MORDOR'"
OR,OR,2,Label,IN,"('Dragon1','Dragon2')"
OR,OR,2,End,=,"'6'"
OR,OR,2,Start,GE,"'10'"
;;;;
run;
/* make some formats */
PROC FORMAT library=permlib.testfmts;
picture MyMSdt other='%0Y-%0m-%0dT%0H:%0M:%0S' (datatype=datetime);
RUN;
data work.fmts;
length fmtname $32;
do fmtname='SMAUG','MORDOR','GOLLUM';
do start=1 to 10;
label= cats('Dragon ',start);
output;
end;
end;
run;
proc sort data=work.fmts nodupkey;
by fmtname start;
run;
proc format cntlin=work.fmts library=permlib.testfmts;
run;
proc format library=permlib.testfmts;
invalue indays (default=13) other=42;
run;
%mp_assertscope(SNAPSHOT)
%mp_filterstore(libds=permlib.testfmts-fc,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
%mp_assertscope(COMPARE)
%mp_assert(iftrue=(&syscc=0),
desc=Ensure macro runs without errors,
outds=work.test_results
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test1 from work.result;
%mp_assert(iftrue=(&test1=1),
desc=Ensure filter rk is correct,
outds=work.test_results
)
/* Test 2 - load same table again and ensure we get the same RK */
%mp_filterstore(libds=permlib.testfmts-fc,
queryds=work.inds,
filter_summary=permlib.filtsum,
filter_detail=permlib.filtdet,
lock_table=permlib.locktable,
maxkeytable=permlib.maxkey,
outresult=work.result,
outquery=work.query,
mdebug=1
)
/* ensure only one record created */
%mp_assertdsobs(permlib.filtsum,
desc=Initial query - same obs,
test=ATMOST 1,
outds=work.test_results
)
/* check RK is correct */
proc sql noprint;
select max(filter_rk) into: test2 from work.result;
%mp_assert(iftrue=(&test2=1),
desc=Ensure filter rk is correct for second run,
outds=work.test_results
)

View File

@@ -0,0 +1,19 @@
/**
@file
@brief Difftable DDL test
<h4> SAS Macros </h4>
@li mddl_dc_difftable.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_dc_difftable(libds=WORK.DIFFTABLE)
%mp_assert(
iftrue=(%mf_existds(WORK.DIFFTABLE)=1),
desc=Checking table was created,
outds=work.test_results
)

View File

@@ -0,0 +1,18 @@
/**
@file
@brief Filtertable DDL test
<h4> SAS Macros </h4>
@li mddl_dc_filterdetail.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_dc_filterdetail(libds=WORK.TEST)
%mp_assert(
iftrue=(%mf_existds(WORK.TEST)=1),
desc=Checking table was created,
outds=work.test_results
)

View File

@@ -0,0 +1,18 @@
/**
@file
@brief Filtersummary DDL test
<h4> SAS Macros </h4>
@li mddl_dc_filtersummary.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_dc_filtersummary(libds=WORK.TEST)
%mp_assert(
iftrue=(%mf_existds(WORK.TEST)=1),
desc=Checking table was created,
outds=work.test_results
)

View File

@@ -0,0 +1,18 @@
/**
@file
@brief Locktable DDL test
<h4> SAS Macros </h4>
@li mddl_dc_locktable.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_dc_locktable(libds=WORK.TEST)
%mp_assert(
iftrue=(%mf_existds(WORK.TEST)=1),
desc=Checking table was created,
outds=work.test_results
)

View File

@@ -0,0 +1,18 @@
/**
@file
@brief Maxkeytable DDL test
<h4> SAS Macros </h4>
@li mddl_dc_maxkeytable.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_dc_maxkeytable(libds=WORK.TEST)
%mp_assert(
iftrue=(%mf_existds(WORK.TEST)=1),
desc=Checking table was created,
outds=work.test_results
)

View File

@@ -0,0 +1,18 @@
/**
@file
@brief mddl_sas_cntlout ddl test
<h4> SAS Macros </h4>
@li mddl_sas_cntlout.sas
@li mf_existds.sas
@li mp_assert.sas
**/
%mddl_sas_cntlout(libds=WORK.TEST)
%mp_assert(
iftrue=(%mf_existds(WORK.TEST)=1),
desc=Checking table was created,
outds=work.test_results
)