206 lines
7.7 KiB
PL/PgSQL
206 lines
7.7 KiB
PL/PgSQL
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_sources (
|
|
source_id text PRIMARY KEY,
|
|
tenant text NOT NULL DEFAULT 'default',
|
|
source_type text NOT NULL CHECK (source_type IN ('file', 'folder')),
|
|
source_ref text NOT NULL,
|
|
active_version_id uuid NULL,
|
|
needs_reingest boolean NOT NULL DEFAULT false,
|
|
disabled_at timestamptz NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_source_versions (
|
|
version_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_id text NOT NULL REFERENCES rag_sources(source_id) ON DELETE RESTRICT,
|
|
version_number bigint NOT NULL CHECK (version_number > 0),
|
|
previous_version_id uuid NULL,
|
|
state text NOT NULL CHECK (state IN (
|
|
'pending', 'indexing', 'review_required', 'ready', 'active',
|
|
'superseded', 'failed', 'rejected', 'purging', 'purged'
|
|
)),
|
|
original_manifest_hash char(64) NOT NULL,
|
|
source_content_hash char(64) NULL,
|
|
processing_fingerprint char(64) NOT NULL,
|
|
metadata_hash char(64) NOT NULL,
|
|
tags text[] NOT NULL DEFAULT '{}',
|
|
activate_requested boolean NOT NULL DEFAULT true,
|
|
base_active_version_id uuid NULL,
|
|
embedding_provider text NOT NULL,
|
|
embedding_model text NOT NULL,
|
|
embedding_dimensions integer NOT NULL CHECK (embedding_dimensions > 0),
|
|
qdrant_collection text NOT NULL,
|
|
expected_document_count integer NOT NULL DEFAULT 0 CHECK (expected_document_count >= 0),
|
|
expected_point_count integer NOT NULL DEFAULT 0 CHECK (expected_point_count >= 0),
|
|
verified_point_count integer NOT NULL DEFAULT 0 CHECK (verified_point_count >= 0),
|
|
error_code text NULL,
|
|
error_detail text NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
indexing_started_at timestamptz NULL,
|
|
ready_at timestamptz NULL,
|
|
activated_at timestamptz NULL,
|
|
superseded_at timestamptz NULL,
|
|
reviewed_at timestamptz NULL,
|
|
reviewed_by text NULL,
|
|
artifact_state text NOT NULL DEFAULT 'none'
|
|
CHECK (artifact_state IN ('none', 'present', 'retention_deleting', 'retention_deleted')),
|
|
retention_due_at timestamptz NULL,
|
|
purged_at timestamptz NULL,
|
|
UNIQUE (source_id, version_number),
|
|
UNIQUE (version_id, source_id),
|
|
FOREIGN KEY (previous_version_id, source_id)
|
|
REFERENCES rag_source_versions(version_id, source_id)
|
|
DEFERRABLE INITIALLY DEFERRED,
|
|
FOREIGN KEY (base_active_version_id, source_id)
|
|
REFERENCES rag_source_versions(version_id, source_id)
|
|
DEFERRABLE INITIALLY DEFERRED
|
|
);
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint WHERE conname = 'rag_sources_active_version_fk'
|
|
) THEN
|
|
ALTER TABLE rag_sources
|
|
ADD CONSTRAINT rag_sources_active_version_fk
|
|
FOREIGN KEY (active_version_id, source_id)
|
|
REFERENCES rag_source_versions(version_id, source_id)
|
|
DEFERRABLE INITIALLY DEFERRED;
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS rag_one_active_version_per_source
|
|
ON rag_source_versions(source_id)
|
|
WHERE state = 'active';
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS rag_one_reusable_content_version
|
|
ON rag_source_versions(source_id, source_content_hash, processing_fingerprint, metadata_hash)
|
|
WHERE source_content_hash IS NOT NULL
|
|
AND state NOT IN ('failed', 'rejected', 'purged');
|
|
|
|
CREATE INDEX IF NOT EXISTS rag_source_versions_source_state_idx
|
|
ON rag_source_versions(source_id, state);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_version_documents (
|
|
version_id uuid NOT NULL REFERENCES rag_source_versions(version_id) ON DELETE RESTRICT,
|
|
document_id text NOT NULL,
|
|
document_key text NOT NULL,
|
|
original_hash char(64) NOT NULL,
|
|
original_hash_kind text NOT NULL CHECK (original_hash_kind IN ('bytes', 'legacy-derived')),
|
|
content_hash char(64) NULL,
|
|
mime_type text NOT NULL,
|
|
title text NOT NULL,
|
|
extraction_method text NOT NULL DEFAULT 'native',
|
|
index_state text NOT NULL CHECK (index_state IN ('pending', 'indexing', 'ready', 'failed')),
|
|
chunk_count integer NOT NULL DEFAULT 0 CHECK (chunk_count >= 0),
|
|
artifact_manifest_path text NULL,
|
|
error_detail text NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (version_id, document_id),
|
|
UNIQUE (version_id, document_key)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS rag_version_documents_document_idx
|
|
ON rag_version_documents(document_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_ingestion_attempts (
|
|
attempt_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
source_id text NOT NULL REFERENCES rag_sources(source_id) ON DELETE RESTRICT,
|
|
version_id uuid NULL REFERENCES rag_source_versions(version_id) ON DELETE SET NULL,
|
|
state text NOT NULL CHECK (state IN ('received', 'processing', 'completed', 'failed')),
|
|
input_locator text NOT NULL,
|
|
input_hash char(64) NULL,
|
|
error_code text NULL,
|
|
error_detail text NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
completed_at timestamptz NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_system_state (
|
|
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
|
|
maintenance boolean NOT NULL DEFAULT false,
|
|
maintenance_reason text NULL,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
INSERT INTO rag_system_state(singleton) VALUES (true)
|
|
ON CONFLICT (singleton) DO NOTHING;
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_legacy_migration_batches (
|
|
batch_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
state text NOT NULL CHECK (state IN ('planned', 'running', 'verified', 'rolled_back', 'failed')),
|
|
snapshot_ref text NOT NULL,
|
|
report jsonb NOT NULL DEFAULT '{}',
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
completed_at timestamptz NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS rag_legacy_migration_items (
|
|
batch_id uuid NOT NULL REFERENCES rag_legacy_migration_batches(batch_id) ON DELETE RESTRICT,
|
|
source_id text NOT NULL,
|
|
version_id uuid NULL REFERENCES rag_source_versions(version_id) ON DELETE SET NULL,
|
|
expected_point_count integer NOT NULL CHECK (expected_point_count >= 0),
|
|
state text NOT NULL CHECK (state IN ('planned', 'migrated', 'verified', 'rolled_back', 'failed')),
|
|
error_detail text NULL,
|
|
PRIMARY KEY (batch_id, source_id)
|
|
);
|
|
|
|
CREATE OR REPLACE FUNCTION rag_touch_updated_at()
|
|
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
BEGIN
|
|
NEW.updated_at = now();
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS rag_sources_touch_updated_at ON rag_sources;
|
|
CREATE TRIGGER rag_sources_touch_updated_at
|
|
BEFORE UPDATE ON rag_sources
|
|
FOR EACH ROW EXECUTE FUNCTION rag_touch_updated_at();
|
|
|
|
CREATE OR REPLACE FUNCTION rag_validate_active_source_pointer()
|
|
RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE
|
|
invalid_pointer_count integer;
|
|
orphan_active_count integer;
|
|
BEGIN
|
|
SELECT count(*) INTO invalid_pointer_count
|
|
FROM rag_sources s
|
|
LEFT JOIN rag_source_versions v
|
|
ON v.version_id = s.active_version_id
|
|
AND v.source_id = s.source_id
|
|
AND v.state = 'active'
|
|
WHERE s.active_version_id IS NOT NULL
|
|
AND v.version_id IS NULL;
|
|
|
|
IF invalid_pointer_count > 0 THEN
|
|
RAISE EXCEPTION 'rag_sources.active_version_id must point to an active version in the same source';
|
|
END IF;
|
|
|
|
SELECT count(*) INTO orphan_active_count
|
|
FROM rag_source_versions v
|
|
JOIN rag_sources s ON s.source_id = v.source_id
|
|
WHERE v.state = 'active'
|
|
AND s.active_version_id IS DISTINCT FROM v.version_id;
|
|
|
|
IF orphan_active_count > 0 THEN
|
|
RAISE EXCEPTION 'every active rag_source_versions row must be the source active_version_id';
|
|
END IF;
|
|
|
|
RETURN NULL;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS rag_sources_validate_active_pointer ON rag_sources;
|
|
CREATE CONSTRAINT TRIGGER rag_sources_validate_active_pointer
|
|
AFTER INSERT OR UPDATE ON rag_sources
|
|
DEFERRABLE INITIALLY DEFERRED
|
|
FOR EACH ROW EXECUTE FUNCTION rag_validate_active_source_pointer();
|
|
|
|
DROP TRIGGER IF EXISTS rag_versions_validate_active_pointer ON rag_source_versions;
|
|
CREATE CONSTRAINT TRIGGER rag_versions_validate_active_pointer
|
|
AFTER INSERT OR UPDATE ON rag_source_versions
|
|
DEFERRABLE INITIALLY DEFERRED
|
|
FOR EACH ROW EXECUTE FUNCTION rag_validate_active_source_pointer();
|