Files
EvoBGP/migrations/postgres/000001_init.up.sql
T

221 lines
9.5 KiB
SQL

-- EvoBGP initial schema (PostgreSQL). See plan §5.
-- UUIDs: application may use UUID v7/ULID as text-compatible UUID.
CREATE TABLE tenant (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
slug TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT tenant_slug_chk CHECK (length(trim(slug)) > 0),
CONSTRAINT tenant_slug_uniq UNIQUE (slug)
);
CREATE TABLE doh_profile (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT '',
url TEXT NOT NULL,
timeout_ms INTEGER,
secret_ref TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT doh_profile_url_chk CHECK (length(trim(url)) > 0)
);
CREATE INDEX idx_doh_profile_tenant ON doh_profile (tenant_id);
CREATE TABLE bgp_community (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT '',
kind TEXT NOT NULL,
value_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT bgp_community_kind_chk CHECK (length(trim(kind)) > 0)
);
CREATE INDEX idx_bgp_community_tenant ON bgp_community (tenant_id);
CREATE TABLE module (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
type TEXT NOT NULL,
name TEXT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
priority INTEGER NOT NULL DEFAULT 0,
doh_profile_id UUID REFERENCES doh_profile (id) ON DELETE SET NULL,
refresh_interval_sec INTEGER,
cron_expr TEXT,
default_community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
deleted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT module_type_chk CHECK (type IN (
'AS_PREFIXES', 'CDN_CIDRS', 'DOMAINS', 'IP_RANGES'
)),
CONSTRAINT module_name_chk CHECK (length(trim(name)) > 0)
);
CREATE INDEX idx_module_tenant ON module (tenant_id);
CREATE INDEX idx_module_tenant_type ON module (tenant_id, type);
CREATE INDEX idx_module_tenant_enabled ON module (tenant_id, enabled) WHERE deleted_at IS NULL;
CREATE TABLE module_cdn_source (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
source_kind TEXT NOT NULL,
url TEXT NOT NULL,
etag TEXT,
refresh_interval_sec INTEGER,
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT module_cdn_source_url_chk CHECK (length(trim(url)) > 0)
);
CREATE INDEX idx_module_cdn_source_module ON module_cdn_source (module_id);
CREATE TABLE module_domain_entry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
fqdn TEXT NOT NULL,
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
resolve_meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT module_domain_fqdn_chk CHECK (length(trim(fqdn)) > 0)
);
CREATE INDEX idx_module_domain_entry_module ON module_domain_entry (module_id);
CREATE TABLE module_as_entry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
asn BIGINT,
prefix CIDR,
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT module_as_entry_asn_or_prefix_chk CHECK (asn IS NOT NULL OR prefix IS NOT NULL)
);
CREATE INDEX idx_module_as_entry_module ON module_as_entry (module_id);
CREATE TABLE module_ip_range_entry (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
prefix CIDR NOT NULL,
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_module_ip_range_entry_module ON module_ip_range_entry (module_id);
-- Full config snapshot for a tenant (optionally tied to one module for filtering in API).
CREATE TABLE config_revision (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
module_id UUID REFERENCES module (id) ON DELETE SET NULL,
content_hash TEXT NOT NULL,
parent_revision_id UUID REFERENCES config_revision (id) ON DELETE SET NULL,
artifact_ref TEXT,
meta_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT config_revision_hash_chk CHECK (length(trim(content_hash)) > 0)
);
CREATE INDEX idx_config_revision_tenant ON config_revision (tenant_id, created_at DESC);
CREATE INDEX idx_config_revision_module ON config_revision (module_id) WHERE module_id IS NOT NULL;
CREATE TABLE bgp_speaker (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
role TEXT NOT NULL,
endpoint TEXT,
last_applied_revision_id UUID REFERENCES config_revision (id) ON DELETE SET NULL,
meta_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT bgp_speaker_role_chk CHECK (role IN ('master', 'replica'))
);
CREATE INDEX idx_bgp_speaker_tenant ON bgp_speaker (tenant_id);
CREATE TABLE bgp_peer (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
bgp_speaker_id UUID REFERENCES bgp_speaker (id) ON DELETE SET NULL,
neighbor INET NOT NULL,
remote_asn BIGINT NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
policies_json JSONB NOT NULL DEFAULT '{}',
meta_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_bgp_peer_tenant ON bgp_peer (tenant_id);
CREATE INDEX idx_bgp_peer_speaker ON bgp_peer (bgp_speaker_id);
CREATE TABLE revision_materialized_prefix (
id BIGSERIAL PRIMARY KEY,
revision_id UUID NOT NULL REFERENCES config_revision (id) ON DELETE CASCADE,
prefix CIDR NOT NULL,
community_id UUID REFERENCES bgp_community (id) ON DELETE SET NULL,
source TEXT NOT NULL DEFAULT '',
meta_json JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_rev_mat_prefix_revision ON revision_materialized_prefix (revision_id);
CREATE INDEX idx_rev_mat_prefix_value ON revision_materialized_prefix (prefix);
CREATE TABLE job_audit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
kind TEXT NOT NULL,
status TEXT NOT NULL,
idempotency_key TEXT,
module_id UUID REFERENCES module (id) ON DELETE SET NULL,
error_message TEXT,
progress_pct SMALLINT,
meta_json JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
CONSTRAINT job_audit_status_chk CHECK (status IN (
'queued', 'running', 'succeeded', 'failed', 'cancelled'
)),
CONSTRAINT job_audit_kind_chk CHECK (length(trim(kind)) > 0)
);
CREATE INDEX idx_job_audit_tenant_created ON job_audit (tenant_id, created_at DESC);
CREATE INDEX idx_job_audit_tenant_status ON job_audit (tenant_id, status);
CREATE UNIQUE INDEX idx_job_audit_idempotency
ON job_audit (tenant_id, idempotency_key)
WHERE idempotency_key IS NOT NULL;
CREATE TABLE global_settings (
tenant_id UUID NOT NULL REFERENCES tenant (id) ON DELETE CASCADE,
key TEXT NOT NULL,
value_json JSONB NOT NULL DEFAULT '{}',
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (tenant_id, key),
CONSTRAINT global_settings_key_chk CHECK (length(trim(key)) > 0)
);
-- Optional CDN fetch audit (plan §5.4).
CREATE TABLE module_cdn_fetch_log (
id BIGSERIAL PRIMARY KEY,
module_id UUID NOT NULL REFERENCES module (id) ON DELETE CASCADE,
source_id UUID REFERENCES module_cdn_source (id) ON DELETE SET NULL,
http_status INTEGER,
bytes BIGINT,
error TEXT,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_module_cdn_fetch_log_module ON module_cdn_fetch_log (module_id, fetched_at DESC);