-- A tenant clock is the short commit barrier for revision admission. Semantic -- writers update it in their own transaction, after their entity row change. CREATE TABLE tenant_render_generation ( tenant_id UUID PRIMARY KEY REFERENCES tenant(id) ON DELETE CASCADE, generation BIGINT NOT NULL DEFAULT 1 ); INSERT INTO tenant_render_generation(tenant_id, generation) SELECT id, 1 FROM tenant; CREATE SEQUENCE module_snapshot_version_seq AS BIGINT; ALTER TABLE module_prefix_snapshot ADD COLUMN snapshot_version BIGINT NOT NULL DEFAULT nextval('module_snapshot_version_seq'); CREATE TABLE render_revision_manifest ( revision_id UUID PRIMARY KEY REFERENCES config_revision(id) ON DELETE CASCADE, tenant_id UUID NOT NULL REFERENCES tenant(id) ON DELETE CASCADE, config_generation BIGINT NOT NULL, manifest_json JSONB NOT NULL ); CREATE INDEX render_revision_manifest_tenant ON render_revision_manifest(tenant_id, revision_id); CREATE FUNCTION render_bump_tenant_generation(p_tenant UUID) RETURNS void LANGUAGE plpgsql AS $$ BEGIN IF p_tenant IS NULL THEN RETURN; END IF; INSERT INTO tenant_render_generation(tenant_id, generation) VALUES (p_tenant, 2) ON CONFLICT (tenant_id) DO UPDATE SET generation = tenant_render_generation.generation + 1; END; $$; CREATE FUNCTION render_bump_module_generation(p_module UUID) RETURNS void LANGUAGE plpgsql AS $$ DECLARE v_tenant UUID; BEGIN IF p_module IS NULL THEN RETURN; END IF; SELECT tenant_id INTO v_tenant FROM module WHERE id = p_module; PERFORM render_bump_tenant_generation(v_tenant); END; $$; CREATE FUNCTION render_tenant_created() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO tenant_render_generation(tenant_id, generation) VALUES(NEW.id, 1); RETURN NEW; END; $$; CREATE TRIGGER tenant_render_created AFTER INSERT ON tenant FOR EACH ROW EXECUTE FUNCTION render_tenant_created(); CREATE FUNCTION render_semantic_change() RETURNS trigger LANGUAGE plpgsql AS $$ DECLARE v_old JSONB; v_new JSONB; v_old_tenant UUID; v_new_tenant UUID; v_old_module UUID; v_new_module UUID; BEGIN IF TG_OP = 'UPDATE' THEN v_old := to_jsonb(OLD); v_new := to_jsonb(NEW); IF TG_TABLE_NAME = 'module' THEN v_old := v_old - ARRAY['input_hash','last_refreshed_at','updated_at']; v_new := v_new - ARRAY['input_hash','last_refreshed_at','updated_at']; ELSIF TG_TABLE_NAME = 'module_cdn_source' THEN v_old := v_old - ARRAY['etag','last_refreshed_at','updated_at']; v_new := v_new - ARRAY['etag','last_refreshed_at','updated_at']; ELSIF TG_TABLE_NAME = 'module_as_entry' THEN v_old := v_old - ARRAY['asn_name','prefix_count','asn_resolved_at','updated_at']; v_new := v_new - ARRAY['asn_name','prefix_count','asn_resolved_at','updated_at']; ELSIF TG_TABLE_NAME = 'module_domain_entry' THEN v_old := v_old - ARRAY['resolve_meta','updated_at']; v_new := v_new - ARRAY['resolve_meta','updated_at']; ELSIF TG_TABLE_NAME = 'bgp_peer' THEN v_old := v_old - ARRAY['meta_json','updated_at']; v_new := v_new - ARRAY['meta_json','updated_at']; ELSIF TG_TABLE_NAME = 'bgp_community' THEN v_old := v_old - ARRAY['title','updated_at']; v_new := v_new - ARRAY['title','updated_at']; ELSIF TG_TABLE_NAME = 'bgp_speaker' THEN v_old := jsonb_build_object('node_ipv4',OLD.meta_json->>'node_ipv4', 'bird_bgp_source_ipv4',OLD.meta_json->>'bird_bgp_source_ipv4', 'bird_bgp_source_ipv6',OLD.meta_json->>'bird_bgp_source_ipv6'); v_new := jsonb_build_object('node_ipv4',NEW.meta_json->>'node_ipv4', 'bird_bgp_source_ipv4',NEW.meta_json->>'bird_bgp_source_ipv4', 'bird_bgp_source_ipv6',NEW.meta_json->>'bird_bgp_source_ipv6'); ELSE v_old := v_old - 'updated_at'; v_new := v_new - 'updated_at'; END IF; IF v_old = v_new THEN RETURN NEW; END IF; END IF; IF TG_TABLE_NAME IN ('module','doh_profile','bgp_peer','bgp_community','bgp_speaker','global_settings') THEN IF TG_OP = 'INSERT' THEN v_new_tenant := NEW.tenant_id; ELSIF TG_OP = 'DELETE' THEN v_old_tenant := OLD.tenant_id; ELSE v_old_tenant := OLD.tenant_id; v_new_tenant := NEW.tenant_id; END IF; ELSIF TG_TABLE_NAME IN ('module_cdn_source','module_domain_entry','module_as_entry','module_ip_range_entry','module_doh_profile') THEN IF TG_OP = 'INSERT' THEN v_new_module := NEW.module_id; ELSIF TG_OP = 'DELETE' THEN v_old_module := OLD.module_id; ELSE v_old_module := OLD.module_id; v_new_module := NEW.module_id; END IF; ELSIF TG_TABLE_NAME = 'module_prefix_snapshot' THEN IF TG_OP = 'INSERT' THEN v_new_tenant := NEW.tenant_id; ELSIF TG_OP = 'DELETE' THEN v_old_tenant := OLD.tenant_id; ELSE v_old_tenant := OLD.tenant_id; v_new_tenant := NEW.tenant_id; END IF; END IF; IF v_old_module IS NOT NULL THEN PERFORM render_bump_module_generation(v_old_module); END IF; IF v_new_module IS NOT NULL AND v_new_module IS DISTINCT FROM v_old_module THEN PERFORM render_bump_module_generation(v_new_module); END IF; IF v_old_tenant IS NOT NULL THEN PERFORM render_bump_tenant_generation(v_old_tenant); END IF; IF v_new_tenant IS NOT NULL AND v_new_tenant IS DISTINCT FROM v_old_tenant THEN PERFORM render_bump_tenant_generation(v_new_tenant); END IF; IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; RETURN NEW; END; $$; CREATE FUNCTION render_snapshot_version() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.snapshot_version := nextval('module_snapshot_version_seq'); RETURN NEW; END; $$; CREATE TRIGGER module_snapshot_version BEFORE INSERT OR UPDATE ON module_prefix_snapshot FOR EACH ROW EXECUTE FUNCTION render_snapshot_version(); CREATE TRIGGER render_module_changed AFTER INSERT OR UPDATE OR DELETE ON module FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_cdn_source_changed AFTER INSERT OR UPDATE OR DELETE ON module_cdn_source FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_as_entry_changed AFTER INSERT OR UPDATE OR DELETE ON module_as_entry FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_domain_entry_changed AFTER INSERT OR UPDATE OR DELETE ON module_domain_entry FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_ip_range_entry_changed AFTER INSERT OR UPDATE OR DELETE ON module_ip_range_entry FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_doh_profile_changed AFTER INSERT OR UPDATE OR DELETE ON doh_profile FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_module_doh_changed AFTER INSERT OR UPDATE OR DELETE ON module_doh_profile FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_peer_changed AFTER INSERT OR UPDATE OR DELETE ON bgp_peer FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_community_changed AFTER INSERT OR UPDATE OR DELETE ON bgp_community FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_settings_changed AFTER INSERT OR UPDATE OR DELETE ON global_settings FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_speaker_changed AFTER INSERT OR UPDATE OR DELETE ON bgp_speaker FOR EACH ROW EXECUTE FUNCTION render_semantic_change(); CREATE TRIGGER render_snapshot_changed AFTER INSERT OR UPDATE OR DELETE ON module_prefix_snapshot FOR EACH ROW EXECUTE FUNCTION render_semantic_change();