Em primeiro lugar, acho que a maneira de abordar "administradores desonestos" é com uma combinação da trilha de auditoria da Oracle e Cofre de banco de dados recursos.
Dito isto, aqui está o que eu poderia tentar:
1) Crie uma função de agregação ODCI personalizada para calcular um hash de várias linhas como uma agregação.2) Crie um
VIRTUAL NOT NULL
coluna na tabela que era um hash SHA de todas as colunas na tabela - ou todas as que você se preocupa em proteger. Você manteria isso o tempo todo - basicamente trocando alguns insert/update/delete
desempenho em troca para poder calcular hashes mais rapidamente.3) Crie um índice não exclusivo nessa coluna virtual4) SELECT my_aggregate_hash_function(virtual_hash_column) FROM my_table
para obter os resultados. Aqui está o código:
Crie uma função agregada para calcular um hash SHA em várias linhas
CREATE OR REPLACE TYPE matt_hash_aggregate_impl AS OBJECT
(
hash_value RAW(32000),
CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT,
-- Called to initialize a new aggregation context
-- For analytic functions, the aggregation context of the *previous* window is passed in, so we only need to adjust as needed instead
-- of creating the new aggregation context from scratch
STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER,
-- Called when a new data point is added to an aggregation context
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER,
-- Called to return the computed aggragate from an aggregation context
MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER,
-- Called to merge to two aggregation contexts into one (e.g., merging results of parallel slaves)
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER,
-- ODCIAggregateDelete
MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER
);
/
CREATE OR REPLACE TYPE BODY matt_hash_aggregate_impl IS
CONSTRUCTOR FUNCTION matt_hash_aggregate_impl(SELF IN OUT NOCOPY matt_hash_aggregate_impl ) RETURN SELF AS RESULT IS
BEGIN
SELF.hash_value := null;
RETURN;
END;
STATIC FUNCTION ODCIAggregateInitialize (sctx IN OUT matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
sctx := matt_hash_aggregate_impl ();
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateIterate (self IN OUT matt_hash_aggregate_impl, value IN raw ) RETURN NUMBER IS
BEGIN
IF self.hash_value IS NULL THEN
self.hash_value := dbms_crypto.hash(value, dbms_crypto.hash_sh1);
ELSE
self.hash_value := dbms_crypto.hash(self.hash_value || value, dbms_crypto.hash_sh1);
END IF;
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateTerminate (self IN matt_hash_aggregate_impl, returnValue OUT raw, flags IN NUMBER) RETURN NUMBER IS
BEGIN
returnValue := dbms_crypto.hash(self.hash_value,dbms_crypto.hash_sh1);
RETURN ODCIConst.Success;
END;
MEMBER FUNCTION ODCIAggregateMerge (self IN OUT matt_hash_aggregate_impl, ctx2 IN matt_hash_aggregate_impl) RETURN NUMBER IS
BEGIN
self.hash_value := dbms_crypto.hash(self.hash_value || ctx2.hash_value, dbms_crypto.hash_sh1);
RETURN ODCIConst.Success;
END;
-- ODCIAggregateDelete
MEMBER FUNCTION ODCIAggregateDelete(self IN OUT matt_hash_aggregate_impl, value raw) RETURN NUMBER IS
BEGIN
raise_application_error(-20001, 'Invalid operation -- hash aggregate function does not support windowing!');
END;
END;
/
CREATE OR REPLACE FUNCTION matt_hash_aggregate ( input raw) RETURN raw
PARALLEL_ENABLE AGGREGATE USING matt_hash_aggregate_impl;
/
Crie uma tabela de teste para trabalhar (você pula isso, pois tem sua tabela real)
create table mattmsi as select * from mtl_system_items where rownum <= 200000;
Crie um hash de coluna virtual dos dados de cada linha. Certifique-se de que é NOT NULL
alter table mattmsi add compliance_hash generated always as ( dbms_crypto.hash(to_clob(inventory_item_id || segment1 || last_update_date || created_by || description), 3 /*dbms_crypto.hash_sh1*/) ) VIRTUAL not null ;
Cria um índice na coluna virtual; desta forma você pode calcular seu hash com uma varredura completa do índice estreito em vez de uma varredura completa da tabela de gordura
create index msi_compliance_hash_n1 on mattmsi (compliance_hash);
Junte tudo para calcular seu hash
SELECT matt_hash_aggregate(compliance_hash) from (select compliance_hash from mattmsi order by compliance_hash);
Alguns comentários:
- Acho importante usar um hash para calcular a agregação (em vez de simplesmente fazer um
SUM()
sobre os hashes em nível de linha, porque um invasor pode forjar a soma correta com muita facilidade. - Acho que você não pode (facilmente?) usar consulta paralela porque é importante que as linhas sejam alimentadas para a função de agregação em uma ordem consistente, caso contrário o valor do hash será alterado.