pgvector-haystack 8.1.0__tar.gz → 8.1.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 (27) hide show
  1. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/CHANGELOG.md +7 -0
  2. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/PKG-INFO +1 -1
  3. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/document_stores/pgvector/document_store.py +9 -9
  4. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_document_store.py +21 -0
  5. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_document_store_async.py +21 -0
  6. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/.gitignore +0 -0
  7. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/LICENSE.txt +0 -0
  8. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/README.md +0 -0
  9. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/examples/embedding_retrieval.py +0 -0
  10. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/examples/hybrid_retrieval.py +0 -0
  11. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/pydoc/config_docusaurus.yml +0 -0
  12. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/pyproject.toml +0 -0
  13. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  14. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
  15. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
  16. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  17. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  18. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/document_stores/pgvector/converters.py +0 -0
  19. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/document_stores/pgvector/filters.py +0 -0
  20. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/src/haystack_integrations/document_stores/py.typed +0 -0
  21. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/__init__.py +0 -0
  22. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/conftest.py +0 -0
  23. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_converters.py +0 -0
  24. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_filters.py +0 -0
  25. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_retrieval.py +0 -0
  26. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_retrieval_async.py +0 -0
  27. {pgvector_haystack-8.1.0 → pgvector_haystack-8.1.1}/tests/test_retrievers.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/pgvector-v8.1.0] - 2026-10-20
4
+
5
+ ### 🚀 Features
6
+
7
+ - *(pgvector)* Make PgvectorDocumentStore connection overridable for PostgreSQL-backed stores (#4129)
8
+
9
+
3
10
  ## [integrations/pgvector-v8.0.0] - 2026-10-16
4
11
 
5
12
  ### 🧪 Testing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pgvector-haystack
3
- Version: 8.1.0
3
+ Version: 8.1.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
@@ -1023,18 +1023,19 @@ class PgvectorDocumentStore:
1023
1023
  if not document_ids:
1024
1024
  return
1025
1025
 
1026
- document_ids_str = ", ".join(f"'{document_id}'" for document_id in document_ids)
1027
-
1028
- delete_sql = SQL("DELETE FROM {schema_name}.{table_name} WHERE id IN ({document_ids_str})").format(
1026
+ # pass the ids as a bound parameter, so they are never interpreted as SQL
1027
+ delete_sql = SQL("DELETE FROM {schema_name}.{table_name} WHERE id = ANY(%s)").format(
1029
1028
  schema_name=Identifier(self.schema_name),
1030
1029
  table_name=Identifier(self.table_name),
1031
- document_ids_str=SQL(document_ids_str),
1032
1030
  )
1033
1031
 
1034
1032
  self._ensure_db_setup()
1035
1033
  assert self._cursor is not None
1036
1034
  self._execute_sql(
1037
- cursor=self._cursor, sql_query=delete_sql, error_msg="Could not delete documents from PgvectorDocumentStore"
1035
+ cursor=self._cursor,
1036
+ sql_query=delete_sql,
1037
+ params=(document_ids,),
1038
+ error_msg="Could not delete documents from PgvectorDocumentStore",
1038
1039
  )
1039
1040
 
1040
1041
  async def delete_documents_async(self, document_ids: list[str]) -> None:
@@ -1046,12 +1047,10 @@ class PgvectorDocumentStore:
1046
1047
  if not document_ids:
1047
1048
  return
1048
1049
 
1049
- document_ids_str = ", ".join(f"'{document_id}'" for document_id in document_ids)
1050
-
1051
- delete_sql = SQL("DELETE FROM {schema_name}.{table_name} WHERE id IN ({document_ids_str})").format(
1050
+ # pass the ids as a bound parameter, so they are never interpreted as SQL
1051
+ delete_sql = SQL("DELETE FROM {schema_name}.{table_name} WHERE id = ANY(%s)").format(
1052
1052
  schema_name=Identifier(self.schema_name),
1053
1053
  table_name=Identifier(self.table_name),
1054
- document_ids_str=SQL(document_ids_str),
1055
1054
  )
1056
1055
 
1057
1056
  await self._ensure_db_setup_async()
@@ -1059,6 +1058,7 @@ class PgvectorDocumentStore:
1059
1058
  await self._execute_sql_async(
1060
1059
  cursor=self._async_cursor,
1061
1060
  sql_query=delete_sql,
1061
+ params=(document_ids,),
1062
1062
  error_msg="Could not delete documents from PgvectorDocumentStore",
1063
1063
  )
1064
1064
 
@@ -605,6 +605,27 @@ def test_delete_documents_empty_list_is_noop(document_store: PgvectorDocumentSto
605
605
  assert after == before
606
606
 
607
607
 
608
+ @pytest.mark.integration
609
+ def test_delete_documents_with_quotes_in_ids(document_store: PgvectorDocumentStore):
610
+ """
611
+ The ids are passed as a bound parameter, so quotes in them are not interpreted as SQL.
612
+ """
613
+ docs = [
614
+ Document(id="O'Brien", content="quote in id"),
615
+ Document(id="x') OR ('1'='1", content="injection-shaped id"),
616
+ Document(id="1", content="hello"),
617
+ Document(id="2", content="world"),
618
+ ]
619
+ document_store.write_documents(docs)
620
+
621
+ document_store.delete_documents(["O'Brien"])
622
+ assert sorted(doc.id for doc in document_store.filter_documents()) == ["1", "2", "x') OR ('1'='1"]
623
+
624
+ # only matches the document with this exact id, instead of every document
625
+ document_store.delete_documents(["x') OR ('1'='1"])
626
+ assert sorted(doc.id for doc in document_store.filter_documents()) == ["1", "2"]
627
+
628
+
608
629
  @pytest.mark.integration
609
630
  def test_update_by_filter_empty_meta_raises_error(document_store: PgvectorDocumentStore):
610
631
  docs = [Document(content="Doc 1", meta={"category": "A"})]
@@ -189,6 +189,27 @@ class TestDocumentStoreAsync(
189
189
  """
190
190
  await document_store.delete_table_async() # if throw error, test fails
191
191
 
192
+ async def test_delete_documents_async_with_quotes_in_ids(self, document_store: PgvectorDocumentStore):
193
+ """
194
+ The ids are passed as a bound parameter, so quotes in them are not interpreted as SQL.
195
+ """
196
+ docs = [
197
+ Document(id="O'Brien", content="quote in id"),
198
+ Document(id="x') OR ('1'='1", content="injection-shaped id"),
199
+ Document(id="1", content="hello"),
200
+ Document(id="2", content="world"),
201
+ ]
202
+ await document_store.write_documents_async(docs)
203
+
204
+ await document_store.delete_documents_async(["O'Brien"])
205
+ remaining = await document_store.filter_documents_async()
206
+ assert sorted(doc.id for doc in remaining) == ["1", "2", "x') OR ('1'='1"]
207
+
208
+ # only matches the document with this exact id, instead of every document
209
+ await document_store.delete_documents_async(["x') OR ('1'='1"])
210
+ remaining = await document_store.filter_documents_async()
211
+ assert sorted(doc.id for doc in remaining) == ["1", "2"]
212
+
192
213
  async def test_close_async_and_reopen(self, document_store: PgvectorDocumentStore):
193
214
  assert await document_store.count_documents_async() == 0
194
215
  assert document_store._async_connection is not None