Modelli comuni per il filtro delle righe e la maschera di colonna

Questa pagina descrive i pattern comuni per l'implementazione di politiche di filtro di riga e maschera di colonna ABAC.

Funzioni di mascheramento compatibili con il cast

Azure Databricks esegue automaticamente il cast dell'output della funzione di maschera in modo che corrisponda al tipo di dati della colonna di destinazione. Consulta Cast automatico dei tipi per le maschere di colonna.

I modelli seguenti consentono di progettare funzioni di mascheramento compatibili con il casting.

Restituire un tipo convertibile

Quando si maschera una colonna, restituire lo stesso tipo di dati o un tipo coerente con esso. Controllare i tipi di dati delle colonne a cui si applicano i criteri e verificare che ogni ramo della funzione restituisca un valore compatibile.

-- Succeeds: Masks a DOUBLE column, returns DOUBLE in every branch
CREATE FUNCTION mask_salary(salary DOUBLE, user_role STRING)
RETURNS DOUBLE
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN salary
  WHEN user_role = 'manager' THEN ROUND(salary / 1000) * 1000
  ELSE 0.0
END;

-- Fails: 'CONFIDENTIAL' cannot be cast to a DOUBLE column type
CREATE FUNCTION mask_salary_as_text(salary DOUBLE, user_role STRING)
RETURNS STRING
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN CAST(salary AS STRING)
  ELSE 'CONFIDENTIAL'
END;

Evitare l'overflow numerico

Quando una funzione maschera accetta e restituisce un tipo numerico più ampio rispetto alla colonna di destinazione, il risultato viene automaticamente convertito al tipo della colonna. Se il valore restituito supera l'intervallo del tipo più stretto, il cast provoca un overflow e la query fallisce durante l'esecuzione.

-- The target column is TINYINT (max 127). The input is upcast to BIGINT
-- for the function. Adding 1000 produces a BIGINT result that overflows
-- when cast back to TINYINT.
CREATE FUNCTION mask_score(score BIGINT)
RETURNS BIGINT
RETURN score + 1000;

Usare VARIANT per più tipi di colonna

Vedi Mascherare più tipi di colonne con una sola funzione.

Compatibilità del cast di test

Testare le funzioni di mascheramento con modelli di dati diversi.

SELECT CAST(mask_salary(salary, 'admin') AS DOUBLE) FROM employees;
SELECT CAST(mask_salary(salary, 'manager') AS DOUBLE) FROM employees;
SELECT CAST(mask_salary(salary, 'viewer') AS DOUBLE) FROM employees;

Mascherare più tipi di colonne con una singola funzione

Un’unica UDF di mascheramento che accetta e restituisce un VARIANT può mascherare colonne di molti tipi di dati, riducendo così il numero di UDF e criteri che devi gestire. Azure Databricks esegue il cast del valore della colonna in VARIANT prima che la funzione venga eseguita, quindi riconverte il valore restituito nel tipo della colonna in base alle regole ANSI SQL.

All'interno della funzione, usa schema_of_variant() per ispezionare il valore e gestire il flusso in base al suo tipo. In ogni ramo restituisci un valore convertibile nel tipo della colonna di destinazione.

Maschera più tipi numerici

La maschera VARIANT più semplice restituisce una singola costante che Azure Databricks converte nel tipo di dati di ciascuna colonna. Una policy che utilizza questa funzione può mascherare INT, DOUBLE, e DECIMAL colonne, senza una funzione separata per ogni precisione:

CREATE FUNCTION mask_numeric(val VARIANT)
RETURNS VARIANT
DETERMINISTIC
RETURN 0::VARIANT;

Per variare il valore mascherato in base al tipo, usa schema_of_variant() per effettuare una diramazione e restituisci un valore appropriato per ogni tipo:

CREATE FUNCTION flexible_mask(data VARIANT)
RETURNS VARIANT
RETURN CASE
  WHEN schema_of_variant(data) = 'BIGINT' THEN 0::VARIANT
  WHEN schema_of_variant(data) = 'DATE' THEN DATE'1970-01-01'::VARIANT
  WHEN schema_of_variant(data) = 'DOUBLE' THEN 0.00::VARIANT
  ELSE NULL::VARIANT
END;

I tipi interi vengono estesi a VARIANT in un BIGINT, quindi eseguire il branching su INT anziché su TINYINT o BIGINT.

