Fix pgvector sslmode handling for PostgreSQL URIs (#5308)

This commit is contained in:
Ren yiwei
2026-06-04 19:32:47 +05:30
committed by GitHub
parent 95b6f95f7b
commit 866888df41
2 changed files with 55 additions and 12 deletions
+22 -9
View File
@@ -1,7 +1,9 @@
import json import json
import logging import logging
import re
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, List, Optional from typing import Any, List, Optional
from urllib.parse import parse_qsl, urlencode, urlsplit
from pydantic import BaseModel from pydantic import BaseModel
@@ -113,6 +115,24 @@ def _build_filter_conditions(filters):
return conditions, params return conditions, params
def _with_sslmode(connection_string: str, sslmode: str) -> str:
"""Add or replace sslmode in URI and keyword conninfo strings.
Keyword conninfo values are assumed not to contain nested ``sslmode=``
substrings, such as inside an ``options`` value.
"""
if "://" in connection_string:
parsed = urlsplit(connection_string)
query = [(key, value) for key, value in parse_qsl(parsed.query, keep_blank_values=True) if key != "sslmode"]
query.append(("sslmode", sslmode))
return parsed._replace(query=urlencode(query)).geturl()
if re.search(r"(^|\s)sslmode=", connection_string):
return re.sub(r"(^|\s)sslmode=\S+", lambda match: f"{match.group(1)}sslmode={sslmode}", connection_string)
return f"{connection_string} sslmode={sslmode}"
class OutputData(BaseModel): class OutputData(BaseModel):
id: Optional[str] id: Optional[str]
score: Optional[float] score: Optional[float]
@@ -168,18 +188,11 @@ class PGVector(VectorStoreBase):
self.connection_pool = connection_pool self.connection_pool = connection_pool
elif connection_string: elif connection_string:
if sslmode: if sslmode:
# Append sslmode to connection string if provided connection_string = _with_sslmode(connection_string, sslmode)
if 'sslmode=' in connection_string:
# Replace existing sslmode
import re
connection_string = re.sub(r'sslmode=[^ ]*', f'sslmode={sslmode}', connection_string)
else:
# Add sslmode to connection string
connection_string = f"{connection_string} sslmode={sslmode}"
else: else:
connection_string = f"postgresql://{user}:{password}@{host}:{port}/{dbname}" connection_string = f"postgresql://{user}:{password}@{host}:{port}/{dbname}"
if sslmode: if sslmode:
connection_string = f"{connection_string} sslmode={sslmode}" connection_string = _with_sslmode(connection_string, sslmode)
if self.connection_pool is None: if self.connection_pool is None:
if PSYCOPG_VERSION == 3: if PSYCOPG_VERSION == 3:
+33 -3
View File
@@ -4,7 +4,7 @@ import unittest
import uuid import uuid
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from mem0.vector_stores.pgvector import PGVector, _build_filter_conditions from mem0.vector_stores.pgvector import PGVector, _build_filter_conditions, _with_sslmode
class TestPGVector(unittest.TestCase): class TestPGVector(unittest.TestCase):
@@ -2084,8 +2084,8 @@ class TestPGVector(unittest.TestCase):
connection_string=connection_string connection_string=connection_string
) )
# Verify ConnectionPool was called with the connection string including sslmode # Verify ConnectionPool was called with sslmode as a URI query parameter
expected_conn_string = f"{connection_string} sslmode=require" expected_conn_string = f"{connection_string}?sslmode=require"
mock_connection_pool.assert_called_with( mock_connection_pool.assert_called_with(
conninfo=expected_conn_string, conninfo=expected_conn_string,
min_size=1, min_size=1,
@@ -2095,6 +2095,36 @@ class TestPGVector(unittest.TestCase):
self.assertEqual(pgvector.collection_name, "test_collection") self.assertEqual(pgvector.collection_name, "test_collection")
self.assertEqual(pgvector.embedding_model_dims, 3) self.assertEqual(pgvector.embedding_model_dims, 3)
def test_with_sslmode_appends_uri_query_param(self):
"""Test sslmode is appended to URI connection strings without corrupting dbname."""
connection_string = _with_sslmode(
"postgresql://user:pass@localhost:5432/db?connect_timeout=10",
"require",
)
self.assertEqual(
connection_string,
"postgresql://user:pass@localhost:5432/db?connect_timeout=10&sslmode=require",
)
def test_with_sslmode_replaces_existing_uri_sslmode(self):
"""Test existing URI sslmode values are replaced rather than duplicated."""
connection_string = _with_sslmode(
"postgresql://user:pass@localhost:5432/db?sslmode=prefer&connect_timeout=10",
"require",
)
self.assertEqual(
connection_string,
"postgresql://user:pass@localhost:5432/db?connect_timeout=10&sslmode=require",
)
def test_with_sslmode_preserves_keyword_conninfo_format(self):
"""Test non-URI conninfo strings keep PostgreSQL keyword syntax."""
connection_string = _with_sslmode("dbname=test user=postgres sslmode=prefer", "require")
self.assertEqual(connection_string, "dbname=test user=postgres sslmode=require")
# Enhanced Test for Index Creation with DiskANN # Enhanced Test for Index Creation with DiskANN
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 3) @patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 3)
@patch('mem0.vector_stores.pgvector.ConnectionPool') @patch('mem0.vector_stores.pgvector.ConnectionPool')