Rung 2 of the #376 ratchet: `sas/.sasjslint` promotes noUndeclaredMacros to "error". Its 25 findings on release/7.22 are cleared, and two of them were real faults rather than missing prose. WHAT WAS BROKEN Comparing the compiled program against the macros it calls (compile, then list every project macro each generated service calls but does not define) found three services calling macros that were absent from their own compiled program: - services/validations/tables_all.sas calls %mf_getuniquename() on its third line and never declared it. - services/validations/mpe_tables.dsn.sas calls %mf_getuniquename() and %dc_assignlib() and declared neither. - services/hooks/mpe_tables_postedit.sas calls %dc_assignlib(), which is supplied by the service that includes the hook (see below). The two validation services are configured as rule_value entries in MPE_TABLES (mpe_makedata.sas and the v3.12/v3.13 migrations), so the calls run when a user reviews or filters a library - each would have raised "Apparent invocation of macro ... not resolved". Both now declare what they call, and the definitions are present in the compiled program. WHAT CHANGED - 14 files declare the macros they call but had been relying on another declaration to bring in: mf_getquotedstr, mf_nobs, mf_getuser (twice), mf_getapploc, mf_getattrn, mp_abort (six services), mf_getuniquename (twice), dc_assignlib, bitemporal_closeouts. These were defined in the compiled program already; the declarations make the dependency visible and the two validation services correct. - 6 hook scripts get a file-local @sasjslint {"ignoredMacros": [...]} (mp_abort, plus dc_assignlib in mpe_tables_postedit) instead of a macro list. Hooks are included at run time by the service that owns them, so the macros are already in that session, and these files are copied and edited by end users - they should not carry a declaration section to maintain. Recorded in AGENTS.md. - bitemporal_dataloader.test.2.sas loses its test 30 block: it is gated with `and 0` ("skip until have a db") and its only setup line calls %assign_direct_lib, a macro that exists nowhere in this project or in @sasjs/core. Enabling it would fail on the first line. VERIFICATION - `npm run lint:sas`: 18 warnings, 0 errors, exit 0. - `sasjs compile`: succeeds; the called-but-undefined list drops from three services to the one hook exception; the only definition-count changes are additions. - 18 warnings remain for the next rungs: noBannedStrings (13), macro name length (2) and case (2), indentationMultiple (1).
330 lines
10 KiB
SAS
Executable File
330 lines
10 KiB
SAS
Executable File
/**
|
|
@file
|
|
@brief Generic loader for tables configured in &mpelib..mpe_tables
|
|
@details General wrapper for loading tables using ALL load types.
|
|
|
|
Called by admin/postdata. The dclib should already be assigned.
|
|
|
|
@param [in] dclib= (NOTPROVIDED) The data controller control library
|
|
@param [in] libds= the library.dataset to load
|
|
@param [in] now= (%sysfunc(datetime())) Static processed timestamp
|
|
|
|
<h4> SAS Macros </h4>
|
|
@li bitemporal_dataloader.sas
|
|
@li mf_existvar.sas
|
|
@li mf_getengine.sas
|
|
@li mf_getuniquename.sas
|
|
@li mf_getuser.sas
|
|
@li mp_abort.sas
|
|
@li mp_loadformat.sas
|
|
@li mp_lockanytable.sas
|
|
|
|
@version 9.2
|
|
@author 4GL Apps Ltd
|
|
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
|
|
and may not be re-distributed or re-sold without the express permission of
|
|
4GL Apps Ltd.
|
|
|
|
**/
|
|
|
|
%macro mpe_targetloader(libds= /* library.dataset to LOAD (target) */
|
|
,now= %sysfunc(datetime()) /* static processed timestamp */
|
|
,etlsource= /* process from whence the data came */
|
|
,STAGING_DS= STAGING_DS /* name of staging (work) dataset which should
|
|
be appended into the target. */
|
|
,LOADTARGET=NO /* set to yes to actually load the target */
|
|
,CLOSE_VARS= /* provide close vars to override defaults */
|
|
,dclib=NOTPROVIDED
|
|
,mdebug=0
|
|
,dc_dttmtfmt=%sysfunc(datetime())
|
|
);
|
|
%local lib ds nobs;
|
|
|
|
/**
|
|
* if a format catalog (suffix "-FC") we assume the catalog has already been
|
|
* created by the calling program with a libds of work.fmtextract
|
|
*/
|
|
%let orig_lib=%upcase(%scan(&libds,1,.));
|
|
%let orig_ds=%upcase(%scan(&libds,2,.));
|
|
%let orig_libds=&libds;
|
|
%if %scan(&libds,2,-)=FC %then %do;
|
|
%let lib=WORK;
|
|
%let ds=FMTEXTRACT;
|
|
%let libds=&lib..&ds;
|
|
%end;
|
|
%else %do;
|
|
%let lib=&orig_lib;
|
|
%let ds=&orig_ds;
|
|
%end;
|
|
|
|
%mp_abort(iftrue= (&dclib=NOTPROVIDED)
|
|
,mac=&sysmacroname
|
|
,msg=%str(dclib=NOTPROVIDED)
|
|
)
|
|
|
|
/* get table attributes */
|
|
%let nobs=0;
|
|
data work.sumo_config;
|
|
set &mpelib..mpe_tables;
|
|
where &dc_dttmtfmt. lt tx_to
|
|
and libref="&orig_lib"
|
|
and dsn="&orig_ds";
|
|
call symputx('LOADTYPE',loadtype,'l');
|
|
call symputx('BUSKEY',buskey,'l');
|
|
call symputx('VAR_TXFROM',var_txfrom,'l');
|
|
call symputx('VAR_TXTO',var_txto,'l');
|
|
call symputx('VAR_BUSFROM',var_busfrom,'l');
|
|
call symputx('VAR_BUSTO',var_busto,'l');
|
|
call symputx('VAR_PROCESSED',VAR_PROCESSED,'l');
|
|
call symputx('RK_UNDERLYING',RK_UNDERLYING,'l');
|
|
%if %length(&CLOSE_VARS)=0 %then %do;
|
|
call symputx('CLOSE_VARS',CLOSE_VARS,'l');
|
|
%end;
|
|
call symputx('nobs',_n_,'l');
|
|
if missing(AUDIT_LIBDS) then AUDIT_LIBDS="&dclib..MPE_AUDIT";
|
|
call symputx('AUDIT_LIBDS',AUDIT_LIBDS,'l');
|
|
put (_all_)(=);
|
|
run;
|
|
|
|
/* check if table is actually configured to load */
|
|
%if &nobs ne 1 %then %do;
|
|
proc sql;
|
|
insert into &mpelib..mpe_loads
|
|
set USER_NM="%mf_getuser()"
|
|
,STATUS='FAILED (BAD DS)'
|
|
,CSV_DIR=symget('ETLSOURCE')
|
|
,PROCESSED_DTTM=&now;
|
|
%end;
|
|
%mp_abort(iftrue= (&nobs=0)
|
|
,mac=&sysmacroname
|
|
,msg=%str(Table not registered in &mpelib..mpe_tables)
|
|
)
|
|
%mp_abort(iftrue= (&nobs>1)
|
|
,mac=&sysmacroname
|
|
,msg=%str(Something is very wrong with the PK in &mpelib..mpe_tables)
|
|
)
|
|
|
|
|
|
%if &LOADTYPE=TXTEMPORAL %then %do;
|
|
|
|
%bitemporal_dataloader(bus_from=,bus_to= /* explicitly empty*/
|
|
,tech_from=&VAR_TXFROM
|
|
,tech_to = &VAR_TXTO
|
|
,base_lib=&lib
|
|
,base_dsn=&ds
|
|
,append_lib=WORK
|
|
,append_dsn=&STAGING_DS
|
|
,high_date='31DEC9999:23:59:59'dt
|
|
,PK= &buskey
|
|
,ETLSOURCE=&ETLSOURCE
|
|
,LOADTYPE=&loadtype
|
|
,RK_UNDERLYING=&RK_UNDERLYING
|
|
,LOADTARGET=&LOADTARGET
|
|
,RK_UPDATE_MAXKEYTABLE=&LOADTARGET
|
|
,CLOSE_VARS=&CLOSE_VARS
|
|
,processed=&VAR_PROCESSED
|
|
,dclib=&dclib
|
|
,outds_audit=&AUDIT_LIBDS
|
|
)
|
|
%end;
|
|
%else %if &loadtype=REPLACE %then %do;
|
|
%if &LOADTARGET=YES %then %do;
|
|
%mp_lockanytable(LOCK,lib=&lib,ds=&ds,ref=%str(&etlsource),
|
|
ctl_ds=&dclib..mpe_lockanytable
|
|
)
|
|
data WORK.&STAGING_DS;
|
|
set WORK.&STAGING_DS;
|
|
%if %mf_existvar(&libds,&VAR_PROCESSED) %then %do;
|
|
&VAR_PROCESSED = &now;
|
|
%end;
|
|
drop _____DELETE__THIS__RECORD_____;
|
|
run;
|
|
%local engine_type;
|
|
%let engine_type=%mf_getengine(&lib);
|
|
%if &engine_type=CAS %then %do;
|
|
/* Fixed char variables cannot be appended to CAS varchar columns, so
|
|
cast them (per the target table structure) in a CASUSER copy of the
|
|
staging table, then append via data step. Approach mirrors the CAS
|
|
append in bitemporal_dataloader.sas */
|
|
proc contents noprint data=&libds
|
|
out=work.rpl_base_cols(keep=name type);
|
|
run;
|
|
proc contents noprint data=WORK.&STAGING_DS
|
|
out=work.rpl_stag_cols(keep=name type);
|
|
run;
|
|
proc sql noprint;
|
|
create table work.rpl_vchars as
|
|
select a.name
|
|
from work.rpl_base_cols a
|
|
inner join work.rpl_stag_cols b
|
|
on upcase(a.name)=upcase(b.name)
|
|
where a.type=6; /* varchar in target */
|
|
quit;
|
|
/* get varchar variables ready for casting */
|
|
%local vcfmt vcrename vcassign vcdrop tmpds;
|
|
%let vcfmt=;
|
|
%let vcrename=;
|
|
%let vcassign=;
|
|
%let vcdrop=;
|
|
data _null_;
|
|
set work.rpl_vchars end=last;
|
|
length vcrename vcassign vcdrop vcfmt $32767 rancol $32;
|
|
retain vcrename vcassign vcdrop vcfmt;
|
|
if _n_=1 then vcrename='(rename=(';
|
|
rancol=resolve('%mf_getuniquename()');
|
|
vcfmt=trim(vcfmt)!!'length '!!cats(name)!!' varchar(*);';
|
|
vcrename=trim(vcrename)!!' '!!cats(name,'=',rancol);
|
|
vcassign=cats(vcassign,name,'=',rancol,';');
|
|
vcdrop=cats(vcdrop,'drop '!!rancol,';');
|
|
if last then do;
|
|
vcrename=cats(vcrename,'))');
|
|
call symputx('vcfmt',vcfmt);
|
|
call symputx('vcrename',vcrename);
|
|
call symputx('vcassign',vcassign);
|
|
call symputx('vcdrop',vcdrop);
|
|
end;
|
|
run;
|
|
/* prepare a temp cas table with varchars casted */
|
|
%let tmpds=%mf_getuniquename();
|
|
data casuser.&tmpds;
|
|
&vcfmt
|
|
set WORK.&STAGING_DS &vcrename;
|
|
&vcassign
|
|
&vcdrop
|
|
run;
|
|
/* exit on err condition before the destructive truncate below */
|
|
%if &syscc>0 %then %do;
|
|
%mp_lockanytable(UNLOCK,lib=&lib,ds=&ds,ref=&ETLSOURCE (aborted),
|
|
ctl_ds=&dclib..mpe_lockanytable
|
|
)
|
|
/* ensure cleanup */
|
|
proc sql;drop table CASUSER.&tmpds;quit;
|
|
%end;
|
|
%mp_abort(iftrue= (&syscc>0)
|
|
,mac=&sysmacroname
|
|
,msg=%str(syscc=&syscc - aborting before REPLACE truncate of &libds.)
|
|
)
|
|
/* CAS tables do not support SQL deletes, so truncate with deleteRows.
|
|
This is deliberately the last step before the append, to minimise
|
|
the time in which the target table is empty. */
|
|
proc cas;
|
|
table.deleteRows / table={caslib="&lib",name="&ds",where="1=1"};
|
|
quit;
|
|
/* load the target with varchars applied */
|
|
data &libds (append=yes) / sessref=dcsession;
|
|
set casuser.&tmpds;
|
|
run;
|
|
/* drop temp table */
|
|
proc sql;
|
|
drop table CASUSER.&tmpds;
|
|
quit;
|
|
%end;
|
|
%else %do;
|
|
/* exit on err condition before the destructive delete below */
|
|
%if &syscc>0 %then %do;
|
|
%mp_lockanytable(UNLOCK,lib=&lib,ds=&ds,ref=&ETLSOURCE (aborted),
|
|
ctl_ds=&dclib..mpe_lockanytable
|
|
)
|
|
%end;
|
|
%mp_abort(iftrue= (&syscc>0)
|
|
,mac=&sysmacroname
|
|
,msg=%str(syscc=&syscc - aborting before REPLACE delete of &libds.)
|
|
)
|
|
proc sql;
|
|
delete * from &libds;
|
|
quit;
|
|
proc append base=&libds data=WORK.&STAGING_DS force nowarn;run;
|
|
%end;
|
|
|
|
%mp_lockanytable(UNLOCK,lib=&lib,ds=&ds,ctl_ds=&dclib..mpe_lockanytable)
|
|
%end;
|
|
%else %do;
|
|
/* is full replace so treat all staged records as new in diff screen */
|
|
data work.outds_mod work.outds_add ;
|
|
set work.&staging_ds;
|
|
output work.outds_add;
|
|
run;
|
|
/* previous table will be considered fully deleted */
|
|
data work.outds_del;
|
|
set &lib..&ds;
|
|
run;
|
|
%end;
|
|
%end;
|
|
%else %if &loadtype=UPDATE %then %do;
|
|
%bitemporal_dataloader(bus_from=,bus_to=
|
|
,tech_from= ,tech_to = /* explicitly empty*/
|
|
,base_lib=&lib
|
|
,base_dsn=&ds
|
|
,append_lib=WORK
|
|
,append_dsn=&STAGING_DS
|
|
,high_date='31DEC9999:23:59:59'dt
|
|
,PK= &buskey
|
|
,ETLSOURCE=%superq(etlsource)
|
|
,LOADTYPE=UPDATE
|
|
,RK_UNDERLYING=&RK_UNDERLYING
|
|
,LOADTARGET=&LOADTARGET
|
|
,RK_UPDATE_MAXKEYTABLE=&LOADTARGET
|
|
,processed=&VAR_PROCESSED
|
|
,dclib=&dclib
|
|
,outds_audit=&AUDIT_LIBDS
|
|
)
|
|
%end;
|
|
%else %if &loadtype=FORMAT_CAT %then %do;
|
|
/**
|
|
* run mp_formatload
|
|
* inputs:
|
|
* - LOADTARGET
|
|
* - CATALOG
|
|
* - STAGEDATA
|
|
* - LOADAUDIT
|
|
* outputs:
|
|
* work.outds_add
|
|
* work.outds_del
|
|
* work.outds_mod
|
|
*/
|
|
%mp_loadformat(&orig_libds
|
|
,&staging_ds
|
|
,loadtarget=&LOADTARGET
|
|
,auditlibds=&AUDIT_LIBDS
|
|
,locklibds=&dclib..mpe_lockanytable
|
|
,delete_col=_____DELETE__THIS__RECORD_____
|
|
,outds_add=outds_add
|
|
,outds_del=outds_del
|
|
,outds_mod=outds_mod
|
|
,mdebug=&mdebug
|
|
)
|
|
%end;
|
|
%else %if &loadtype=BITEMPORAL %then %do;
|
|
%bitemporal_dataloader(bus_from=&VAR_BUSFROM,bus_to=&VAR_BUSTO
|
|
,tech_from=&VAR_TXFROM
|
|
,tech_to = &VAR_TXTO
|
|
,base_lib=&lib
|
|
,base_dsn=&ds
|
|
,append_lib=WORK
|
|
,append_dsn=&STAGING_DS
|
|
,high_date='31DEC9999:23:59:59'dt
|
|
,PK= &buskey
|
|
,ETLSOURCE=%superq(etlsource)
|
|
,LOADTYPE=BITEMPORAL
|
|
,RK_UNDERLYING=&RK_UNDERLYING
|
|
,LOADTARGET=&LOADTARGET
|
|
,RK_UPDATE_MAXKEYTABLE=&LOADTARGET
|
|
,CLOSE_VARS=&CLOSE_VARS
|
|
,processed=&VAR_PROCESSED
|
|
,dclib=&dclib
|
|
,outds_audit=&AUDIT_LIBDS
|
|
)
|
|
%end;
|
|
%else %do;
|
|
%put %str(WARN)ING: LOADTYPE &LOADTYPE not supported;
|
|
%let syscc=4;
|
|
%mp_abort(msg=LOADTYPE &LOADTYPE not supported,mac=mpe_targetloader.sas)
|
|
%end;
|
|
|
|
%mp_abort(iftrue= (&syscc ne 0)
|
|
,mac=&_program
|
|
,msg=%str(syscc=&syscc exiting MPE_TARGETLOADER macro)
|
|
)
|
|
|
|
%mend mpe_targetloader;
|