Mascherare le colonne STRUCT, ARRAY e MAP

L'approccio VARIANT maschera anche colonne di tipo complesso, estendendo l'esempio numerico sopra ai dati annidati. Una colonna STRUCT, ARRAY o MAP viene passata alla funzione come VARIANT e Azure Databricks riconverte il valore restituito dalla funzione nel tipo dichiarato della colonna. Il valore restituito deve avere lo stesso schema dell'input, altrimenti il cast non riesce e la query genera un errore.

Annotazioni

STRUCT le colonne sono supportate in Databricks Runtime 18.1 e versioni successive. Le colonne ARRAY e MAP sono supportate a partire da Databricks Runtime 19. Questo casting funziona solo all'interno delle maschere di colonna ABAC e dei criteri di filtro delle righe, non nel linguaggio SQL in generale né nelle maschere generali a livello di tabella.

Il seguente esempio di funzione maschera tipi specifici STRUCT, ARRAY, e MAP . Aggiungi un WHEN branch per ogni schema che devi mascherare:

CREATE OR REPLACE FUNCTION generic_mask(val VARIANT)
RETURNS VARIANT
RETURN CASE
  -- STRUCT<id: INT, ssn: STRING>: keep id, redact ssn
  WHEN schema_of_variant(val) = 'OBJECT<id: BIGINT, ssn: STRING>' THEN
    to_variant_object(named_struct('id', val:id, 'ssn', 'xxx-xx-xxxx'))
  -- ARRAY<STRING>: return a single redacted element
  WHEN schema_of_variant(val) = 'ARRAY<STRING>' THEN
    to_variant_object(array('redacted'))
  -- MAP<STRING, STRING>: redact every value
  WHEN schema_of_variant(val) = 'OBJECT<key1: STRING, key2: STRING>' THEN
    to_variant_object(map('key1', 'redacted', 'key2', 'redacted'))
  ELSE NULL::VARIANT
END;

Ogni WHEN ramo fa due cose, descritte nelle seguenti sezioni:

  1. Identifica lo schema VARIANT. Ogni schema VARIANT richiede una propria logica di mascheramento, quindi la funzione si ramifica in base alla stringa dello schema restituita da schema_of_variant(val) per applicare il mascheramento corretto a ciascuno.
  2. Ricostruisci il valore mascherato. Crea un valore redatto del tipo della colonna e racchiudilo in to_variant_object() per restituire un VARIANT.

Un valore il cui schema non corrisponde a nessun ramo passa a ELSE.

Identificare lo schema VARIANT

Confronta con lo schema del valore VARIANT, non con il tipo dichiarato della colonna: il cast a VARIANT normalizza i dati, quindi lo schema può differire dalla definizione della colonna. I seguenti esempi mostrano casi comuni:

Tipo di colonna Risultato schema_of_variant() Notes
ARRAY<STRING> ARRAY<STRING>
ARRAY<INT> ARRAY<BIGINT> I tipi interi si allargano a BIGINT.
STRUCT<id: INT, name: STRING> OBJECT<id: BIGINT, name: STRING> STRUCT e MAP diventano entrambe colonne OBJECT.
STRUCT<name: STRING, id: INT> OBJECT<id: BIGINT, name: STRING> I campi sono ordinati alfabeticamente per chiave, non per ordine di dichiarazione.
ARRAY<STRUCT<id: INT, name: STRING>> ARRAY<OBJECT<id: BIGINT, name: STRING>>
MAP<STRING, STRING> Sentenza {a: 'x'} OBJECT<a: STRING> Varia a seconda della riga: le chiavi di ogni riga determinano lo schema.
MAP<STRING, STRING> Sentenza {a: 'x', b: 'y'} OBJECT<a: STRING, b: STRING> Una riga diversa della stessa colonna produce uno schema diverso.
MAP<STRING, STRING> Sentenza {a: null} OBJECT<a: VOID> Un valore nullo diventa VOID.
MAP<STRING, STRUCT<id: INT, name: STRING>> Sentenza {a: ...} OBJECT<a: OBJECT<id: BIGINT, name: STRING>, ...>

Ricostruisci il valore mascherato

