Assessor_2.0/server/postgres/migrations/001_initial_schema.sql

170 lines
7.4 KiB
SQL

CREATE TABLE IF NOT EXISTS assessor_repository_state (
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
revision bigint NOT NULL DEFAULT 0 CHECK (revision >= 0)
);
INSERT INTO assessor_repository_state (singleton, revision)
VALUES (true, 0)
ON CONFLICT (singleton) DO NOTHING;
CREATE TABLE assessor_projects (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
name text NOT NULL CHECK (btrim(name) <> ''),
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
attributes jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(attributes) = 'object'),
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object')
);
CREATE TABLE assessor_assessments (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
name text NOT NULL CHECK (btrim(name) <> ''),
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
UNIQUE (id, project_id)
);
CREATE TABLE assessor_scan_runs (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
assessment_id text NOT NULL REFERENCES assessor_assessments(id) ON DELETE CASCADE,
sequence integer NOT NULL CHECK (sequence >= 0),
imported_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
UNIQUE (assessment_id, sequence)
);
CREATE TABLE assessor_import_files (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
scan_run_id text NOT NULL REFERENCES assessor_scan_runs(id) ON DELETE CASCADE,
file_name text NOT NULL CHECK (btrim(file_name) <> ''),
format text NOT NULL CHECK (btrim(format) <> ''),
imported_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object')
);
CREATE TABLE assessor_source_tables (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
import_file_id text NOT NULL REFERENCES assessor_import_files(id) ON DELETE CASCADE,
table_index integer NOT NULL CHECK (table_index >= 0),
name text NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
UNIQUE (import_file_id, table_index)
);
CREATE TABLE assessor_finding_definitions (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
canonical_key text NOT NULL CHECK (btrim(canonical_key) <> ''),
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
search_document tsvector GENERATED ALWAYS AS (
to_tsvector(
'simple'::regconfig,
coalesce(data->>'canonicalKey', '') || ' ' ||
coalesce(data->>'title', '') || ' ' ||
coalesce(data->>'description', '') || ' ' ||
coalesce(data->>'category', '') || ' ' ||
coalesce(data->>'recommendation', '')
)
) STORED
);
CREATE UNIQUE INDEX assessor_finding_definitions_canonical_key_uq
ON assessor_finding_definitions (canonical_key);
CREATE INDEX assessor_finding_definitions_search_idx
ON assessor_finding_definitions USING gin (search_document);
CREATE TABLE assessor_finding_occurrences (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
definition_id text NOT NULL REFERENCES assessor_finding_definitions(id) ON DELETE RESTRICT,
scan_run_id text NOT NULL REFERENCES assessor_scan_runs(id) ON DELETE CASCADE,
import_file_id text NOT NULL REFERENCES assessor_import_files(id) ON DELETE CASCADE,
source_table_id text REFERENCES assessor_source_tables(id) ON DELETE SET NULL,
tool_id text NOT NULL,
severity text,
finding_status text,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
search_document tsvector GENERATED ALWAYS AS (
to_tsvector(
'simple'::regconfig,
coalesce(data #>> '{sourceIdentity,originalRuleId}', '') || ' ' ||
coalesce(data #>> '{sourceIdentity,canonicalRuleId}', '') || ' ' ||
coalesce(data #>> '{sourceIdentity,originalTitle}', '') || ' ' ||
coalesce(data #>> '{affectedAsset,normalizedValue}', '') || ' ' ||
data::text
)
) STORED
);
CREATE INDEX assessor_occurrences_scan_run_idx ON assessor_finding_occurrences (scan_run_id);
CREATE INDEX assessor_occurrences_definition_idx ON assessor_finding_occurrences (definition_id);
CREATE INDEX assessor_occurrences_filter_idx ON assessor_finding_occurrences (tool_id, severity, finding_status);
CREATE INDEX assessor_occurrences_search_idx ON assessor_finding_occurrences USING gin (search_document);
CREATE INDEX assessor_occurrences_data_idx ON assessor_finding_occurrences USING gin (data jsonb_path_ops);
CREATE TABLE assessor_evidence (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
occurrence_id text NOT NULL REFERENCES assessor_finding_occurrences(id) ON DELETE CASCADE,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object')
);
CREATE TABLE assessor_import_diagnostics (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
import_file_id text NOT NULL REFERENCES assessor_import_files(id) ON DELETE CASCADE,
source_table_id text REFERENCES assessor_source_tables(id) ON DELETE SET NULL,
severity text NOT NULL,
code text NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object')
);
CREATE TABLE assessor_reviews (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
target_kind text NOT NULL CHECK (target_kind IN ('definition', 'occurrence')),
definition_id text REFERENCES assessor_finding_definitions(id) ON DELETE CASCADE,
occurrence_id text REFERENCES assessor_finding_occurrences(id) ON DELETE CASCADE,
revision integer NOT NULL CHECK (revision >= 1),
updated_at timestamptz NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
CHECK (
(target_kind = 'definition' AND definition_id IS NOT NULL AND occurrence_id IS NULL)
OR
(target_kind = 'occurrence' AND occurrence_id IS NOT NULL AND definition_id IS NULL)
)
);
CREATE INDEX assessor_reviews_definition_idx ON assessor_reviews (definition_id) WHERE definition_id IS NOT NULL;
CREATE INDEX assessor_reviews_occurrence_idx ON assessor_reviews (occurrence_id) WHERE occurrence_id IS NOT NULL;
CREATE TABLE assessor_profile_registrations (
profile_id text NOT NULL CHECK (btrim(profile_id) <> ''),
profile_version text NOT NULL CHECK (btrim(profile_version) <> ''),
schema_version integer NOT NULL CHECK (schema_version >= 1),
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
PRIMARY KEY (profile_id, profile_version)
);
CREATE TABLE assessor_local_comparison_rules (
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
id text NOT NULL CHECK (btrim(id) <> ''),
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
PRIMARY KEY (project_id, id)
);
CREATE TABLE assessor_comparisons (
id text PRIMARY KEY CHECK (btrim(id) <> ''),
project_id text NOT NULL REFERENCES assessor_projects(id) ON DELETE CASCADE,
assessment_id text NOT NULL,
data jsonb NOT NULL CHECK (jsonb_typeof(data) = 'object'),
FOREIGN KEY (assessment_id, project_id)
REFERENCES assessor_assessments(id, project_id)
ON DELETE CASCADE
);
CREATE INDEX assessor_assessments_project_idx ON assessor_assessments (project_id);
CREATE INDEX assessor_scan_runs_assessment_idx ON assessor_scan_runs (assessment_id);
CREATE INDEX assessor_import_files_scan_run_idx ON assessor_import_files (scan_run_id);
CREATE INDEX assessor_source_tables_file_idx ON assessor_source_tables (import_file_id);
CREATE INDEX assessor_evidence_occurrence_idx ON assessor_evidence (occurrence_id);
CREATE INDEX assessor_diagnostics_file_idx ON assessor_import_diagnostics (import_file_id);
CREATE INDEX assessor_comparisons_assessment_idx ON assessor_comparisons (assessment_id);