pgvector-haystack 6.6.0__tar.gz → 7.0.0__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 (27) hide show
  1. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/CHANGELOG.md +81 -16
  2. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/PKG-INFO +1 -1
  3. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +26 -11
  4. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/filters.py +4 -0
  5. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_document_store.py +21 -0
  6. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_document_store_async.py +22 -0
  7. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_filters.py +18 -0
  8. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/.gitignore +0 -0
  9. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/LICENSE.txt +0 -0
  10. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/README.md +0 -0
  11. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/examples/embedding_retrieval.py +0 -0
  12. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/examples/hybrid_retrieval.py +0 -0
  13. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/pydoc/config_docusaurus.yml +0 -0
  14. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/pyproject.toml +0 -0
  15. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  16. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
  17. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
  18. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  19. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  20. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/converters.py +0 -0
  21. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  22. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/__init__.py +0 -0
  23. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/conftest.py +0 -0
  24. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_converters.py +0 -0
  25. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_retrieval.py +0 -0
  26. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_retrieval_async.py +0 -0
  27. {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_retrievers.py +0 -0
@@ -1,5 +1,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/pgvector-v6.6.1] - 2026-09-25
4
+
5
+ ### 🐛 Bug Fixes
6
+
7
+ - *(pgvector)* Raise FilterError on empty logical conditions (#3946)
8
+ - *(pgvector)* Count every written document in write_documents_async (#3964)
9
+
10
+ ### ⚙️ CI
11
+
12
+ - Improve changelog generation; fix existing changelogs (#3883)
13
+
14
+
15
+ ## [integrations/pgvector-v6.6.0] - 2026-08-26
16
+
17
+ ### 🐛 Bug Fixes
18
+
19
+ - PGVector adding optional parameter `filters` to `get_metadata_field_unique_values()` (#3735)
20
+ - Standardize license files (#3771)
21
+
22
+ ### 🧪 Testing
23
+
24
+ - `PGVectorDocumentStore` removing duplicated tests already covered by haystack Mixin tests (#3783)
25
+
26
+ ### 🧹 Chores
27
+
28
+ - Re-standardize READMEs (#3768)
29
+
30
+
3
31
  ## [integrations/pgvector-v6.5.0] - 2026-08-03
4
32
 
5
33
  ### 🚀 Features
@@ -147,9 +175,14 @@
147
175
 
148
176
  ### 🧹 Chores
149
177
 
150
- - Remove black (#1985)
151
178
  - Pgvector - update docker image in examples (#1995)
152
179
 
180
+ ## [integrations/pgvector-v5.2.0] - 2025-06-26
181
+
182
+ ### 🧹 Chores
183
+
184
+ - Remove black (#1985)
185
+
153
186
 
154
187
  ## [integrations/pgvector-v3.4.1] - 2025-06-20
155
188
 
@@ -253,48 +286,75 @@
253
286
 
254
287
  ### 🚀 Features
255
288
 
256
- - Add filter_policy to pgvector integration (#820)
257
289
  - Add schema support to pgvector document store. (#1095)
258
290
  - Pgvector - recreate the connection if it is no longer valid (#1202)
259
291
 
260
- ### 🐛 Bug Fixes
261
-
262
- - `PgVector` - Fallback to default filter policy when deserializing retrievers without the init parameter (#900)
263
-
264
292
  ### 📚 Documentation
265
293
 
266
294
  - Explain different connection string formats in the docstring (#1132)
267
295
 
296
+ ### ⚙️ CI
297
+
298
+ - Adopt uv as installer (#1142)
299
+
300
+ ### 🧹 Chores
301
+
302
+ - Update changelog after removing legacy filters (#1083)
303
+ - Update ruff linting scripts and settings (#1105)
304
+
305
+ ## [integrations/pgvector-v1.0.0] - 2024-09-12
306
+
268
307
  ### 🧪 Testing
269
308
 
270
309
  - Do not retry tests in `hatch run test` command (#954)
271
310
 
311
+ ### 🧹 Chores
312
+
313
+ - PgVector - remove legacy filter support (#1068)
314
+
315
+ ### 🌀 Miscellaneous
316
+
317
+ - Chore: Update pgvector test for the new `apply_filter_policy` usage (#970)
318
+ - Chore: pgvector ruff update, don't ruff tests (#984)
319
+
320
+ ## [integrations/pgvector-v0.5.1] - 2024-07-17
321
+
322
+ ### 🐛 Bug Fixes
323
+
324
+ - `PgVector` - Fallback to default filter policy when deserializing retrievers without the init parameter (#900)
325
+
326
+ ## [integrations/pgvector-v0.5.0] - 2024-07-15
327
+
328
+ ### 🚀 Features
329
+
330
+ - Add filter_policy to pgvector integration (#820)
331
+
272
332
  ### ⚙️ CI
273
333
 
274
334
  - Retry tests to reduce flakyness (#836)
275
- - Adopt uv as installer (#1142)
276
335
 
277
336
  ### 🧹 Chores
278
337
 
279
338
  - Update ruff invocation to include check parameter (#853)
280
- - PgVector - remove legacy filter support (#1068)
281
- - Update changelog after removing legacy filters (#1083)
282
- - Update ruff linting scripts and settings (#1105)
283
339
 
284
340
  ### 🌀 Miscellaneous
285
341
 
286
342
  - Ci: install `pytest-rerunfailures` where needed; add retry config to `test-cov` script (#845)
287
343
  - Chore: Minor retriever pydoc fix (#884)
288
- - Chore: Update pgvector test for the new `apply_filter_policy` usage (#970)
289
- - Chore: pgvector ruff update, don't ruff tests (#984)
290
344
 
291
345
  ## [integrations/pgvector-v0.4.0] - 2024-06-20
292
346
 
293
347
  ### 🚀 Features
294
348
 
295
- - Defer the database connection to when it's needed (#773)
296
349
  - Add customizable index names for pgvector (#818)
297
350
 
351
+
352
+ ## [integrations/pgvector-v0.3.0] - 2024-05-30
353
+
354
+ ### 🚀 Features
355
+
356
+ - Defer the database connection to when it's needed (#773)
357
+
298
358
  ### 🌀 Miscellaneous
299
359
 
300
360
  - Docs: add missing api references (#728)
@@ -328,6 +388,14 @@
328
388
 
329
389
  ## [integrations/pgvector-v0.1.0] - 2024-02-14
330
390
 
391
+ ### 🌀 Miscellaneous
392
+
393
+ - Pgvector: generate API docs (#325)
394
+ - Pgvector: add an example (#334)
395
+ - Adopt `Secret` to pgvector (#402)
396
+
397
+ ## [integrations/pgvector-v0.0.1] - 2024-02-02
398
+
331
399
  ### 🐛 Bug Fixes
332
400
 
333
401
  - Pgvector: fix linting (#328)
@@ -338,8 +406,5 @@
338
406
  - Pgvector - filters (#257)
339
407
  - Pgvector - embedding retrieval (#298)
340
408
  - Pgvector - Embedding Retriever (#320)
341
- - Pgvector: generate API docs (#325)
342
- - Pgvector: add an example (#334)
343
- - Adopt `Secret` to pgvector (#402)
344
409
 
345
410
  <!-- generated by git-cliff -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pgvector-haystack
3
- Version: 6.6.0
3
+ Version: 7.0.0
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
@@ -9,6 +9,7 @@ from haystack import default_from_dict, default_to_dict, logging
9
9
  from haystack.dataclasses.document import Document
10
10
  from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
11
11
  from haystack.document_stores.types import DuplicatePolicy
12
+ from haystack.errors import FilterError
12
13
  from haystack.utils.auth import Secret, deserialize_secrets_inplace
13
14
  from psycopg import AsyncConnection, Connection, Cursor, Error, IntegrityError
14
15
  from psycopg.cursor_async import AsyncCursor
@@ -923,7 +924,7 @@ class PgvectorDocumentStore:
923
924
  )
924
925
  raise DocumentStoreError(error_msg) from e
925
926
 
926
- # get the number of the inserted documents, inspired by psycopg3 docs
927
+ # executemany(returning=True) yields one result set per document, so walk them all with nextset()
927
928
  # https://www.psycopg.org/psycopg3/docs/api/cursors.html#psycopg.Cursor.executemany
928
929
  written_docs = 0
929
930
  while True:
@@ -980,9 +981,14 @@ class PgvectorDocumentStore:
980
981
  )
981
982
  raise DocumentStoreError(error_msg) from e
982
983
 
984
+ # executemany(returning=True) yields one result set per document, so walk them all with nextset()
985
+ # https://www.psycopg.org/psycopg3/docs/api/cursors.html#psycopg.Cursor.executemany
983
986
  written_docs = 0
984
- async for _ in self._async_cursor:
985
- written_docs += 1
987
+ while True:
988
+ if await self._async_cursor.fetchone():
989
+ written_docs += 1
990
+ if not self._async_cursor.nextset():
991
+ break
986
992
 
987
993
  return written_docs
988
994
 
@@ -1073,18 +1079,21 @@ class PgvectorDocumentStore:
1073
1079
  :param filters: The filters to apply to select documents for deletion.
1074
1080
  For filter syntax, see [Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering)
1075
1081
  :returns: The number of documents deleted.
1082
+ :raises FilterError: If `filters` is empty. Use `delete_all_documents()` to delete everything.
1076
1083
  """
1077
1084
  _validate_filters(filters)
1085
+ if not filters:
1086
+ # An empty filter would compile to an unqualified DELETE and empty the table
1087
+ msg = "delete_by_filter requires a non-empty filter. Use delete_all_documents() to delete every document."
1088
+ raise FilterError(msg)
1078
1089
 
1079
1090
  delete_sql = SQL("DELETE FROM {schema_name}.{table_name}").format(
1080
1091
  schema_name=Identifier(self.schema_name),
1081
1092
  table_name=Identifier(self.table_name),
1082
1093
  )
1083
1094
 
1084
- params = ()
1085
- if filters:
1086
- sql_where_clause, params = _convert_filters_to_where_clause_and_params(filters)
1087
- delete_sql += sql_where_clause
1095
+ sql_where_clause, params = _convert_filters_to_where_clause_and_params(filters)
1096
+ delete_sql += sql_where_clause
1088
1097
 
1089
1098
  self._ensure_db_setup()
1090
1099
  assert self._cursor is not None
@@ -1115,18 +1124,24 @@ class PgvectorDocumentStore:
1115
1124
  :param filters: The filters to apply to select documents for deletion.
1116
1125
  For filter syntax, see [Haystack metadata filtering](https://docs.haystack.deepset.ai/docs/metadata-filtering)
1117
1126
  :returns: The number of documents deleted.
1127
+ :raises FilterError: If `filters` is empty. Use `delete_all_documents_async()` to delete everything.
1118
1128
  """
1119
1129
  _validate_filters(filters)
1130
+ if not filters:
1131
+ # An empty filter would compile to an unqualified DELETE and empty the table
1132
+ msg = (
1133
+ "delete_by_filter_async requires a non-empty filter. "
1134
+ "Use delete_all_documents_async() to delete every document."
1135
+ )
1136
+ raise FilterError(msg)
1120
1137
 
1121
1138
  delete_sql = SQL("DELETE FROM {schema_name}.{table_name}").format(
1122
1139
  schema_name=Identifier(self.schema_name),
1123
1140
  table_name=Identifier(self.table_name),
1124
1141
  )
1125
1142
 
1126
- params = ()
1127
- if filters:
1128
- sql_where_clause, params = _convert_filters_to_where_clause_and_params(filters)
1129
- delete_sql += sql_where_clause
1143
+ sql_where_clause, params = _convert_filters_to_where_clause_and_params(filters)
1144
+ delete_sql += sql_where_clause
1130
1145
 
1131
1146
  await self._ensure_db_setup_async()
1132
1147
  assert self._async_cursor is not None
@@ -71,6 +71,10 @@ def _parse_logical_condition(condition: dict[str, Any]) -> tuple[Composed, list[
71
71
  msg = f"Unknown logical operator '{operator}'. Valid operators are: 'AND', 'OR'"
72
72
  raise FilterError(msg)
73
73
 
74
+ if not condition["conditions"]:
75
+ msg = f"'conditions' must not be empty in {condition}"
76
+ raise FilterError(msg)
77
+
74
78
  # logical conditions can be nested, so we need to parse them recursively
75
79
  conditions = []
76
80
  for c in condition["conditions"]:
@@ -9,6 +9,7 @@ import pytest
9
9
  from haystack.dataclasses.document import ByteStream, Document
10
10
  from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
11
11
  from haystack.document_stores.types import DuplicatePolicy
12
+ from haystack.errors import FilterError
12
13
  from haystack.testing.document_store import (
13
14
  CountDocumentsByFilterTest,
14
15
  CountDocumentsTest,
@@ -66,6 +67,20 @@ class TestDocumentStore(
66
67
  with pytest.raises(DuplicateDocumentError):
67
68
  document_store.write_documents(docs, DuplicatePolicy.FAIL)
68
69
 
70
+ def test_write_documents_counts_every_document(self, document_store: PgvectorDocumentStore):
71
+ docs = [Document(id="1"), Document(id="2"), Document(id="3")]
72
+ assert document_store.write_documents(docs) == 3
73
+
74
+ def test_write_documents_skip_counts_only_new_documents(self, document_store: PgvectorDocumentStore):
75
+ document_store.write_documents([Document(id="1")])
76
+ docs = [Document(id="1"), Document(id="2"), Document(id="3")]
77
+ assert document_store.write_documents(docs, DuplicatePolicy.SKIP) == 2
78
+
79
+ def test_write_documents_overwrite_counts_every_document(self, document_store: PgvectorDocumentStore):
80
+ document_store.write_documents([Document(id="1", content="old")])
81
+ docs = [Document(id="1", content="new"), Document(id="2"), Document(id="3")]
82
+ assert document_store.write_documents(docs, DuplicatePolicy.OVERWRITE) == 3
83
+
69
84
  def test_get_metadata_field_unique_values_distinct_types(self, document_store: PgvectorDocumentStore):
70
85
  """
71
86
  Override: the base mixin test stores int, float, str and bool under the *same* metadata field
@@ -151,6 +166,12 @@ class TestDocumentStore(
151
166
  assert document_store._connection is not None
152
167
 
153
168
 
169
+ def test_delete_by_filter_rejects_an_empty_filter(mock_store):
170
+ """An empty filter would compile to an unqualified DELETE and empty the table."""
171
+ with pytest.raises(FilterError, match="non-empty filter"):
172
+ mock_store.delete_by_filter({})
173
+
174
+
154
175
  @pytest.mark.usefixtures("patches_for_unit_tests")
155
176
  def test_init(monkeypatch):
156
177
  monkeypatch.setenv("PG_CONN_STR", "some_connection_string")
@@ -9,6 +9,7 @@ import pytest
9
9
  from haystack.dataclasses.document import ByteStream, Document
10
10
  from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
11
11
  from haystack.document_stores.types import DuplicatePolicy
12
+ from haystack.errors import FilterError
12
13
  from haystack.testing.document_store_async import (
13
14
  CountDocumentsAsyncTest,
14
15
  CountDocumentsByFilterAsyncTest,
@@ -71,6 +72,20 @@ class TestDocumentStoreAsync(
71
72
  with pytest.raises(DuplicateDocumentError):
72
73
  await document_store.write_documents_async(docs, DuplicatePolicy.FAIL)
73
74
 
75
+ async def test_write_documents_async_counts_every_document(self, document_store: PgvectorDocumentStore):
76
+ docs = [Document(id="1"), Document(id="2"), Document(id="3")]
77
+ assert await document_store.write_documents_async(docs) == 3
78
+
79
+ async def test_write_documents_async_skip_counts_only_new_documents(self, document_store: PgvectorDocumentStore):
80
+ await document_store.write_documents_async([Document(id="1")])
81
+ docs = [Document(id="1"), Document(id="2"), Document(id="3")]
82
+ assert await document_store.write_documents_async(docs, DuplicatePolicy.SKIP) == 2
83
+
84
+ async def test_write_documents_async_overwrite_counts_every_document(self, document_store: PgvectorDocumentStore):
85
+ await document_store.write_documents_async([Document(id="1", content="old")])
86
+ docs = [Document(id="1", content="new"), Document(id="2"), Document(id="3")]
87
+ assert await document_store.write_documents_async(docs, DuplicatePolicy.OVERWRITE) == 3
88
+
74
89
  async def test_get_metadata_field_unique_values_distinct_types_async(self, document_store: PgvectorDocumentStore):
75
90
  """
76
91
  Override: the base mixin test stores int, float, str and bool under the *same* metadata field
@@ -353,6 +368,13 @@ async def test_write_documents_async_rejects_non_document_items(mock_store):
353
368
  await mock_store.write_documents_async([{"not": "a document"}])
354
369
 
355
370
 
371
+ @pytest.mark.asyncio
372
+ async def test_delete_by_filter_async_rejects_an_empty_filter(mock_store):
373
+ """Same contract as the sync path, and the message must name the async methods."""
374
+ with pytest.raises(FilterError, match=r"delete_by_filter_async requires .* delete_all_documents_async\(\)"):
375
+ await mock_store.delete_by_filter_async({})
376
+
377
+
356
378
  @pytest.mark.asyncio
357
379
  async def test_count_unique_metadata_by_filter_async_rejects_empty_fields(mock_store):
358
380
  with pytest.raises(ValueError, match="metadata_fields must be a non-empty list"):
@@ -483,3 +483,21 @@ def test_validate_filters():
483
483
  invalid_filters = {"field": "meta.number", "value": "100"}
484
484
  with pytest.raises(ValueError):
485
485
  _validate_filters(invalid_filters)
486
+
487
+
488
+ @pytest.mark.parametrize("operator", ["AND", "OR"])
489
+ def test_logical_condition_empty_conditions_raises(operator):
490
+ with pytest.raises(FilterError):
491
+ _parse_logical_condition({"operator": operator, "conditions": []})
492
+
493
+
494
+ def test_logical_condition_nested_empty_conditions_raises():
495
+ filters = {
496
+ "operator": "AND",
497
+ "conditions": [
498
+ {"field": "meta.type", "operator": "==", "value": "article"},
499
+ {"operator": "OR", "conditions": []},
500
+ ],
501
+ }
502
+ with pytest.raises(FilterError):
503
+ _convert_filters_to_where_clause_and_params(filters)