Per ogni schema che corrispondi, costruisci un valore oscurato con la stessa struttura, poi lo avvolgi in to_variant_object():

  • Usalo named_struct() per ricostruire un STRUCT, mantenendo i campi che vuoi e sostituendo il resto.
  • Usare array() per ricostruire un ARRAY.
  • Usa map() per ricostruire un MAP con valori oscurati.

Azure Databricks riconverte il valore restituito VARIANT nel tipo dichiarato della colonna, quindi ogni valore ricostruito deve essere convertibile in tale tipo.

Testa una maschera VARIANT

Prima di associare una funzione a una policy, puoi testarla in una query semplice per confermare l'output mascherato. La seguente funzione maschera una ARRAY<STRUCT<id: BIGINT, value: FLOAT>> colonna e genera un errore per qualsiasi altro schema:

CREATE OR REPLACE FUNCTION mask_points(v VARIANT)
RETURNS VARIANT
RETURN CASE
  WHEN schema_of_variant(v) = 'ARRAY<OBJECT<id: BIGINT, value: FLOAT>>' THEN
    to_variant_object(array(named_struct('id', 1, 'value', 2.1)))
  ELSE raise_error('Unexpected VARIANT schema: ' || schema_of_variant(v))
END;

Converti la colonna con to_variant_object(), applica la funzione di mascheramento e usa variant_get() per riportare il mascherato VARIANT nel tipo della colonna. Questo rispecchia ciò che la politica fa in tempo reale:

SELECT variant_get(mask_points(to_variant_object(points)), '$', typeof(points)) AS masked
FROM my_catalog.my_schema.my_table;

Limitations

  • Una colonna complessa che contiene TIME, VARIANT, GEOGRAPHY, GEOMETRY o VARCHAR non può essere mascherata con CHAR.
  • MAP Le chiavi devono essere STRING. Le colonne digitate MAP<INT, ...>, MAP<DATE, ...>, e così via non vengono convertite.

Impedire l'accesso fino a quando non vengono contrassegnate le colonne sensibili

Un modello di governance comune consiste nel controllare l'accesso in base al fatto che i dati siano stati classificati. È possibile implementare questa impostazione con un tag e criteri restrittivi predefiniti che applicano livelli di protezione diversi a seconda dello stato di classificazione.

  1. Applicare un tag come classification : unverified a tutti i nuovi oggetti per impostazione predefinita, tramite l'automazione o l'ereditarietà dei tag applicando il tag a livello di catalogo o schema, in modo che tutte le nuove tabelle aggiunte al catalogo o allo schema ereditino automaticamente il tag.
  2. Creare criteri di filtro di riga che bloccano l'accesso alle tabelle con tag classification : unverified.
  3. Creare un criterio di maschera di colonna che maschera le colonne sensibili nelle tabelle in cui il classification : unverified tag non è più presente.
  4. Quando un amministratore dei dati completa la classificazione, aggiorna il tag. I criteri di blocco non corrispondono più e il criterio di maschera diventa effettivo.
-- Block access to unverified tables for all non-admin users
CREATE FUNCTION catalog.schema.block_all() RETURNS BOOLEAN
  RETURN FALSE;

CREATE POLICY block_unverified
ON CATALOG my_catalog
ROW FILTER catalog.schema.block_all
TO `account users` EXCEPT `data_admins`
FOR TABLES
WHEN has_tag_value('classification', 'unverified');

Per proteggere i dati sensibili dopo la classificazione, definire un criterio di maschera di colonna che diventa effettivo quando il classification : unverified tag non è più presente:

CREATE FUNCTION catalog.schema.mask_pii(val STRING)
RETURNS STRING
RETURN '***';

CREATE POLICY mask_reviewed_pii
ON CATALOG my_catalog
COLUMN MASK catalog.schema.mask_pii
TO `account users`
EXCEPT `data_admins`
FOR TABLES
WHEN NOT has_tag_value('classification', 'unverified')
MATCH COLUMNS (has_tag_value('pii', 'name') OR has_tag_value('pii', 'address')) AS m
ON COLUMN m;

Reveal parziale senza espressione regolare

Rivelare parte di un valore sensibile usando operazioni stringa anziché regex. La maschera basata su regex analizza l'intero valore per ogni riga, che è costosa nei campi di testo di grandi dimensioni(vedere Evitare la maschera regex in campi di testo di grandi dimensioni).

CREATE FUNCTION mask_ssn(ssn STRING, show_last INT) RETURNS STRING
DETERMINISTIC
  RETURN CONCAT('***-**-', RIGHT(ssn, show_last));

