pgvector-haystack 6.2.0__tar.gz → 6.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/CHANGELOG.md +23 -0
  2. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/PKG-INFO +3 -2
  3. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/pyproject.toml +17 -4
  4. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +3 -1
  5. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +3 -1
  6. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/document_stores/pgvector/converters.py +3 -2
  7. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/document_stores/pgvector/document_store.py +47 -43
  8. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/document_stores/pgvector/filters.py +50 -58
  9. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/conftest.py +14 -1
  10. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_document_store.py +215 -140
  11. pgvector_haystack-6.3.1/tests/test_document_store_async.py +304 -0
  12. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_filters.py +49 -23
  13. pgvector_haystack-6.2.0/tests/test_document_store_async.py +0 -799
  14. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/.gitignore +0 -0
  15. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/LICENSE.txt +0 -0
  16. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/README.md +0 -0
  17. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/examples/embedding_retrieval.py +0 -0
  18. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/examples/hybrid_retrieval.py +0 -0
  19. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/pydoc/config_docusaurus.yml +0 -0
  20. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  21. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  22. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  23. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/src/haystack_integrations/document_stores/py.typed +0 -0
  24. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/__init__.py +0 -0
  25. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_converters.py +0 -0
  26. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_retrieval.py +0 -0
  27. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_retrieval_async.py +0 -0
  28. {pgvector_haystack-6.2.0 → pgvector_haystack-6.3.1}/tests/test_retrievers.py +0 -0
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/pgvector-v6.3.0] - 2026-03-17
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - PgVectorDocumentStore `_treat_meta_field` and comparison functions now return Composed - string escaping done by `psycopg` (#2964)
8
+
9
+
10
+ ## [integrations/pgvector-v6.2.0] - 2026-03-02
11
+
12
+ ### 🐛 Bug Fixes
13
+
14
+ - Add metadata field-name validation using regex in `PGVectorDocumentStore` filters to prevent SQL injection vectors. (#2881)
15
+ - Remove NUL bytes when converting from haystack to pg documents (#2892)
16
+
17
+ ### 📚 Documentation
18
+
19
+ - Simplify pydoc configs (#2855)
20
+
21
+ ### 🧪 Testing
22
+
23
+ - Replacing each `DocumentStore` specific tests and used the generalised ones from `haystack.testing.document_store` (#2812)
24
+
25
+
3
26
  ## [integrations/pgvector-v6.1.0] - 2026-01-28
4
27
 
5
28
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgvector-haystack
3
- Version: 6.2.0
3
+ Version: 6.3.1
4
4
  Summary: An integration of pgvector (vector search extension for Postgres) with Haystack
5
5
  Project-URL: Source, https://github.com/deepset-ai/haystack-core-integrations
6
6
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/blob/main/integrations/pgvector/README.md
@@ -15,10 +15,11 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
18
19
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
20
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
21
  Requires-Python: >=3.10
21
- Requires-Dist: haystack-ai>=2.24.0
22
+ Requires-Dist: haystack-ai>=2.28.0
22
23
  Requires-Dist: pgvector>=0.3.0
23
24
  Requires-Dist: psycopg[binary]
24
25
  Description-Content-Type: text/markdown
@@ -19,11 +19,12 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3.11",
20
20
  "Programming Language :: Python :: 3.12",
21
21
  "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
22
23
  "Programming Language :: Python :: Implementation :: CPython",
23
24
  "Programming Language :: Python :: Implementation :: PyPy",
24
25
  ]
25
26
  dependencies = [
26
- "haystack-ai>=2.24.0",
27
+ "haystack-ai>=2.28.0",
27
28
  "pgvector>=0.3.0",
28
29
  "psycopg[binary]"
29
30
  ]
@@ -67,7 +68,8 @@ dependencies = [
67
68
  unit = 'pytest -m "not integration" {args:tests}'
68
69
  integration = 'pytest -m "integration" {args:tests}'
69
70
  all = 'pytest {args:tests}'
70
- cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x {args:tests}'
71
+ unit-cov-retry = 'pytest --cov=haystack_integrations --reruns 3 --reruns-delay 30 -x -m "not integration" {args:tests}'
72
+ integration-cov-append-retry = 'pytest --cov=haystack_integrations --cov-append --reruns 3 --reruns-delay 30 -x -m "integration" {args:tests}'
71
73
 
72
74
  types = "mypy -p haystack_integrations.document_stores.pgvector -p haystack_integrations.components.retrievers.pgvector {args}"
73
75
 
@@ -91,9 +93,17 @@ line-length = 120
91
93
  [tool.ruff.lint]
92
94
  select = [
93
95
  "A",
96
+ "ANN",
94
97
  "ARG",
95
98
  "B",
96
99
  "C",
100
+ "D102", # Missing docstring in public method
101
+ "D103", # Missing docstring in public function
102
+ "D205", # 1 blank line required between summary line and description
103
+ "D209", # Closing triple quotes go to new line
104
+ "D213", # summary lines must be positioned on the second physical line of the docstring
105
+ "D417", # Missing argument descriptions in the docstring
106
+ "D419", # Docstring is empty
97
107
  "DTZ",
98
108
  "E",
99
109
  "EM",
@@ -119,6 +129,8 @@ select = [
119
129
  ignore = [
120
130
  # Allow non-abstract empty methods in abstract base classes
121
131
  "B027",
132
+ # Allow Any in type annotations at dynamic boundaries
133
+ "ANN401",
122
134
  # Allow boolean positional values in function calls, like `dict.get(... True)`
123
135
  "FBT003",
124
136
  # Ignore checks for possible passwords
@@ -145,14 +157,15 @@ ban-relative-imports = "parents"
145
157
 
146
158
  [tool.ruff.lint.per-file-ignores]
147
159
  # Tests can use magic values, assertions, and relative imports
148
- "tests/**/*" = ["PLR2004", "S101", "TID252"]
160
+ "tests/**/*" = ["D", "PLR2004", "S101", "TID252", "ANN"]
149
161
  # examples can contain "print" commands
150
- "examples/**/*" = ["T201"]
162
+ "examples/**/*" = ["D", "T201"]
151
163
 
152
164
 
153
165
  [tool.coverage.run]
154
166
  source = ["haystack_integrations"]
155
167
  branch = true
168
+ relative_files = true
156
169
  parallel = false
157
170
 
158
171
  [tool.coverage.report]
@@ -66,8 +66,10 @@ class PgvectorEmbeddingRetriever:
66
66
  top_k: int = 10,
67
67
  vector_function: Literal["cosine_similarity", "inner_product", "l2_distance"] | None = None,
68
68
  filter_policy: str | FilterPolicy = FilterPolicy.REPLACE,
69
- ):
69
+ ) -> None:
70
70
  """
71
+ Initialize the PgvectorEmbeddingRetriever.
72
+
71
73
  :param document_store: An instance of `PgvectorDocumentStore`.
72
74
  :param filters: Filters applied to the retrieved Documents.
73
75
  :param top_k: Maximum number of Documents to return.
@@ -55,8 +55,10 @@ class PgvectorKeywordRetriever:
55
55
  filters: dict[str, Any] | None = None,
56
56
  top_k: int = 10,
57
57
  filter_policy: str | FilterPolicy = FilterPolicy.REPLACE,
58
- ):
58
+ ) -> None:
59
59
  """
60
+ Initialize the PgvectorKeywordRetriever.
61
+
60
62
  :param document_store: An instance of `PgvectorDocumentStore`.
61
63
  :param filters: Filters applied to the retrieved Documents.
62
64
  :param top_k: Maximum number of Documents to return.
@@ -10,8 +10,9 @@ logger = logging.getLogger(__name__)
10
10
 
11
11
  def _from_haystack_to_pg_documents(documents: list[Document]) -> list[dict[str, Any]]:
12
12
  """
13
- Internal method to convert a list of Haystack Documents to a list of dictionaries that can be used to insert
14
- documents into the PgvectorDocumentStore.
13
+ Internal method to convert a list of Haystack Documents to a list of dictionaries.
14
+
15
+ The resulting dictionaries can be used to insert documents into the PgvectorDocumentStore.
15
16
  """
16
17
 
17
18
  db_documents = []
@@ -96,9 +96,10 @@ class PgvectorDocumentStore:
96
96
  hnsw_index_name: str = "haystack_hnsw_index",
97
97
  hnsw_ef_search: int | None = None,
98
98
  keyword_index_name: str = "haystack_keyword_index",
99
- ):
99
+ ) -> None:
100
100
  """
101
101
  Creates a new PgvectorDocumentStore instance.
102
+
102
103
  It is meant to be connected to a PostgreSQL database with the pgvector extension installed.
103
104
  A specific table to store Haystack documents will be created if it doesn't exist yet.
104
105
 
@@ -223,7 +224,7 @@ class PgvectorDocumentStore:
223
224
  return default_from_dict(cls, data)
224
225
 
225
226
  @staticmethod
226
- def _connection_is_valid(connection):
227
+ def _connection_is_valid(connection: Connection) -> bool:
227
228
  """
228
229
  Internal method to check if the connection is still valid.
229
230
  """
@@ -238,7 +239,7 @@ class PgvectorDocumentStore:
238
239
  return True
239
240
 
240
241
  @staticmethod
241
- async def _connection_is_valid_async(connection):
242
+ async def _connection_is_valid_async(connection: AsyncConnection) -> bool:
242
243
  """
243
244
  Internal method to check if the async connection is still valid.
244
245
  """
@@ -250,18 +251,18 @@ class PgvectorDocumentStore:
250
251
 
251
252
  @overload
252
253
  def _execute_sql(
253
- self, cursor: Cursor, sql_query: Composed, params: tuple | None = None, error_msg: str = ""
254
+ self, cursor: Cursor, sql_query: SQL | Composed, params: tuple | None = None, error_msg: str = ""
254
255
  ) -> Cursor: ...
255
256
 
256
257
  @overload
257
258
  def _execute_sql(
258
- self, cursor: Cursor[DictRow], sql_query: Composed, params: tuple | None = None, error_msg: str = ""
259
+ self, cursor: Cursor[DictRow], sql_query: SQL | Composed, params: tuple | None = None, error_msg: str = ""
259
260
  ) -> Cursor[DictRow]: ...
260
261
 
261
262
  def _execute_sql(
262
263
  self,
263
264
  cursor: Cursor | Cursor[DictRow],
264
- sql_query: Composed,
265
+ sql_query: SQL | Composed,
265
266
  params: tuple | None = None,
266
267
  error_msg: str = "",
267
268
  ) -> Cursor | Cursor[DictRow]:
@@ -299,18 +300,18 @@ class PgvectorDocumentStore:
299
300
 
300
301
  @overload
301
302
  async def _execute_sql_async(
302
- self, cursor: AsyncCursor, sql_query: Composed, params: tuple | None = None, error_msg: str = ""
303
+ self, cursor: AsyncCursor, sql_query: SQL | Composed, params: tuple | None = None, error_msg: str = ""
303
304
  ) -> AsyncCursor: ...
304
305
 
305
306
  @overload
306
307
  async def _execute_sql_async(
307
- self, cursor: AsyncCursor[DictRow], sql_query: Composed, params: tuple | None = None, error_msg: str = ""
308
+ self, cursor: AsyncCursor[DictRow], sql_query: SQL | Composed, params: tuple | None = None, error_msg: str = ""
308
309
  ) -> AsyncCursor[DictRow]: ...
309
310
 
310
311
  async def _execute_sql_async(
311
312
  self,
312
313
  cursor: AsyncCursor | AsyncCursor[DictRow],
313
- sql_query: Composed,
314
+ sql_query: SQL | Composed,
314
315
  params: tuple | None = None,
315
316
  error_msg: str = "",
316
317
  ) -> AsyncCursor | AsyncCursor[DictRow]:
@@ -346,9 +347,10 @@ class PgvectorDocumentStore:
346
347
 
347
348
  return result
348
349
 
349
- def _ensure_db_setup(self):
350
+ def _ensure_db_setup(self) -> None:
350
351
  """
351
352
  Ensures that the connection to the PostgreSQL database exists and is valid.
353
+
352
354
  If not, connection and cursors are created.
353
355
  If the table is not initialized, it will be set up.
354
356
  """
@@ -384,9 +386,10 @@ class PgvectorDocumentStore:
384
386
  if not self._table_initialized:
385
387
  self._initialize_table()
386
388
 
387
- async def _ensure_db_setup_async(self):
389
+ async def _ensure_db_setup_async(self) -> None:
388
390
  """
389
391
  Async internal method.
392
+
390
393
  Ensures that the connection to the PostgreSQL database exists and is valid.
391
394
  If not, connection and cursors are created.
392
395
  If the table is not initialized, it will be set up.
@@ -426,7 +429,7 @@ class PgvectorDocumentStore:
426
429
  if not self._table_initialized:
427
430
  await self._initialize_table_async()
428
431
 
429
- def _build_table_creation_queries(self):
432
+ def _build_table_creation_queries(self) -> tuple[SQL, Composed, SQL, Composed]:
430
433
  """
431
434
  Internal method to build the SQL queries for table creation.
432
435
  """
@@ -453,7 +456,7 @@ class PgvectorDocumentStore:
453
456
 
454
457
  return sql_table_exists, sql_create_table, sql_keyword_index_exists, sql_create_keyword_index
455
458
 
456
- def _initialize_table(self):
459
+ def _initialize_table(self) -> None:
457
460
  """
458
461
  Internal method to initialize the table.
459
462
  """
@@ -497,7 +500,7 @@ class PgvectorDocumentStore:
497
500
 
498
501
  self._table_initialized = True
499
502
 
500
- async def _initialize_table_async(self):
503
+ async def _initialize_table_async(self) -> None:
501
504
  """
502
505
  Internal async method to initialize the table.
503
506
  """
@@ -547,9 +550,10 @@ class PgvectorDocumentStore:
547
550
 
548
551
  self._table_initialized = True
549
552
 
550
- def delete_table(self):
553
+ def delete_table(self) -> None:
551
554
  """
552
555
  Deletes the table used to store Haystack documents.
556
+
553
557
  The name of the schema (`schema_name`) and the name of the table (`table_name`)
554
558
  are defined when initializing the `PgvectorDocumentStore`.
555
559
  """
@@ -567,7 +571,7 @@ class PgvectorDocumentStore:
567
571
  error_msg=f"Could not delete table {self.schema_name}.{self.table_name} in PgvectorDocumentStore",
568
572
  )
569
573
 
570
- async def delete_table_async(self):
574
+ async def delete_table_async(self) -> None:
571
575
  """
572
576
  Async method to delete the table used to store Haystack documents.
573
577
  """
@@ -585,7 +589,7 @@ class PgvectorDocumentStore:
585
589
  error_msg=f"Could not delete table {self.schema_name}.{self.table_name} in PgvectorDocumentStore",
586
590
  )
587
591
 
588
- def _build_hnsw_queries(self):
592
+ def _build_hnsw_queries(self) -> tuple[Composed | None, SQL, Composed, Composed]:
589
593
  """Common method to build all HNSW-related SQL queries"""
590
594
 
591
595
  sql_set_hnsw_ef_search = (
@@ -633,9 +637,10 @@ class PgvectorDocumentStore:
633
637
 
634
638
  return sql_set_hnsw_ef_search, sql_hnsw_index_exists, sql_drop_hnsw_index, sql_create_hnsw_index
635
639
 
636
- def _handle_hnsw(self):
640
+ def _handle_hnsw(self) -> None:
637
641
  """
638
642
  Internal method to handle the HNSW index creation.
643
+
639
644
  It also sets the `hnsw.ef_search` parameter for queries if it is specified.
640
645
  """
641
646
 
@@ -645,7 +650,7 @@ class PgvectorDocumentStore:
645
650
 
646
651
  assert self._cursor is not None
647
652
 
648
- if self.hnsw_ef_search:
653
+ if sql_set_hnsw_ef_search is not None:
649
654
  self._execute_sql(
650
655
  cursor=self._cursor, sql_query=sql_set_hnsw_ef_search, error_msg="Could not set hnsw.ef_search"
651
656
  )
@@ -671,7 +676,7 @@ class PgvectorDocumentStore:
671
676
 
672
677
  self._execute_sql(cursor=self._cursor, sql_query=sql_create_hnsw_index, error_msg="Could not create HNSW index")
673
678
 
674
- async def _handle_hnsw_async(self):
679
+ async def _handle_hnsw_async(self) -> None:
675
680
  """
676
681
  Internal async method to handle the HNSW index creation.
677
682
  """
@@ -682,7 +687,7 @@ class PgvectorDocumentStore:
682
687
 
683
688
  assert self._async_cursor is not None
684
689
 
685
- if self.hnsw_ef_search:
690
+ if sql_set_hnsw_ef_search is not None:
686
691
  await self._execute_sql_async(
687
692
  cursor=self._async_cursor, sql_query=sql_set_hnsw_ef_search, error_msg="Could not set hnsw.ef_search"
688
693
  )
@@ -1346,10 +1351,13 @@ class PgvectorDocumentStore:
1346
1351
  # cosine_similarity and inner_product are modified from the result of the operator
1347
1352
  if vector_function == "cosine_similarity":
1348
1353
  score_definition = f"1 - (embedding <=> {query_embedding_for_postgres}) AS score"
1354
+ order_by_definition = f"embedding <=> {query_embedding_for_postgres}"
1349
1355
  elif vector_function == "inner_product":
1350
1356
  score_definition = f"(embedding <#> {query_embedding_for_postgres}) * -1 AS score"
1357
+ order_by_definition = f"embedding <#> {query_embedding_for_postgres}"
1351
1358
  elif vector_function == "l2_distance":
1352
1359
  score_definition = f"embedding <-> {query_embedding_for_postgres} AS score"
1360
+ order_by_definition = f"embedding <-> {query_embedding_for_postgres}"
1353
1361
 
1354
1362
  sql_select = SQL("SELECT *, {score} FROM {schema_name}.{table_name}").format(
1355
1363
  schema_name=Identifier(self.schema_name),
@@ -1362,13 +1370,9 @@ class PgvectorDocumentStore:
1362
1370
  if filters:
1363
1371
  sql_where_clause, params = _convert_filters_to_where_clause_and_params(filters)
1364
1372
 
1365
- # we always want to return the most similar documents first
1366
- # so when using l2_distance, the sort order must be ASC
1367
- sort_order = "ASC" if vector_function == "l2_distance" else "DESC"
1368
-
1369
- sql_sort = SQL(" ORDER BY score {sort_order} LIMIT {top_k}").format(
1373
+ sql_sort = SQL(" ORDER BY {order_by} ASC LIMIT {top_k}").format(
1370
1374
  top_k=SQLLiteral(top_k),
1371
- sort_order=SQL(sort_order),
1375
+ order_by=SQL(order_by_definition),
1372
1376
  )
1373
1377
 
1374
1378
  sql_query = sql_select + sql_where_clause + sql_sort
@@ -1418,8 +1422,9 @@ class PgvectorDocumentStore:
1418
1422
  vector_function: Literal["cosine_similarity", "inner_product", "l2_distance"] | None = None,
1419
1423
  ) -> list[Document]:
1420
1424
  """
1421
- Asynchronously retrieves documents that are most similar to the query embedding using a
1422
- vector similarity metric.
1425
+ Asynchronously retrieves documents that are most similar to the query embedding.
1426
+
1427
+ Uses a vector similarity metric for comparison.
1423
1428
  """
1424
1429
 
1425
1430
  sql_query, params = self._check_and_build_embedding_retrieval_query(
@@ -1439,7 +1444,7 @@ class PgvectorDocumentStore:
1439
1444
  docs = _from_pg_to_haystack_documents(records)
1440
1445
  return docs
1441
1446
 
1442
- def _prepare_filters_count_documents(self, filters):
1447
+ def _prepare_filters_count_documents(self, filters: dict[str, Any] | None) -> tuple[tuple[Any, ...], Composed]:
1443
1448
  _validate_filters(filters)
1444
1449
  sql_count = SQL("SELECT COUNT(*) FROM {schema_name}.{table_name}").format(
1445
1450
  schema_name=Identifier(self.schema_name), table_name=Identifier(self.table_name)
@@ -1571,8 +1576,9 @@ class PgvectorDocumentStore:
1571
1576
 
1572
1577
  def count_unique_metadata_by_filter(self, filters: dict[str, Any], metadata_fields: list[str]) -> dict[str, int]:
1573
1578
  """
1574
- Returns the count of unique values for each specified metadata field,
1575
- considering only documents that match the provided filters.
1579
+ Returns the count of unique values for each specified metadata field.
1580
+
1581
+ Considers only documents that match the provided filters.
1576
1582
 
1577
1583
  :param filters: The filters to apply to select documents.
1578
1584
  For filter syntax, see [Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering)
@@ -1604,8 +1610,9 @@ class PgvectorDocumentStore:
1604
1610
  self, filters: dict[str, Any], metadata_fields: list[str]
1605
1611
  ) -> dict[str, int]:
1606
1612
  """
1607
- Asynchronously returns the count of unique values for each specified metadata field,
1608
- considering only documents that match the provided filters.
1613
+ Asynchronously returns the count of unique values for each specified metadata field.
1614
+
1615
+ Considers only documents that match the provided filters.
1609
1616
 
1610
1617
  :param filters: The filters to apply to select documents.
1611
1618
  For filter syntax, see [Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering)
@@ -1661,7 +1668,7 @@ class PgvectorDocumentStore:
1661
1668
  :param records: List of database records containing 'meta' field.
1662
1669
  :returns: A dictionary mapping field names to their type information.
1663
1670
  """
1664
- fields_info: dict[str, dict[str, str]] = {"content": {"type": "text"}}
1671
+ fields_info: dict[str, dict[str, str]] = {}
1665
1672
 
1666
1673
  # Analyze metadata from all documents
1667
1674
  for record in records:
@@ -1681,7 +1688,7 @@ class PgvectorDocumentStore:
1681
1688
 
1682
1689
  return fields_info
1683
1690
 
1684
- def _analyze_metadata_from_docs_query(self):
1691
+ def _analyze_metadata_from_docs_query(self) -> Composed:
1685
1692
  # query all documents to analyze metadata structure
1686
1693
  sql_query = SQL("SELECT meta FROM {schema_name}.{table_name} WHERE meta IS NOT NULL").format(
1687
1694
  schema_name=Identifier(self.schema_name), table_name=Identifier(self.table_name)
@@ -1698,7 +1705,6 @@ class PgvectorDocumentStore:
1698
1705
  Example return:
1699
1706
  ```python
1700
1707
  {
1701
- 'content': {'type': 'text'},
1702
1708
  'category': {'type': 'text'},
1703
1709
  'status': {'type': 'text'},
1704
1710
  'priority': {'type': 'integer'},
@@ -1838,15 +1844,14 @@ class PgvectorDocumentStore:
1838
1844
  :returns: A dictionary with 'min' and 'max' keys containing the minimum and maximum values.
1839
1845
  For numeric fields (integer, real), returns numeric min/max.
1840
1846
  For text fields, returns lexicographic min/max based on database collation.
1841
- :raises ValueError: If the field doesn't exist or has no values.
1847
+ Returns `{"min": None, "max": None}` when the field has no values or the store is empty.
1842
1848
  """
1843
1849
  normalized_field = PgvectorDocumentStore._normalize_metadata_field_name(metadata_field)
1844
1850
 
1845
1851
  # Get field type information from metadata fields info
1846
1852
  fields_info = self.get_metadata_fields_info()
1847
1853
  if normalized_field not in fields_info:
1848
- msg = f"Metadata field '{metadata_field}' not found in document store"
1849
- raise ValueError(msg)
1854
+ return {"min": None, "max": None}
1850
1855
 
1851
1856
  field_type = fields_info[normalized_field]["type"]
1852
1857
  sql_query = self._build_min_max_query(normalized_field, field_type)
@@ -1871,15 +1876,14 @@ class PgvectorDocumentStore:
1871
1876
  :returns: A dictionary with 'min' and 'max' keys containing the minimum and maximum values.
1872
1877
  For numeric fields (integer, real), returns numeric min/max.
1873
1878
  For text fields, returns lexicographic min/max based on database collation.
1874
- :raises ValueError: If the field doesn't exist or has no values.
1879
+ Returns ``{"min": None, "max": None}`` when the field has no values or the store is empty.
1875
1880
  """
1876
1881
  normalized_field = PgvectorDocumentStore._normalize_metadata_field_name(metadata_field)
1877
1882
 
1878
1883
  # Get field type information from metadata fields info
1879
1884
  fields_info = await self.get_metadata_fields_info_async()
1880
1885
  if normalized_field not in fields_info:
1881
- msg = f"Metadata field '{metadata_field}' not found in document store"
1882
- raise ValueError(msg)
1886
+ return {"min": None, "max": None}
1883
1887
 
1884
1888
  field_type = fields_info[normalized_field]["type"]
1885
1889
  sql_query = self._build_min_max_query(normalized_field, field_type)