fix: Plugin migration runner + unified_search + marketplace migrations
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
Fixes 3 issues found by API integration tests: 1. migration_runner.py: Add GLOBAL TABLE exemption for tables without tenant_id - New _extract_global_table_names() method parses -- GLOBAL TABLE: comments - marketplace_listings is intentionally global (no tenant_id) 2. unified_search/migrations/0002_embeddings.sql: Remove companies table (does not exist), add DO $$ BEGIN END $$ blocks to check table existence before ALTER 3. marketplace/migrations/0001_initial.sql: Add -- GLOBAL TABLE: marketplace_listings comment
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
-- Marketplace listings table (global, NOT tenant-scoped)
|
-- Marketplace listings table (global, NOT tenant-scoped)
|
||||||
|
-- GLOBAL TABLE: marketplace_listings
|
||||||
CREATE TABLE IF NOT EXISTS marketplace_listings (
|
CREATE TABLE IF NOT EXISTS marketplace_listings (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
name VARCHAR(80) NOT NULL UNIQUE,
|
name VARCHAR(80) NOT NULL UNIQUE,
|
||||||
|
|||||||
@@ -1,27 +1,45 @@
|
|||||||
-- Unified Search: Embedding columns and HNSW indexes
|
-- Unified Search: Embedding columns and HNSW indexes
|
||||||
|
-- Note: These columns are also added by Alembic migration 0104.
|
||||||
|
-- This migration is idempotent (IF NOT EXISTS) and skips non-existent tables.
|
||||||
|
|
||||||
-- Ensure pgvector extension is installed
|
-- Ensure pgvector extension is installed
|
||||||
CREATE EXTENSION IF NOT EXISTS vector;
|
CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
|
|
||||||
-- ─── Mails ───
|
-- ─── Mails ───
|
||||||
ALTER TABLE mails ADD COLUMN IF NOT EXISTS embedding vector(768);
|
DO $$ BEGIN
|
||||||
CREATE INDEX IF NOT EXISTS ix_mails_embedding ON mails USING hnsw(embedding vector_cosine_ops);
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'mails') THEN
|
||||||
|
ALTER TABLE mails ADD COLUMN IF NOT EXISTS embedding vector(768);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_mails_embedding ON mails USING hnsw(embedding vector_cosine_ops);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- ─── Contacts ───
|
-- ─── Contacts ───
|
||||||
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS embedding vector(768);
|
DO $$ BEGIN
|
||||||
CREATE INDEX IF NOT EXISTS ix_contacts_embedding ON contacts USING hnsw(embedding vector_cosine_ops);
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'contacts') THEN
|
||||||
|
ALTER TABLE contacts ADD COLUMN IF NOT EXISTS embedding vector(768);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_contacts_embedding ON contacts USING hnsw(embedding vector_cosine_ops);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- ─── Companies ───
|
-- ─── Files ───
|
||||||
ALTER TABLE companies ADD COLUMN IF NOT EXISTS embedding vector(768);
|
DO $$ BEGIN
|
||||||
CREATE INDEX IF NOT EXISTS ix_companies_embedding ON companies USING hnsw(embedding vector_cosine_ops);
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'files') THEN
|
||||||
|
ALTER TABLE files ADD COLUMN IF NOT EXISTS embedding vector(768);
|
||||||
-- ─── Files (content_text already added in 0001) ───
|
CREATE INDEX IF NOT EXISTS ix_files_embedding ON files USING hnsw(embedding vector_cosine_ops);
|
||||||
ALTER TABLE files ADD COLUMN IF NOT EXISTS embedding vector(768);
|
END IF;
|
||||||
CREATE INDEX IF NOT EXISTS ix_files_embedding ON files USING hnsw(embedding vector_cosine_ops);
|
END $$;
|
||||||
|
|
||||||
-- ─── Calendar Entries ───
|
-- ─── Calendar Entries ───
|
||||||
ALTER TABLE calendar_entries ADD COLUMN IF NOT EXISTS embedding vector(768);
|
DO $$ BEGIN
|
||||||
CREATE INDEX IF NOT EXISTS ix_cal_embedding ON calendar_entries USING hnsw(embedding vector_cosine_ops);
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'calendar_entries') THEN
|
||||||
|
ALTER TABLE calendar_entries ADD COLUMN IF NOT EXISTS embedding vector(768);
|
||||||
|
CREATE INDEX IF NOT EXISTS ix_cal_embedding ON calendar_entries USING hnsw(embedding vector_cosine_ops);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- ─── Tags (shorter dimension for short text) ───
|
-- ─── Tags (shorter dimension for short text) ───
|
||||||
ALTER TABLE tags ADD COLUMN IF NOT EXISTS embedding vector(384);
|
DO $$ BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'tags') THEN
|
||||||
|
ALTER TABLE tags ADD COLUMN IF NOT EXISTS embedding vector(384);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|||||||
@@ -95,8 +95,12 @@ class MigrationRunner:
|
|||||||
created_tables = new_tables - existing_tables
|
created_tables = new_tables - existing_tables
|
||||||
|
|
||||||
# Validate: all newly created tables must have tenant_id column
|
# Validate: all newly created tables must have tenant_id column
|
||||||
|
# Exception: tables marked as global in the SQL with `-- GLOBAL TABLE` comment
|
||||||
|
global_tables = self._extract_global_table_names(sql_content)
|
||||||
tables_without_tenant = []
|
tables_without_tenant = []
|
||||||
for table_name in created_tables:
|
for table_name in created_tables:
|
||||||
|
if table_name in global_tables:
|
||||||
|
continue # Global tables are exempt from tenant_id requirement
|
||||||
if not await self._table_has_column_via_session(db, table_name, "tenant_id"):
|
if not await self._table_has_column_via_session(db, table_name, "tenant_id"):
|
||||||
tables_without_tenant.append(table_name)
|
tables_without_tenant.append(table_name)
|
||||||
|
|
||||||
@@ -105,7 +109,8 @@ class MigrationRunner:
|
|||||||
await db.rollback()
|
await db.rollback()
|
||||||
raise MigrationValidationError(
|
raise MigrationValidationError(
|
||||||
f"Plugin '{plugin_name}' migration '{migration_filename}' created tables without tenant_id column: "
|
f"Plugin '{plugin_name}' migration '{migration_filename}' created tables without tenant_id column: "
|
||||||
f"{', '.join(tables_without_tenant)}. All plugin tables must have tenant_id for multi-tenant isolation."
|
f"{', '.join(tables_without_tenant)}. All plugin tables must have tenant_id for multi-tenant isolation. "
|
||||||
|
f"If a table is intentionally global, add `-- GLOBAL TABLE: table_name` comment to the migration SQL."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Record the migration in plugin_migrations table
|
# Record the migration in plugin_migrations table
|
||||||
@@ -469,3 +474,14 @@ class MigrationRunner:
|
|||||||
# Match CREATE TABLE [IF NOT EXISTS] "table_name" or CREATE TABLE table_name
|
# Match CREATE TABLE [IF NOT EXISTS] "table_name" or CREATE TABLE table_name
|
||||||
pattern = r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["\']?(\w+)["\']?'
|
pattern = r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["\']?(\w+)["\']?'
|
||||||
return re.findall(pattern, sql, re.IGNORECASE)
|
return re.findall(pattern, sql, re.IGNORECASE)
|
||||||
|
|
||||||
|
def _extract_global_table_names(sql: str) -> set[str]:
|
||||||
|
"""Extract table names marked as global in SQL comments.
|
||||||
|
|
||||||
|
Looks for `-- GLOBAL TABLE: table_name` comments in the SQL.
|
||||||
|
These tables are exempt from the tenant_id column requirement.
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
pattern = r'--\s*GLOBAL\s+TABLE:\s*(\w+)'
|
||||||
|
return set(re.findall(pattern, sql, re.IGNORECASE))
|
||||||
|
|||||||
Reference in New Issue
Block a user