Hashing coerente (pseudonimizzazione deterministica)

L'hashing coerente (detto anche pseudonimo deterministico) sostituisce i dati sensibili con un valore hash identico in più tabelle. Contrassegnare una funzione come DETERMINISTIC indica al motore che la funzione restituisce sempre lo stesso risultato per lo stesso input, che consente di ottimizzare la query. Vedere Usare espressioni deterministiche e sicure per gli errori.

La funzione seguente esegue costantemente l'hashing di un valore stringa e usa un version parametro per supportare la rotazione delle chiavi. Incrementare il numero version tramite la clausola USING COLUMNS della politica per generare nuovi hash senza interrompere i dati storici che hanno utilizzato la versione precedente. La funzione concatena il valore originale con il numero di versione prima dell'hashing, quindi lo stesso input con la stessa versione produce sempre lo stesso hash.

CREATE FUNCTION pseudonymize(val STRING, version INT) RETURNS STRING
DETERMINISTIC
  RETURN SHA2(CONCAT(val, CAST(version AS STRING)), 256);

Maschera una colonna in base agli attributi dell'utente che interroga

Importante

Gli attributi di identità nelle policy ABAC sono in Beta. Per utilizzarli, un amministratore dell'account deve:

Un criterio di mascheramento di colonna può utilizzare gli attributi di identità dell’utente che esegue la query per mascherare dati sensibili senza richiedere gruppi dedicati. Ad esempio, può mantenere i dati non mascherati per gli utenti con department = HR e mascherarli per tutti gli altri.

Questi pattern richiedono attributi di identità forniti ai tuoi utenti dal tuo fornitore di identità, e le funzioni si comportano in modo diverso dalle condizioni solo tag, influenzando il modo in cui scrivi la policy. Prima di usarli, esamina le funzioni di attributo identità e gli attributi identità.

Importante

Le funzioni restituiscono false quando l'utente non ha alcun valore per l'attributo o quando la chiave dell'attributo non esiste. Scrivi la condizione in modo che questo false risultato limiti l'accesso invece di concederlo. Annulla la corrispondenza con NOT così che la maschera valga a meno che l'attributo non corrisponda. Ad esempio, WHEN NOT has_identity_attribute_value('department', 'HR') maschera la colonna per tutti tranne gli utenti il cui dipartimento è HR, e poiché un valore mancante è anche false, gli utenti senza attributo dipartimento sono mascherati. Evitare il contrario: una condizione che maschera solo quando l'attributo coincide lascia non mascherati gli utenti privi di un valore per l'attributo.

Per il comportamento di valutazione, vedi Condizioni dell'attributo di identità. Per le limitazioni, vedi Attributi di identità nelle condizioni di policy.

Abbina un valore fisso

Maschera ssn per tutti coloro il cui reparto non è HR:

CREATE FUNCTION hr_catalog.people.mask_ssn(s STRING) RETURNS STRING RETURN '***-**-****';

CREATE OR REPLACE POLICY mask_ssn_non_hr
ON SCHEMA hr_catalog.people
COLUMN MASK hr_catalog.people.mask_ssn
TO `account users`
FOR TABLES
WHEN NOT has_identity_attribute_value('department', 'HR')
MATCH COLUMNS has_tag_value('pii', 'ssn') AS ssn_col
ON COLUMN ssn_col;

In questo esempio, un utente il cui dipartimento è HR vede valori reali. Un utente in qualsiasi altro dipartimento, e un utente senza attributo di dipartimento, vedono entrambi la maschera.

Match contro un tag governato

L'esempio precedente indica un valore di attributo specifico (HR) nella policy, quindi coprire diversi dipartimenti significherebbe scrivere una policy separata per ciascuno. Per coprire tutti i dipartimenti con una sola politica, tagga ogni tabella con il dipartimento che ne è proprietario, poi confronta l'attributo dell'utente department che fa la query con quel tag. La colonna viene rivelata solo quando il reparto dell'utente corrisponde al valore della dept_tag tabella:

CREATE FUNCTION prod.sales.mask_ssn(s STRING) RETURNS STRING RETURN '***-**-****';

