89 lines
2.5 KiB
Python
89 lines
2.5 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Check database indexes — verify all required indexes exist for performance.
|
||
|
|
|
||
|
|
Usage:
|
||
|
|
python scripts/check_indexes.py
|
||
|
|
|
||
|
|
Checks that critical indexes for contacts, companies, and tenant scoping
|
||
|
|
are present in the database.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
# Add project root to path
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
|
|
||
|
|
from sqlalchemy import text
|
||
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
||
|
|
|
||
|
|
from app.config import get_settings
|
||
|
|
|
||
|
|
# Required indexes for performance (table, index_name)
|
||
|
|
REQUIRED_INDEXES = [
|
||
|
|
("contacts", "ix_contacts_tenant_deleted"),
|
||
|
|
("contacts", "ix_contacts_tenant_name"),
|
||
|
|
("contacts", "ix_contacts_email"),
|
||
|
|
("companies", "ix_companies_tenant_deleted"),
|
||
|
|
("companies", "ix_companies_tenant_name"),
|
||
|
|
("companies", "ix_companies_industry"),
|
||
|
|
("company_contacts", "ix_cc_company"),
|
||
|
|
("company_contacts", "ix_cc_contact"),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
async def check_indexes() -> int:
|
||
|
|
"""Check that all required indexes exist in the database.
|
||
|
|
|
||
|
|
Returns 0 if all indexes present, 1 if any missing.
|
||
|
|
"""
|
||
|
|
settings = get_settings()
|
||
|
|
engine = create_async_engine(settings.database_url)
|
||
|
|
|
||
|
|
missing: list[tuple[str, str]] = []
|
||
|
|
present: list[tuple[str, str]] = []
|
||
|
|
|
||
|
|
async with engine.connect() as conn:
|
||
|
|
for table_name, index_name in REQUIRED_INDEXES:
|
||
|
|
result = await conn.execute(
|
||
|
|
text(
|
||
|
|
"SELECT 1 FROM pg_indexes "
|
||
|
|
"WHERE schemaname = 'public' "
|
||
|
|
"AND tablename = :table "
|
||
|
|
"AND indexname = :index"
|
||
|
|
),
|
||
|
|
{"table": table_name, "index": index_name},
|
||
|
|
)
|
||
|
|
if result.scalar():
|
||
|
|
present.append((table_name, index_name))
|
||
|
|
print(f" ✓ {table_name}.{index_name}")
|
||
|
|
else:
|
||
|
|
missing.append((table_name, index_name))
|
||
|
|
print(f" ✗ MISSING: {table_name}.{index_name}")
|
||
|
|
|
||
|
|
await engine.dispose()
|
||
|
|
|
||
|
|
print(f"\nSummary: {len(present)} present, {len(missing)} missing")
|
||
|
|
if missing:
|
||
|
|
print("\nMissing indexes:")
|
||
|
|
for table, idx in missing:
|
||
|
|
print(f" - {table}.{idx}")
|
||
|
|
return 1
|
||
|
|
|
||
|
|
print("\nAll required indexes present!")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Run index check."""
|
||
|
|
print("Checking database indexes...")
|
||
|
|
exit_code = asyncio.run(check_indexes())
|
||
|
|
sys.exit(exit_code)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|