Files
dc/sas/sasjs/macros/mpe_refreshtables.sas
T
dc 56b6620ef4
Build / Build-and-ng-test (pull_request) Successful in 5m34s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 18m57s
fix(refresh): one audit folder per refresh call
refreshcatalog.test and viewtables.test failed to complete with

  ERROR: could not create /tmp/DataController/<libref>/dc_staging/refreshcatalog:mpe_refreshcatalogs: + <stamp>

The staging folder a closeout writes its statements to is
&dc_staging_area/&etlsource, and etlsource defaulted to
'refreshcatalog:<step>:%mf_uid()'. A default is resolved when the macro is
compiled, not when it is called, so every refresh in a session reused the same
folder - the first step created it, the next one could not, and the abort took
the whole suite down. Whichever suite reached the staging area first decided
which others failed, which is why the count moved between runs.

etlsource now defaults to 0 and is set per call from
%mf_getuniquename(prefix=<step>): unique, a plain name rather than a path, and
resolved at the point of use.

Verified on the estate: refreshcatalog 4/4 (100%), viewtables 2/2 (100%).
2026-10-27 23:08:47 +00:00

370 lines
9.1 KiB
SAS

/**
@file mpe_refreshtables.sas
@brief Refreshes the data catalog
@details
@param [in] etlsource= (0) Defaults to %mf_getuniquename(prefix=refreshtables), identifies this load
in the MPE_DATALOADS audit row. That table is keyed on etlsource together
with processed_dttm, libref and dsn, so the uid keeps each load distinct.
Usage:
%mpe_refreshtables(sashelp)
<h4> SAS Macros </h4>
@li mf_getuniquename.sas
@li mp_abort.sas
@li bitemporal_dataloader.sas
@li mf_existfeature.sas
@li mf_getengine.sas
@li mf_getschema.sas
@li mp_getconstraints.sas
@version 9.3
@author 4GL Apps Ltd
**/
%macro mpe_refreshtables(lib,ds=#all
,etlsource=0
);
/* a default of 0 rather than a %mf_uid() expression: a default is
resolved when the macro is compiled, so it would be one value for the
whole session and the audit folder below it would collide on the
second call - as it did between the refresh steps of one run */
%if "&etlsource"="0" %then %let etlsource=%mf_getuniquename(prefix=refreshtables);
%let lib=%upcase(&lib);
%let ds=%upcase(&ds);
%local engine; %let engine=%mf_getengine(&lib);
%local schema; %let schema=%mf_getschema(&lib);
%put running &sysmacroname &lib(&engine &schema) for &ds;
proc sql;
create table cols as
select libname as libref
,upcase(memname) as dsn
,memtype
,upcase(name) as name
,type
,length
,varnum
,label
,format
,idxusage
,notnull
from dictionary.columns
where upcase(libname)="&lib"
%if &ds ne #ALL %then %do;
and upcase(memname)="&ds"
%end;
;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&_program
,msg=%str(syscc=&syscc afer &lib cols extraction)
)
%if &engine=SQLSVR %then %do;
proc sql;
connect using &lib;
create table work.indexes as
select * from connection to &lib(
select
s.name as SchemaName,
t.name as memname,
tc.name as name,
ic.key_ordinal as KeyOrderNr
from
sys.schemas s
inner join sys.tables t on s.schema_id=t.schema_id
inner join sys.indexes i on t.object_id=i.object_id
inner join sys.index_columns ic on i.object_id=ic.object_id
and i.index_id=ic.index_id
inner join sys.columns tc on ic.object_id=tc.object_id
and ic.column_id=tc.column_id
where i.is_primary_key=1
and s.name=%str(%')&schema%str(%')
order by t.name, ic.key_ordinal ;
);disconnect from &lib;
create table finalcols as
select a.*
,case when b.name is not null then 1 else 0 end as pk_ind
from work.cols a
left join work.indexes b
on a.dsn=b.memname
and upcase(a.name)=upcase(b.name)
order by libref,dsn;
%end;
%else %do;
%local dsn;
%if &ds = #ALL %then %let dsn=;
%mp_getconstraints(lib=&lib.,ds=&dsn,outds=work.constraints)
/* extract cols that are clearly primary keys */
proc sql;
create table work.pk4sure as
select libref
,table_name
,constraint_name
,constraint_order
,column_name as name
from work.constraints
where constraint_type='PRIMARY'
order by 1,2,3,4;
/* extract unique constraints where every col is also NOT NULL */
proc sql;
create table work.sum as
select a.libref
,a.table_name
,a.constraint_name
,count(a.column_name) as unq_cnt
,count(b.column_name) as nul_cnt
from work.constraints(where=(constraint_type ='UNIQUE')) a
left join work.constraints(where=(constraint_type ='NOT NULL')) b
on a.libref=b.libref
and a.table_name=b.table_name
and a.column_name=b.column_name
group by 1,2,3
having unq_cnt=nul_cnt;
/* extract cols from the relevant unique constraints */
create table work.pkdefault as
select a.libref
,a.table_name
,a.constraint_name
,b.constraint_order
,b.column_name as name
from work.sum a
left join work.constraints(where=(constraint_type ='UNIQUE')) b
on a.libref=b.libref
and a.table_name=b.table_name
and a.constraint_name=b.constraint_name
order by 1,2,3,4;
/* extract cols from the relevant unique INDEXES */
create table work.pkfromindex as
select libname as libref
,memname as table_name
,indxname as constraint_name
,indxpos as constraint_order
,name
from dictionary.indexes
where nomiss='yes' and unique='yes' and upcase(libname)="&lib"
%if &ds ne #ALL %then %do;
and upcase(memname)="&ds"
%end;
order by 1,2,3,4;
/* create one table */
data work.finalpks;
set pkdefault pk4sure pkfromindex;
pk_ind=1;
/* if there are multiple unique constraints, take the first */
by libref table_name constraint_name;
retain keepme;
if first.table_name then keepme=1;
if first.constraint_name and not first.table_name then keepme=0;
if keepme=1;
run;
/* join back to starting table */
proc sql;
create table finalcols as
select a.*
,b.constraint_order
,case when b.pk_ind=1 then 1 else 0 end as pk_ind
from work.cols a
left join work.finalpks b
on a.libref=b.libref
and a.dsn=b.table_name
and upcase(a.name)=upcase(b.name)
order by libref,dsn,constraint_order;
%end;
%mp_abort(iftrue= (&syscc ne 0)
,mac=&_program
,msg=%str(syscc=&syscc afer &lib indexes extraction)
)
/* load columns */
%bitemporal_dataloader(base_lib=&mpelib
,base_dsn=mpe_datacatalog_vars
,append_dsn=finalcols
,PK=LIBREF DSN NAME
,etlsource=&etlsource
,loadtype=TXTEMPORAL
,tech_from=TX_FROM
,tech_to=TX_TO
%if &ds ne #ALL %then %do;
,close_vars=LIBREF DSN
%end;
,dclib=&mpelib
)
/* prepare tables */
proc sql;
create table work.tabs as select
libname as libref
,upcase(memname) as dsn
,memtype
%if %mf_existfeature(DBMS_MEMTYPE)=1 %then %do;
,dbms_memtype
%end;
%else %do;
,'n/a' as dbms_memtype format=$32.
%end;
,typemem
,memlabel
,nvar
,compress
from dictionary.tables
where upcase(libname)="&lib"
%if &ds ne #ALL %then %do;
and upcase(memname)="&ds"
%end;
;
data tabs2;
set finalcols;
length pk_fields $512;
retain pk_fields;
by libref dsn;
if first.dsn then pk_fields='';
if pk_ind=1 then pk_fields=catx(' ',pk_fields,name);
if last.dsn then output;
run;
proc sql;
create table work.finaltabs as
select a.libref
,a.dsn
,a.memtype
,a.dbms_memtype
,a.typemem
,a.memlabel
,a.nvar
,a.compress
,b.pk_fields
from work.tabs a
left join work.tabs2 b
on a.libref=b.libref
and a.dsn=b.dsn;
%bitemporal_dataloader(base_lib=&mpelib
,base_dsn=mpe_datacatalog_tabs
,append_dsn=finaltabs
,PK=LIBREF DSN
,etlsource=&etlsource
,loadtype=TXTEMPORAL
,tech_from=TX_FROM
,tech_to=TX_TO
,dclib=&mpelib
%if &ds ne #ALL %then %do;
,close_vars=LIBREF
%end;
)
/* prepare table frequently changing attributes */
proc sql;
%if &engine=SQLSVR %then %do;
connect using &lib;
create table work.attrs as select * from connection to &lib(
SELECT SCHEMA_NAME(schema_id) as 'schema', name, create_date, modify_date
FROM sys.tables ;
);
create table work.nobs as select * from connection to &lib(
SELECT SCHEMA_NAME(A.schema_id) AS 'schema'
,A.Name, AVG(B.rows) AS 'RowCount'
FROM sys.objects A
INNER JOIN sys.partitions B ON A.object_id = B.object_id
WHERE A.type = 'U'
GROUP BY A.schema_id, A.Name
);
disconnect from &lib;
create table statustabs as select
a.libref
,a.dsn
,b.create_date as crdate
,b.modify_date as modate
,. as filesize
,c.RowCount as nobs
from work.tabs a
left join work.attrs(where=(schema="&schema")) b
on upcase(a.dsn)=upcase(b.name)
left join work.nobs(where=(schema="&schema")) c
on upcase(a.dsn)=upcase(c.name);
%end;
%else %do;
create table statustabs as select
libname as libref
,upcase(memname) as dsn
,crdate
,modate
,filesize
,nobs
from dictionary.tables
where upcase(libname)="&lib"
%if &ds ne #ALL %then %do;
and upcase(memname)="&ds"
%end;
;
%end;
%bitemporal_dataloader(base_lib=&mpelib
,base_dsn=mpe_datastatus_tabs
,append_dsn=statustabs
,PK=LIBREF DSN
,etlsource=&etlsource
,loadtype=TXTEMPORAL
,tech_from=TX_FROM
,tech_to=TX_TO
,dclib=&mpelib
%if &ds ne #ALL %then %do;
,close_vars=LIBREF
%end;
)
%if &ds = #ALL %then %do;
proc sql;
create table work.sumcat as
select libname as libref,
count(distinct memname) as catalog_cnt
from dictionary.catalogs
where upcase(libname)="&lib"
group by 1;
create table work.sumdsn as select
libref
,sum(filesize) as libsize
,count(*) as table_cnt
from statustabs
group by 1;
create table work.libs as
select libref from work.sumcat
union
select libref from work.sumdsn;
create table work.statuslibs as
select a.libref,
b.libsize,
b.table_cnt,
c.catalog_cnt
from work.libs a
left join work.sumdsn b
on a.libref=b.libref
left join work.sumcat c
on a.libref=c.libref;
%bitemporal_dataloader(base_lib=&mpelib
,base_dsn=mpe_datastatus_libs
,append_dsn=statuslibs
,PK=LIBREF
,etlsource=&etlsource
,loadtype=TXTEMPORAL
,tech_from=TX_FROM
,tech_to=TX_TO
,dclib=&mpelib
)
%end;
%mend mpe_refreshtables;