fix(derived): refuse dependencies on inactive rows, and keep the RLS scope guard coherent
The graph now holds only effective active rows, and a declared dependency that resolves to an inactive row (existing, or one this submission deactivates) is rejected: the engine reads only active rows and aborts every read of the table on the unresolved reference. The derived-variable / RLS guard goes back to rejecting every scope. Exempting VIEW rules left them to fail a moment later in mp_filtercheck, which validates against the physical table where the column does not exist, so the pairing is refused up front with a message that says why. RLS batches now exclude delete-marked rows, matching the badloadtypes check, so a rule being removed is not validated against the derived configuration.
This commit is contained in:
1 parent
85c89ca2a4
commit
7799a300df
2 files changed
+56
-16
No files matched your search
@@ -154,7 +154,9 @@ run;
|
||||
/**
|
||||
* The graph has to hold for the resulting configuration: the incoming rows
|
||||
* plus the current rows of the same tables that this submission does not
|
||||
* replace.
|
||||
* replace. Only active rows are part of it - the engine reads nothing else
|
||||
* (mpe_derived.sas), so an inactive row can neither resolve a dependency nor
|
||||
* be resolved by one.
|
||||
*/
|
||||
proc sql;
|
||||
create table work.derived_rows as
|
||||
@@ -163,13 +165,12 @@ create table work.derived_rows as
|
||||
,strip(var_logic) as logic
|
||||
from work.staging_ds
|
||||
where upcase(_____DELETE__THIS__RECORD_____) ne 'YES'
|
||||
and active=1
|
||||
union all
|
||||
select libref, dsn, upcase(strip(var_nm)) as name
|
||||
,upcase(strip(var_deps)) as deps
|
||||
,strip(var_logic) as logic
|
||||
from &DC_LIBREF..mpe_derived
|
||||
/* only active rows are read at run time (mpe_derived.sas), so an inactive
|
||||
row must not enter the dependency graph */
|
||||
where &dc_dttmtfmt. lt tx_to
|
||||
and active=1
|
||||
and catx('.',libref,dsn,var_nm) not in
|
||||
@@ -188,6 +189,46 @@ data work.derived_dep;
|
||||
keep libref dsn name dep_nm;
|
||||
run;
|
||||
|
||||
/* a dependency on a row the engine will not read cannot resolve: it aborts
|
||||
every read of the table with "VAR_DEPS lists X, which is not a column of the
|
||||
table or a derived variable". Catch it here, including a row this submission
|
||||
deactivates, which the control table alone does not show yet. */
|
||||
proc sql;
|
||||
create table work.derived_baddep as
|
||||
select d.libref, d.dsn, d.name, d.dep_nm
|
||||
from work.derived_dep d
|
||||
inner join (
|
||||
select upcase(strip(libref)) as libref
|
||||
,upcase(strip(dsn)) as dsn
|
||||
,upcase(strip(var_nm)) as name
|
||||
from work.staging_ds
|
||||
where upcase(_____DELETE__THIS__RECORD_____) ne 'YES'
|
||||
and active ne 1
|
||||
union all
|
||||
select upcase(strip(libref)) as libref
|
||||
,upcase(strip(dsn)) as dsn
|
||||
,upcase(strip(var_nm)) as name
|
||||
from &DC_LIBREF..mpe_derived
|
||||
where &dc_dttmtfmt. lt tx_to
|
||||
and active ne 1
|
||||
) inactive
|
||||
on inactive.libref=d.libref
|
||||
and inactive.dsn=d.dsn
|
||||
and inactive.name=d.dep_nm;
|
||||
quit;
|
||||
|
||||
data _null_;
|
||||
set work.derived_baddep(obs=1);
|
||||
call symputx('errmsg'
|
||||
,cats('MPE_DERIVED: ',name,': VAR_DEPS lists ',dep_nm
|
||||
,', which is not active - the engine cannot resolve it'));
|
||||
run;
|
||||
|
||||
%mp_abort(iftrue= (0 < %mf_nobs(work.derived_baddep))
|
||||
,mac=mpe_derived_postedit
|
||||
,msg=%superq(errmsg)
|
||||
)
|
||||
|
||||
data work.derived_ref;
|
||||
set work.derived_rows;
|
||||
length l $4000 tok $32;
|
||||
@@ -273,9 +314,10 @@ run;
|
||||
)
|
||||
|
||||
/* the same invariant the RLS hook enforces from its side: a derived variable
|
||||
cannot be named by an EDIT or ALL row level security rule, because the
|
||||
columns are dropped before the security clause is evaluated. A VIEW rule is
|
||||
applied on the view the EDIT screen reads, where the column exists. */
|
||||
cannot be named by an active row level security rule of any scope. Only the
|
||||
VIEW scope could evaluate it (on the view the EDIT screen reads), but the RLS
|
||||
hook validates every rule against the physical table, where the column does
|
||||
not exist, so the pairing is refused up front rather than failing there. */
|
||||
proc sql;
|
||||
create table work.derived_rls as
|
||||
select d.libref, d.dsn, d.name
|
||||
@@ -285,7 +327,6 @@ create table work.derived_rls as
|
||||
and upcase(r.rls_table)=upcase(d.dsn)
|
||||
and upcase(r.rls_variable_nm)=upcase(d.name)
|
||||
where r.rls_active=1
|
||||
and upcase(r.rls_scope) in ('EDIT','ALL')
|
||||
and &dc_dttmtfmt lt r.tx_to;
|
||||
quit;
|
||||
|
||||
|
||||
@@ -55,13 +55,11 @@ run;
|
||||
) change the loadtype.)
|
||||
)
|
||||
|
||||
/* group and subgroup are not used for validation; scope is carried for the
|
||||
derived-variable check below */
|
||||
/* group and subgroup are not used for validation */
|
||||
proc sql;
|
||||
create table work.batches as
|
||||
select distinct upcase(rls_libref) as rls_libref,
|
||||
upcase(rls_table) as rls_table,
|
||||
upcase(rls_scope) as rls_scope,
|
||||
rls_group_logic as group_logic,
|
||||
rls_subgroup_logic as subgroup_logic,
|
||||
rls_subgroup_id as subgroup_id,
|
||||
@@ -70,6 +68,7 @@ create table work.batches as
|
||||
rls_raw_value as raw_value
|
||||
from work.staging_ds
|
||||
where rls_active=1
|
||||
and upcase(_____DELETE__THIS__RECORD_____) ne 'YES'
|
||||
order by rls_libref, rls_table;
|
||||
|
||||
%let cnt=0;
|
||||
@@ -84,10 +83,11 @@ data _null_;
|
||||
end;
|
||||
run;
|
||||
|
||||
/* derived variables exist only in the view the EDIT screen reads - an EDIT or
|
||||
ALL rule on one cannot be evaluated against the submitted data, so reject it
|
||||
here rather than let the submit fail later with a variable-not-found error.
|
||||
A VIEW rule is applied on that view, where the column exists. */
|
||||
/* derived variables exist only in the view the EDIT screen reads - a rule on
|
||||
one cannot be validated against the physical table, so reject it here rather
|
||||
than let mp_filtercheck fail with a variable-not-found error. Applies to
|
||||
every scope: the VIEW scope could evaluate the column, but this hook checks
|
||||
rules against the table they are submitted for. */
|
||||
proc sql;
|
||||
create table work.rls_derived as
|
||||
select b.rls_libref, b.rls_table, b.variable_nm
|
||||
@@ -96,8 +96,7 @@ create table work.rls_derived as
|
||||
on upcase(d.libref)=b.rls_libref
|
||||
and upcase(d.dsn)=b.rls_table
|
||||
and upcase(d.var_nm)=upcase(b.variable_nm)
|
||||
where b.rls_scope in ('EDIT','ALL')
|
||||
and &dc_dttmtfmt lt d.tx_to
|
||||
where &dc_dttmtfmt lt d.tx_to
|
||||
and d.active=1;
|
||||
quit;
|
||||
|
||||
|
||||
Reference in new issue
Block a user