Skip to content

Commit 4c4c75b

Browse files
authored
fix(pgvector): improve pgvector registration logging (#141)
Enhance error handling during pgvector registration for both asyncpg and psycopg, ensuring that missing extensions are logged as debug messages without disrupting connection usability.
1 parent 79bc750 commit 4c4c75b

File tree

4 files changed

+120
-4
lines changed

4 files changed

+120
-4
lines changed

AGENTS.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,103 @@ if "enable_feature" not in driver_features:
557557
driver_features["enable_feature"] = OPTIONAL_PACKAGE_INSTALLED
558558
```
559559

560+
### Error Handling in Type Handlers
561+
562+
When implementing type handlers that register optional database extensions, distinguish between expected and unexpected failures:
563+
564+
**Expected Failures** (graceful degradation) → **DEBUG level**:
565+
566+
- Database extension not enabled (e.g., `CREATE EXTENSION vector` not run)
567+
- Optional Python package not installed
568+
- Database version doesn't support the feature
569+
570+
**Unexpected Failures** (need investigation) → **WARNING or ERROR level**:
571+
572+
- Network errors during registration
573+
- Permission issues
574+
- Invalid configuration
575+
- Unknown exceptions
576+
577+
**Pattern for Extension Registration**:
578+
579+
```python
580+
async def register_optional_extension(connection):
581+
"""Register optional database extension support.
582+
583+
Gracefully handles missing extensions with DEBUG logging.
584+
"""
585+
if not OPTIONAL_PACKAGE_INSTALLED:
586+
logger.debug("Optional package not installed - skipping extension support")
587+
return
588+
589+
try:
590+
import optional_package
591+
await optional_package.register(connection)
592+
logger.debug("Registered optional extension support")
593+
except SpecificExpectedError as error:
594+
message = str(error).lower()
595+
if "extension not found" in message or "type not found" in message:
596+
logger.debug("Skipping extension registration - extension not enabled in database")
597+
return
598+
logger.warning("Unexpected error during extension registration: %s", error)
599+
except Exception:
600+
logger.exception("Failed to register optional extension")
601+
```
602+
603+
**Real-World Example - PostgreSQL pgvector**:
604+
605+
Different PostgreSQL drivers raise different error messages for the same condition (pgvector extension not enabled):
606+
607+
```python
608+
# AsyncPG - raises ValueError("unknown type: public.vector")
609+
async def register_pgvector_support(connection):
610+
if not PGVECTOR_INSTALLED:
611+
logger.debug("pgvector not installed - skipping vector type support")
612+
return
613+
614+
try:
615+
import pgvector.asyncpg
616+
await pgvector.asyncpg.register_vector(connection)
617+
logger.debug("Registered pgvector support on asyncpg connection")
618+
except ValueError as exc:
619+
message = str(exc).lower()
620+
if "unknown type" in message and "vector" in message:
621+
logger.debug("Skipping pgvector registration - extension not enabled in database")
622+
return
623+
logger.warning("Unexpected error during pgvector registration: %s", exc)
624+
except Exception:
625+
logger.exception("Failed to register pgvector support")
626+
627+
# Psycopg - raises ValueError("vector type not found in the database")
628+
def register_pgvector_sync(connection):
629+
if not PGVECTOR_INSTALLED:
630+
logger.debug("pgvector not installed - skipping vector type handlers")
631+
return
632+
633+
try:
634+
import pgvector.psycopg
635+
pgvector.psycopg.register_vector(connection)
636+
logger.debug("Registered pgvector type handlers on psycopg sync connection")
637+
except ValueError as error:
638+
message = str(error).lower()
639+
if "vector type not found" in message:
640+
logger.debug("Skipping pgvector registration - extension not enabled in database")
641+
return
642+
logger.warning("Unexpected error during pgvector registration: %s", error)
643+
except Exception:
644+
logger.exception("Failed to register pgvector for psycopg sync")
645+
```
646+
647+
**Key Principles**:
648+
649+
1. Check for **specific error messages** to identify expected vs unexpected failures
650+
2. Use **DEBUG** for expected graceful degradation (extension not available)
651+
3. Use **WARNING** for unexpected issues during optional feature setup
652+
4. Use **ERROR/exception** for critical failures
653+
5. Provide **clear, actionable log messages**
654+
6. Never break application flow on expected failures
655+
7. Match error message checks to **actual driver behavior** (different drivers raise different messages)
656+
560657
### Examples from Existing Adapters
561658

562659
**Oracle NumPy VECTOR Support** (`oracledb/_numpy_handlers.py`):

docs/reference/adapters.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1240,6 +1240,7 @@ PostgreSQL's pgvector extension enables vector similarity search. SQLSpec automa
12401240
- PostgreSQL with pgvector extension installed
12411241
- ``pgvector`` Python package installed
12421242
- Automatic registration (no configuration needed)
1243+
- Extension registration failures are downgraded to debug logs, so missing pgvector keeps the connection usable without vector support
12431244

12441245
**Adapters with pgvector support**:
12451246
- ``asyncpg`` - Async PostgreSQL driver

sqlspec/adapters/asyncpg/_type_handlers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,11 @@ async def register_pgvector_support(connection: "AsyncpgConnection") -> None:
6161

6262
await pgvector.asyncpg.register_vector(connection)
6363
logger.debug("Registered pgvector support on asyncpg connection")
64+
except ValueError as exc:
65+
message = str(exc).lower()
66+
if "unknown type" in message and "vector" in message:
67+
logger.debug("Skipping pgvector registration because extension is unavailable")
68+
return
69+
logger.exception("Failed to register pgvector support")
6470
except Exception:
6571
logger.exception("Failed to register pgvector support")

sqlspec/adapters/psycopg/_type_handlers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ def register_pgvector_sync(connection: "Connection[Any]") -> None:
3939

4040
pgvector.psycopg.register_vector(connection)
4141
logger.debug("Registered pgvector type handlers on psycopg sync connection")
42-
except Exception as e:
43-
logger.debug("Failed to register pgvector for psycopg sync: %s", e)
42+
except ValueError as error:
43+
message = str(error).lower()
44+
if "vector type not found" in message:
45+
logger.debug("Skipping pgvector registration - extension not enabled in database")
46+
return
47+
logger.warning("Unexpected error during pgvector registration: %s", error)
48+
except Exception:
49+
logger.exception("Failed to register pgvector for psycopg sync")
4450

4551

4652
async def register_pgvector_async(connection: "AsyncConnection[Any]") -> None:
@@ -64,5 +70,11 @@ async def register_pgvector_async(connection: "AsyncConnection[Any]") -> None:
6470

6571
await register_vector_async(connection)
6672
logger.debug("Registered pgvector type handlers on psycopg async connection")
67-
except Exception as e:
68-
logger.debug("Failed to register pgvector for psycopg async: %s", e)
73+
except ValueError as error:
74+
message = str(error).lower()
75+
if "vector type not found" in message:
76+
logger.debug("Skipping pgvector registration - extension not enabled in database")
77+
return
78+
logger.warning("Unexpected error during pgvector registration: %s", error)
79+
except Exception:
80+
logger.exception("Failed to register pgvector for psycopg async")

0 commit comments

Comments
 (0)