CREATE OR REPLACE POLICY mask_unless_dept_matches
ON SCHEMA prod.sales
COLUMN MASK prod.sales.mask_ssn
TO `account users`
FOR TABLES
WHEN NOT has_identity_attribute_tag_match('department', 'dept_tag')
MATCH COLUMNS has_tag_value('pii', 'ssn') AS ssn_col
ON COLUMN ssn_col;

Sia le chiavi sia i valori degli attributi distinguono tra maiuscole e minuscole e i valori vengono confrontati in modo esatto: Finance e finance non corrispondono.

Limitare l'accesso per agenti esterni che agiscono per conto di un utente

Importante

Gli attributi di contesto nelle policy ABAC sono in Beta. Per utilizzarli, un amministratore dell'account deve abilitare la funzionalità di anteprima Attributi di contesto UC ABAC dalla pagina Anteprime della console dell'account. Vedi Gestire le anteprime a livello di account.

Gli attributi di contesto possono essere usati per limitare l'accesso ai dati per richieste effettuate per conto di un utente tramite un'applicazione OAuth (autorizzazione utente-a macchina (U2M)). Se gli agenti sono connessi tramite OAuth, questa configurazione può essere usata per impedire loro di accedere ai dati quando agiscono per conto di un utente, anche se l'utente può comunque leggere i dati quando li interroga direttamente nello spazio di lavoro.

Qualsiasi accesso autenticato OAuth tramite Azure Databricks CLI, gli SDK o l'API di esecuzione delle istruzioni SQL si imposta request.is_on_behalf_of su 'true', anche quando l'utente sta interrogando manualmente. L'accesso autenticato con un token di accesso personale (PAT) non avviene. L'accesso a Genie non può essere acquisito tramite questo meccanismo.

Questi schemi utilizzano le funzioni di attributo contestuale. Per attributi e comportamenti disponibili, vedi Funzioni di attributo contestuale (Beta).

Connetti l'agente ad Azure Databricks

Per utilizzare attributi di contesto, collega l'agente usando un'applicazione OAuth personalizzata:

  1. Un amministratore account abilita l'anteprima degli attributi di contesto UC ABAC dalla console dell'account. Vedere Gestire le anteprime di Azure Databricks.
  2. Un amministratore dell'account registra un'applicazione OAuth personalizzata nella console dell'account e annota il suo ID client.
  3. Collega l'agente all'MCP gestito da Azure Databricks tramite quell'applicazione OAuth. Vedi Configurare un client OAuth personalizzato.

Un agente che utilizza il client integrato databricks-cli si autentica comunque su OAuth, quindi request.is_on_behalf_of legge 'true'. Tuttavia, non puoi distinguere le sue richieste dall'uso manuale della CLI, perché entrambe condividono l'ID databricks-cli client. Per governare un'applicazione specifica, registra un'applicazione OAuth personalizzata e collega l'agente tramite essa.

Warning

Assicurati che un agente non possa accedere ai dati tramite un percorso che la tua polizza non copre:

  • Se limiti l'accesso in base a request.is_on_behalf_of, assicurati che l'agente non possa autenticarsi con un PAT. Un PAT non imposta request.is_on_behalf_of su 'true', quindi una condizione su tale attributo non lo limita.
  • Se limiti l'accesso in base a request.client_id, assicurati che l'agente non possa connettersi tramite un cliente che la tua condizione non copre, come il cliente generico databricks-cli .

Maschera una colonna per le richieste per conto di

Maschera ssn per le richieste eseguite per conto di un utente, ad esempio da un agente che opera tramite un'applicazione OAuth registrata, lasciandolo non mascherato per le query dirette:

CREATE FUNCTION hr_catalog.people.mask_ssn(s STRING) RETURNS STRING RETURN '***-**-****';

CREATE OR REPLACE POLICY mask_ssn_for_agents
ON SCHEMA hr_catalog.people
COLUMN MASK hr_catalog.people.mask_ssn
TO `account users`
FOR TABLES
WHEN has_context_attribute_value('request.is_on_behalf_of', 'true')
MATCH COLUMNS has_tag_value('pii', 'ssn') AS ssn_col
ON COLUMN ssn_col;

In questo esempio, una query diretta restituisce valori reali e una richiesta per conto di un altro utente mostra i valori mascherati. Usando la CLI e l'API di esecuzione delle istruzioni SQL, request.is_on_behalf_of legge anche 'true', quindi questa policy maschera la colonna anche per quelle richieste. Per selezionare invece come destinazione un'applicazione specifica, fai corrispondere request.client_id all'ID client di tale applicazione.

