diff --git a/all.sas b/all.sas
index 83f0aea..d3fe7ad 100644
--- a/all.sas
+++ b/all.sas
@@ -3351,6 +3351,97 @@ run;
drop table work.&tempds;
%mend mp_copyfolder;/**
+ @file
+ @brief Create the permanent Core tables
+ @details Several macros in the [core](https://github.com/sasjs/core) library
+ make use of permanent tables. To avoid duplication in definitions, this
+ macro provides a central location for managing the corresponding DDL.
+
+ Example usage:
+
+ %mp_coretable(LOCKTABLE,libds=work.locktable)
+
+ @param [in] table_ref The type of table to create. Example values:
+ @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
+ @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.
+
+
Related Macros
+ @li mp_filterstore.sas
+ @li mp_lockanytable.sas
+ @li mp_retainedkey.sas
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mp_coretable(table_ref,libds=0
+)/*/STORE SOURCE*/;
+%local outds ;
+%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
+proc sql;
+%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));
+%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));
+%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));
+%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));
+%end;
+
+
+%if &libds=0 %then %do;
+ describe table &syslast;
+ drop table &syslast;
+%end;
+%mend mp_coretable;/**
@file mp_createconstraints.sas
@brief Creates constraints
@details Takes the output from mp_getconstraints.sas as input
@@ -5098,6 +5189,218 @@ filename &outref temp;
%mend mp_filtergenerate;
/**
+ @file
+ @brief Checks & Stores an input filter table and returns the Filter Key
+ @details Used to generate a FILTER_RK from an input query dataset. This
+ process requires several permanent tables (names are configurable). The
+ benefit of storing query values at backend is to enable stored 'views' of
+ filtered tables at frontend (ie, when building [SAS-Powered Apps](
+ https://sasapps.io)). This macro is also used in [Data Controller for SAS](
+ https://datacontroller.io).
+
+
+ @param [in] libds= The target dataset to be filtered (lib should be assigned)
+ @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|
+|---|---|---|---|---|---|
+|AND|AND|1|SOME_BESTNUM|>|1|
+|AND|AND|1|SOME_TIME|=|77333|
+ @param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
+ summary filter values. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
+ values:
+|FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
+|---|---|---|---|
+|`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
+|`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
+|`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
+ @param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
+ detailed (raw) filter values. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
+ values:
+|FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
+|---|---|---|---|---|---|---|---|---|
+|`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
+|`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
+|`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
+ @param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
+ manage concurrent access. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
+ @param [in] maxkeytable= (0) Optional permanent reference table used for
+ retained key tracking. Described in mp_retainedkey.sas.
+ @param [in] mdebug= set to 1 to enable DEBUG messages
+ @param [out] outresult= The result table with the FILTER_RK
+ @param [out] outquery= The original query, taken as extract after table load
+
+
+ SAS Macros
+ @li mf_getuniquename.sas
+ @li mf_getvalue.sas
+ @li mf_islibds.sas
+ @li mf_nobs.sas
+ @li mp_abort.sas
+ @li mp_filtercheck.sas
+ @li mp_hashdataset.sas
+ @li mp_retainedkey.sas
+
+ Related Macros
+ @li mp_filtercheck.sas
+ @li mp_filtergenerate.sas
+ @li mp_filtervalidate.sas
+ @li mp_filterstore.test.sas
+
+ @version 9.2
+ @author [Allan Bowe](https://www.linkedin.com/in/allanbowe)
+
+**/
+
+%macro mp_filterstore(libds=,
+ queryds=work.filterquery,
+ filter_summary=PERM.FILTER_SUMMARY,
+ filter_detail=PERM.FILTER_DETAIL,
+ lock_table=PERM.LOCK_TABLE,
+ maxkeytable=PERM.MAXKEYTABLE,
+ outresult=work.result,
+ outquery=work.query,
+ mdebug=1
+);
+%put &sysmacroname entry vars:;
+%put _local_;
+
+%local ds1 ds2 ds3 ds4 filter_hash;
+%mp_abort(iftrue= (&syscc ne 0)
+ ,mac=mp_filterstore
+ ,msg=%str(syscc=&syscc on macro entry)
+)
+%mp_abort(iftrue= (%mf_islibds(&filter_summary)=0)
+ ,mac=mp_filterstore
+ ,msg=%str(Invalid filter_summary value: &filter_summary)
+)
+%mp_abort(iftrue= (%mf_islibds(&filter_detail)=0)
+ ,mac=mp_filterstore
+ ,msg=%str(Invalid filter_detail value: &filter_detail)
+)
+%mp_abort(iftrue= (%mf_islibds(&lock_table)=0)
+ ,mac=mp_filterstore
+ ,msg=%str(Invalid lock_table value: &lock_table)
+)
+
+/* validate query */
+%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
+
+/* hash the result */
+%let ds1=%mf_getuniquename(prefix=hashds);
+%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
+%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
+%if &mdebug=1 %then %do;
+ data _null_;
+ putlog "filter_hash=&filter_hash";
+ set &ds1;
+ putlog (_all_)(=);
+ run;
+%end;
+
+/* check if data already exists for this hash */
+data &outresult;
+ set &filter_summary;
+ where filter_hash="&filter_hash";
+run;
+
+%mp_abort(iftrue= (&syscc ne 0)
+ ,mac=mp_filterstore
+ ,msg=%str(syscc=&syscc after hash check)
+)
+%mp_abort(iftrue= ("&filter_hash"=" ")
+ ,mac=mp_filterstore
+ ,msg=%str(problem with filter_hash generation)
+)
+
+%if %mf_nobs(&outresult)=0 %then %do;
+
+ /* update detail table first */
+ %let ds2=%mf_getuniquename(prefix=filterdetail);
+ data &ds2;
+ if 0 then set &filter_detail;
+ set &queryds;
+ format filter_hash $hex32. filter_line 8. processed_dttm E8601DT26.6;
+ filter_hash="&filter_hash";
+ filter_line=_n_;
+ PROCESSED_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
+ run;
+ %mp_lockanytable(LOCK,
+ lib=%scan(&filter_detail,1,.)
+ ,ds=%scan(&filter_detail,2,.)
+ ,ref=MP_FILTERSTORE update - &filter_hash
+ ,ctl_ds=&lock_table
+ )
+ proc append base=&filter_detail data=&ds2;
+ run;
+
+ %mp_lockanytable(UNLOCK,
+ lib=%scan(&filter_detail,1,.)
+ ,ds=%scan(&filter_detail,2,.)
+ ,ref=MP_FILTERSTORE update - &filter_hash
+ ,ctl_ds=&lock_table
+ )
+
+ /* now update summary table */
+ %let ds3=%mf_getuniquename(prefix=filtersum);
+ data &ds3;
+ if 0 then set &filter_summary;
+ filter_table=symget('libds');
+ filter_hash="&filter_hash";
+ PROCESSED_DTTM="%sysfunc(datetime(),E8601DT26.6)"dt;
+ output;
+ stop;
+ run;
+
+ %mp_lockanytable(LOCK,
+ lib=%scan(&filter_summary,1,.)
+ ,ds=%scan(&filter_summary,2,.)
+ ,ref=MP_FILTERSTORE update - &filter_hash
+ ,ctl_ds=&lock_table
+ )
+
+ %let ds4=%mf_getuniquename(prefix=filtersumappend);
+ %mp_retainedkey(
+ base_lib=%scan(&filter_summary,1,.)
+ ,base_dsn=%scan(&filter_summary,2,.)
+ ,append_lib=work
+ ,append_dsn=&ds3
+ ,retained_key=filter_rk
+ ,business_key=filter_hash
+ ,maxkeytable=&maxkeytable
+ ,locktable=&lock_table
+ ,outds=work.&ds4
+ )
+ proc append base=&filter_summary data=&ds4;
+ run;
+
+ %mp_lockanytable(UNLOCK,
+ lib=%scan(&filter_summary,1,.)
+ ,ds=%scan(&filter_summary,2,.)
+ ,ref=MP_FILTERSTORE update - &filter_hash
+ ,ctl_ds=&lock_table
+ )
+
+ data &outresult;
+ set &filter_summary;
+ where filter_hash="&filter_hash";
+ run;
+
+%end;
+
+proc sort data=&filter_detail(where=(filter_hash="&filter_hash")) out=&outquery;
+ by filter_line;
+run;
+
+%mp_abort(iftrue= (&syscc ne 0)
+ ,mac=mp_filterstore
+ ,msg=%str(syscc=&syscc on macro exit)
+)
+
+%mend mp_filterstore;/**
@file
@brief Checks a generated filter query for validity
@details Runs a generated filter in proc sql with the validate option.
@@ -7584,19 +7887,8 @@ select distinct lowcase(memname)
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking.
- Should already be assigned and available. Definition as follows:
-
- proc sql;
- create table &ctl_ds(
- lock_lib char(8),
- lock_ds char(32),
- lock_status_cd char(10) not null,
- lock_user_nm char(100) not null ,
- lock_ref char(200),
- lock_pid char(10),
- lock_start_dttm num format=E8601DT26.6,
- lock_end_dttm num format=E8601DT26.6,
- constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
+ Should already be assigned and available. The definition is available by
+ running mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
@@ -8326,19 +8618,12 @@ run;
@li permlib.base_table - the target table to be loaded (**not** loaded by this
macro)
@li permlib.maxkeytable - optional, used to store load metaadata.
- The structure is as follows:
+ The definition is available by running mp_coretable.sas as follows:
+ `mp_coretable(MAXKEYTABLE)`.
+ @li permlib.locktable - Necessary if maxkeytable is being populated. The
+ definition is available by running mp_coretable.sas as follows:
+ `mp_coretable(LOCKTABLE)`.
- proc sql;
- create table yourlib.maxkeytable(
- 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));
@param [in] base_lib= (WORK) Libref of the base (target) table.
@param [in] base_dsn= (BASETABLE) Name of the base (target) table.
@@ -8374,6 +8659,7 @@ run;
@li mp_lockanytable.sas
Related Macros
+ @li mp_filterstore.sas
@li mp_retainedkey.test.sas
@version 9.2
@@ -8403,7 +8689,7 @@ run;
%let tempds1=%mf_getuniquename();
%let tempds2=%mf_getuniquename();
%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);
-
+%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));
/* validation checks */
%let iserr=0;
%if &syscc>0 %then %do;
@@ -8434,14 +8720,18 @@ run;
%do x=1 %to %sysfunc(countw(&business_key));
/* check business key values exist */
%let key_field=%scan(&business_key,&x,%str( ));
- %if (not %mf_existvar(&app_libds,&key_field))
- or (not %mf_existvar(&base_libds,&key_field))
- %then %do;
+ %if not %mf_existvar(&app_libds,&key_field) %then %do;
%let iserr=1;
- %let msg=Business key (&key_field) not found!;
+ %let msg=Business key (&key_field) not found on &app_libds!;
+ %goto err;
+ %end;
+ %else %if not %mf_existvar(&base_libds,&key_field) %then %do;
+ %let iserr=1;
+ %let msg=Business key (&key_field) not found on &base_libds!;
+ %goto err;
%end;
%end;
-
+%err:
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
@@ -8505,7 +8795,7 @@ quit;
* Update maxkey table if link provided
*/
%if &maxkeytable ne 0 %then %do;
- proc sql;
+ proc sql noprint;
select count(*) into: check from &maxkeytable
where upcase(keytable)="&base_libds";
diff --git a/base/mp_coretable.sas b/base/mp_coretable.sas
new file mode 100644
index 0000000..d0eca56
--- /dev/null
+++ b/base/mp_coretable.sas
@@ -0,0 +1,92 @@
+/**
+ @file
+ @brief Create the permanent Core tables
+ @details Several macros in the [core](https://github.com/sasjs/core) library
+ make use of permanent tables. To avoid duplication in definitions, this
+ macro provides a central location for managing the corresponding DDL.
+
+ Example usage:
+
+ %mp_coretable(LOCKTABLE,libds=work.locktable)
+
+ @param [in] table_ref The type of table to create. Example values:
+ @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
+ @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.
+
+ Related Macros
+ @li mp_filterstore.sas
+ @li mp_lockanytable.sas
+ @li mp_retainedkey.sas
+
+ @version 9.2
+ @author Allan Bowe
+
+**/
+
+%macro mp_coretable(table_ref,libds=0
+)/*/STORE SOURCE*/;
+%local outds ;
+%let outds=%sysfunc(ifc(&libds=0,_data_,&libds));
+proc sql;
+%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));
+%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));
+%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));
+%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));
+%end;
+
+
+%if &libds=0 %then %do;
+ describe table &syslast;
+ drop table &syslast;
+%end;
+%mend mp_coretable;
\ No newline at end of file
diff --git a/base/mp_filterstore.sas b/base/mp_filterstore.sas
index 0b12e48..9bc99c2 100644
--- a/base/mp_filterstore.sas
+++ b/base/mp_filterstore.sas
@@ -2,10 +2,11 @@
@file
@brief Checks & Stores an input filter table and returns the Filter Key
@details Used to generate a FILTER_RK from an input query dataset. This
- process requires several permanent tables (names are configurable):
-
- @li filterdetail (contains raw values)
- @li filtersummary (contains summary data about the filter)
+ process requires several permanent tables (names are configurable). The
+ benefit of storing query values at backend is to enable stored 'views' of
+ filtered tables at frontend (ie, when building [SAS-Powered Apps](
+ https://sasapps.io)). This macro is also used in [Data Controller for SAS](
+ https://datacontroller.io).
@param [in] libds= The target dataset to be filtered (lib should be assigned)
@@ -16,21 +17,26 @@
|AND|AND|1|SOME_BESTNUM|>|1|
|AND|AND|1|SOME_TIME|=|77333|
@param [in] filter_summary= (PERM.FILTER_SUMMARY) Permanent table containing
- summary filter values. Structure:
+ summary filter values. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(FILTER_SUMMARY)`. Example
+ values:
|FILTER_RK:best.|FILTER_HASH:$32.|FILTER_TABLE:$41.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|
|`1 `|`540E96F566D194AB58DD4C413C99C9DB `|`VIYA6014.MPE_TABLES `|`1956084246 `|
|`2 `|`87737DB9EEE2650F5C89956CEAD0A14F `|`VIYA6014.MPE_X_TEST `|`1956084452.1`|
|`3 `|`8048BD908DBBD83D013560734E90D394 `|`VIYA6014.MPE_TABLES `|`1956093620.6`|
@param [in] filter_detail= (PERM.FILTER_DETAIL) Permanent table containing
- detailed (raw) filter values. Structure:
+ detailed (raw) filter values. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(FILTER_DETAIL)`. Example
+ values:
|FILTER_HASH:$32.|FILTER_LINE:best.|GROUP_LOGIC:$3.|SUBGROUP_LOGIC:$3.|SUBGROUP_ID:best.|VARIABLE_NM:$32.|OPERATOR_NM:$12.|RAW_VALUE:$4000.|PROCESSED_DTTM:datetime19.|
|---|---|---|---|---|---|---|---|---|
|`540E96F566D194AB58DD4C413C99C9DB `|`1 `|`AND `|`AND `|`1 `|`LIBREF `|`CONTAINS `|`DC`|`1956084245.8 `|
|`540E96F566D194AB58DD4C413C99C9DB `|`2 `|`AND `|`OR `|`2 `|`DSN `|`= `|` MPE_LOCK_ANYTABLE `|`1956084245.8 `|
|`87737DB9EEE2650F5C89956CEAD0A14F `|`1 `|`AND `|`AND `|`1 `|`PRIMARY_KEY_FIELD `|`IN `|`(1,2,3) `|`1956084451.9 `|
@param [in] lock_table= (PERM.LOCK_TABLE) Permanent locking table. Used to
- manage concurrent access. Described in mp_lockanytable.sas.
+ manage concurrent access. The definition is available by running
+ mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] maxkeytable= (0) Optional permanent reference table used for
retained key tracking. Described in mp_retainedkey.sas.
@param [in] mdebug= set to 1 to enable DEBUG messages
@@ -94,6 +100,7 @@
%mp_filtercheck(&queryds,targetds=&libds,abort=YES)
/* hash the result */
+%let ds1=%mf_getuniquename(prefix=hashds);
%mp_hashdataset(&queryds,outds=&ds1,salt=&libds)
%let filter_hash=%upcase(%mf_getvalue(&ds1,hashkey));
%if &mdebug=1 %then %do;
@@ -124,6 +131,7 @@ run;
/* update detail table first */
%let ds2=%mf_getuniquename(prefix=filterdetail);
data &ds2;
+ if 0 then set &filter_detail;
set &queryds;
format filter_hash $hex32. filter_line 8. processed_dttm E8601DT26.6;
filter_hash="&filter_hash";
@@ -168,13 +176,13 @@ run;
%mp_retainedkey(
base_lib=%scan(&filter_summary,1,.)
,base_dsn=%scan(&filter_summary,2,.)
- ,append_lib=%scan(&ds3,1,.)
- ,append_dsn=%scan(&ds3,2,.)
+ ,append_lib=work
+ ,append_dsn=&ds3
,retained_key=filter_rk
,business_key=filter_hash
,maxkeytable=&maxkeytable
,locktable=&lock_table
- ,outds=&ds4
+ ,outds=work.&ds4
)
proc append base=&filter_summary data=&ds4;
run;
diff --git a/base/mp_lockanytable.sas b/base/mp_lockanytable.sas
index adabb6e..afb0375 100644
--- a/base/mp_lockanytable.sas
+++ b/base/mp_lockanytable.sas
@@ -14,19 +14,8 @@
@param [in] ref= A meaningful reference to enable the lock to be traced. Max
length is 200 characters.
@param [out] ctl_ds= (0) The control table which controls the actual locking.
- Should already be assigned and available. Definition as follows:
-
- proc sql;
- create table &ctl_ds(
- lock_lib char(8),
- lock_ds char(32),
- lock_status_cd char(10) not null,
- lock_user_nm char(100) not null ,
- lock_ref char(200),
- lock_pid char(10),
- lock_start_dttm num format=E8601DT26.6,
- lock_end_dttm num format=E8601DT26.6,
- constraint pk_mp_lockanytable primary key(lock_lib,lock_ds));
+ Should already be assigned and available. The definition is available by
+ running mp_coretable.sas as follows: `mp_coretable(LOCKTABLE)`.
@param [in] loops= (25) Number of times to check for a lock.
@param [in] loop_secs= (1) Seconds to wait between each lock attempt
diff --git a/base/mp_retainedkey.sas b/base/mp_retainedkey.sas
index 25c3d22..8d183bd 100644
--- a/base/mp_retainedkey.sas
+++ b/base/mp_retainedkey.sas
@@ -24,19 +24,12 @@
@li permlib.base_table - the target table to be loaded (**not** loaded by this
macro)
@li permlib.maxkeytable - optional, used to store load metaadata.
- The structure is as follows:
+ The definition is available by running mp_coretable.sas as follows:
+ `mp_coretable(MAXKEYTABLE)`.
+ @li permlib.locktable - Necessary if maxkeytable is being populated. The
+ definition is available by running mp_coretable.sas as follows:
+ `mp_coretable(LOCKTABLE)`.
- proc sql;
- create table yourlib.maxkeytable(
- 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));
@param [in] base_lib= (WORK) Libref of the base (target) table.
@param [in] base_dsn= (BASETABLE) Name of the base (target) table.
@@ -102,7 +95,7 @@
%let tempds1=%mf_getuniquename();
%let tempds2=%mf_getuniquename();
%let comma_pk=%mf_getquotedstr(in_str=%str(&business_key),dlm=%str(,),quote=);
-
+%let outds=%sysfunc(ifc(%index(&outds,.)=0,work.&outds,&outds));
/* validation checks */
%let iserr=0;
%if &syscc>0 %then %do;
@@ -133,14 +126,18 @@
%do x=1 %to %sysfunc(countw(&business_key));
/* check business key values exist */
%let key_field=%scan(&business_key,&x,%str( ));
- %if (not %mf_existvar(&app_libds,&key_field))
- or (not %mf_existvar(&base_libds,&key_field))
- %then %do;
+ %if not %mf_existvar(&app_libds,&key_field) %then %do;
%let iserr=1;
- %let msg=Business key (&key_field) not found!;
+ %let msg=Business key (&key_field) not found on &app_libds!;
+ %goto err;
+ %end;
+ %else %if not %mf_existvar(&base_libds,&key_field) %then %do;
+ %let iserr=1;
+ %let msg=Business key (&key_field) not found on &base_libds!;
+ %goto err;
%end;
%end;
-
+%err:
%if &iserr=1 %then %do;
/* err case so first perform an unlock of the base table before exiting */
%mp_lockanytable(
@@ -204,7 +201,7 @@ quit;
* Update maxkey table if link provided
*/
%if &maxkeytable ne 0 %then %do;
- proc sql;
+ proc sql noprint;
select count(*) into: check from &maxkeytable
where upcase(keytable)="&base_libds";
diff --git a/tests/crossplatform/mp_coretable.test.sas b/tests/crossplatform/mp_coretable.test.sas
new file mode 100644
index 0000000..cd19134
--- /dev/null
+++ b/tests/crossplatform/mp_coretable.test.sas
@@ -0,0 +1,31 @@
+/**
+ @file
+ @brief Testing mp_coretable.sas macro
+
+ SAS Macros
+ @li mf_existds.sas
+ @li mp_coretable.sas
+ @li mp_assert.sas
+
+**/
+
+
+%mp_coretable(LOCKTABLE,libds=work.lock)
+%mp_assert(
+ iftrue=(%mf_existds(work.lock)=1),
+ desc=Lock table created,
+ outds=work.test_results
+)
+%mp_coretable(LOCKTABLE)
+%mp_assert(
+ iftrue=("&syscc"="0"),
+ desc=DDL export ran without errors,
+ outds=work.test_results
+)
+
+%mp_coretable(FILTER_SUMMARY,libds=work.sum)
+%mp_assert(
+ iftrue=(%mf_existds(work.sum)=1),
+ desc=Filter summary table created,
+ outds=work.test_results
+)
\ No newline at end of file
diff --git a/tests/crossplatform/mp_filterstore.test.sas b/tests/crossplatform/mp_filterstore.test.sas
new file mode 100644
index 0000000..370f824
--- /dev/null
+++ b/tests/crossplatform/mp_filterstore.test.sas
@@ -0,0 +1,85 @@
+/**
+ @file
+ @brief Testing mp_filterstore macro
+
+ SAS Macros
+ @li mp_coretable.sas
+ @li mp_filterstore.sas
+ @li mp_assertdsobs.sas
+ @li mp_assert.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,AGE,=,12
+AND,AND,1,SEX,<=,"'M'"
+AND,OR,2,Name,NOT IN,"('Jane','Alfred')"
+AND,OR,2,Weight,>=,77.7
+AND,OR,2,Weight,NE,77.7
+;;;;
+run;
+
+%mp_filterstore(libds=sashelp.class,
+ 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_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=sashelp.class,
+ 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
+)