Limita una colonna a una domanda approvata

Nascondi ssn per ogni richiesta esterna tranne quelle provenienti dalla tua applicazione approvata, identificata dal relativo ID client OAuth:

CREATE OR REPLACE POLICY mask_ssn_unapproved_apps
ON SCHEMA hr_catalog.people
COLUMN MASK hr_catalog.people.mask_ssn
TO `account users`
FOR TABLES
WHEN NOT has_context_attribute_value('request.client_id', '<your-app-client-id>')
MATCH COLUMNS has_tag_value('pii', 'ssn') AS ssn_col
ON COLUMN ssn_col;

Per vedere quale applicazione ha effettuato una richiesta, ispeziona il identity_metadata.acting_resource campo nei log di audit.

Filtro di righe con predicati di sola colonna

Filtrare le righe usando una logica booleana semplice che fa riferimento solo alle colonne della tabella. I predicati di sola colonna abilitano il pushdown del predicato, che consente al motore di ignorare i dati irrilevanti durante le analisi (vedere Informazioni sul pushdown del predicato nelle tabelle protette).

CREATE FUNCTION filter_by_region(region STRING, allowed STRING)
RETURNS BOOLEAN
DETERMINISTIC
  RETURN array_contains(split(allowed, ','), lower(region));

Usare con un criterio che passa le aree consentite come costante:

CREATE POLICY regional_access
ON CATALOG analytics
ROW FILTER filter_by_region
TO 'emea_team'
FOR TABLES
MATCH COLUMNS has_tag('region') AS rgn
USING COLUMNS (rgn, 'emea,apac');

Filtro delle righe tra più colonne correlate

Quando una tabella include più colonne che rappresentano attributi correlati, come ship_to_country e bill_to_country, è possibile abbinarle a condizioni di tag separate e passare entrambe a una singola funzione definita dall'utente. In questo modo si evita di creare criteri separati per ogni colonna. Un criterio può includere fino a tre espressioni di colonna nella MATCH COLUMNS clausola (vedere Quote dei criteri).

CREATE FUNCTION filter_by_countries(ship_country STRING, bill_country STRING, allowed STRING)
RETURNS BOOLEAN
DETERMINISTIC
  RETURN array_contains(split(allowed, ','), lower(ship_country))
      OR array_contains(split(allowed, ','), lower(bill_country));

CREATE POLICY regional_orders
ON SCHEMA prod.orders
ROW FILTER filter_by_countries
TO analysts
FOR TABLES
WHEN has_tag_value('sensitivity', 'high')
MATCH COLUMNS
  has_tag('ship_country') AS ship,
  has_tag('bill_country') AS bill
USING COLUMNS (ship, bill, 'us,ca,mx');

Un analista vede solo gli ordini in cui il paese di spedizione o fatturazione è incluso nell'elenco consentito.

Tabelle di ricerca nelle UDF (Funzioni Definite dall'Utente) dei criteri ABAC

Quando le regole di accesso variano per utente e non possono essere espresse solo tramite le clausole dei TO/EXCEPT criteri, è possibile controllare i diritti di accesso rispetto a una tabella di ricerca di piccole dimensioni. Usare TO/EXCEPT quando possibile, poiché è l'approccio preferito per le entità di destinazione (vedere Approccio per le entità di destinazione). Mantenere la tabella di ricerca piccola in modo che Optimizer converta la sottoquery in un hash join broadcast (vedere Mantenere le tabelle di ricerca di piccole dimensioni).

CREATE TABLE access_rules (
  principal VARCHAR(255),
  priority VARCHAR(64)
);

INSERT INTO access_rules VALUES
  ('alice@company.com', '1-URGENT'),
  ('alice@company.com', '2-HIGH'),
  ('bob@company.com', '1-URGENT');

CREATE FUNCTION priority_allowed(o_priority STRING) RETURNS BOOLEAN
RETURN EXISTS (
  SELECT 1 FROM access_rules
  WHERE principal = session_user() AND priority = o_priority
);

CREATE POLICY priority_filter
ON CATALOG operations
ROW FILTER priority_allowed
TO `account users`
FOR TABLES
MATCH COLUMNS has_tag('priority') AS pri
USING COLUMNS (pri);