pgvector-haystack 6.1.0__tar.gz → 6.2.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.
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/CHANGELOG.md +7 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/PKG-INFO +2 -2
- pgvector_haystack-6.2.0/pydoc/config_docusaurus.yml +15 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/pyproject.toml +6 -2
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/document_stores/pgvector/converters.py +5 -1
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/document_stores/pgvector/filters.py +9 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_converters.py +17 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_document_store.py +18 -218
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_filters.py +5 -0
- pgvector_haystack-6.1.0/pydoc/config_docusaurus.yml +0 -30
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/.gitignore +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/LICENSE.txt +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/README.md +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/examples/embedding_retrieval.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/examples/hybrid_retrieval.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/__init__.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/conftest.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_document_store_async.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_retrieval.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_retrieval_async.py +0 -0
- {pgvector_haystack-6.1.0 → pgvector_haystack-6.2.0}/tests/test_retrievers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pgvector-haystack
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.2.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
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
19
19
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
20
20
|
Requires-Python: >=3.10
|
|
21
|
-
Requires-Dist: haystack-ai>=2.
|
|
21
|
+
Requires-Dist: haystack-ai>=2.24.0
|
|
22
22
|
Requires-Dist: pgvector>=0.3.0
|
|
23
23
|
Requires-Dist: psycopg[binary]
|
|
24
24
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
loaders:
|
|
2
|
+
- modules:
|
|
3
|
+
- haystack_integrations.components.retrievers.pgvector.embedding_retriever
|
|
4
|
+
- haystack_integrations.components.retrievers.pgvector.keyword_retriever
|
|
5
|
+
- haystack_integrations.document_stores.pgvector.document_store
|
|
6
|
+
search_path: [../src]
|
|
7
|
+
processors:
|
|
8
|
+
- type: filter
|
|
9
|
+
documented_only: true
|
|
10
|
+
skip_empty_modules: true
|
|
11
|
+
renderer:
|
|
12
|
+
description: Pgvector integration for Haystack
|
|
13
|
+
id: integrations-pgvector
|
|
14
|
+
filename: pgvector.md
|
|
15
|
+
title: Pgvector
|
|
@@ -22,7 +22,11 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
23
23
|
"Programming Language :: Python :: Implementation :: PyPy",
|
|
24
24
|
]
|
|
25
|
-
dependencies = [
|
|
25
|
+
dependencies = [
|
|
26
|
+
"haystack-ai>=2.24.0",
|
|
27
|
+
"pgvector>=0.3.0",
|
|
28
|
+
"psycopg[binary]"
|
|
29
|
+
]
|
|
26
30
|
|
|
27
31
|
[project.urls]
|
|
28
32
|
Source = "https://github.com/deepset-ai/haystack-core-integrations"
|
|
@@ -45,7 +49,7 @@ installer = "uv"
|
|
|
45
49
|
dependencies = ["haystack-pydoc-tools", "ruff"]
|
|
46
50
|
|
|
47
51
|
[tool.hatch.envs.default.scripts]
|
|
48
|
-
docs = ["pydoc
|
|
52
|
+
docs = ["haystack-pydoc pydoc/config_docusaurus.yml"]
|
|
49
53
|
fmt = "ruff check --fix {args}; ruff format {args}"
|
|
50
54
|
fmt-check = "ruff check {args} && ruff format --check {args}"
|
|
51
55
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from dataclasses import replace
|
|
1
2
|
from typing import Any
|
|
2
3
|
|
|
3
4
|
from haystack import logging
|
|
@@ -22,6 +23,9 @@ def _from_haystack_to_pg_documents(documents: list[Document]) -> list[dict[str,
|
|
|
22
23
|
db_document["blob_meta"] = Jsonb(blob.meta) if blob and blob.meta else None
|
|
23
24
|
db_document["blob_mime_type"] = blob.mime_type if blob and blob.mime_type else None
|
|
24
25
|
db_document["meta"] = Jsonb(db_document["meta"])
|
|
26
|
+
# PostgreSQL text fields cannot contain NUL (0x00) bytes, removing NUL bytes
|
|
27
|
+
if content := db_document["content"]:
|
|
28
|
+
db_document["content"] = content.replace("\x00", "")
|
|
25
29
|
|
|
26
30
|
if "sparse_embedding" in db_document:
|
|
27
31
|
sparse_embedding = db_document.pop("sparse_embedding", None)
|
|
@@ -65,7 +69,7 @@ def _from_pg_to_haystack_documents(documents: list[dict[str, Any]]) -> list[Docu
|
|
|
65
69
|
|
|
66
70
|
if blob_data:
|
|
67
71
|
blob = ByteStream(data=blob_data, meta=blob_meta, mime_type=blob_mime_type)
|
|
68
|
-
haystack_document
|
|
72
|
+
haystack_document = replace(haystack_document, blob=blob)
|
|
69
73
|
|
|
70
74
|
haystack_documents.append(haystack_document)
|
|
71
75
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2023-present deepset GmbH <info@deepset.ai>
|
|
2
2
|
#
|
|
3
3
|
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
import re
|
|
4
6
|
from datetime import datetime
|
|
5
7
|
from itertools import chain
|
|
6
8
|
from typing import Any, Literal
|
|
@@ -19,6 +21,7 @@ PYTHON_TYPES_TO_PG_TYPES = {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
NO_VALUE = "no_value"
|
|
24
|
+
SAFE_META_FIELD_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
def _validate_filters(filters: dict[str, Any] | None = None) -> None:
|
|
@@ -128,6 +131,12 @@ def _treat_meta_field(field: str, value: Any) -> str:
|
|
|
128
131
|
|
|
129
132
|
# use the ->> operator to access keys in the meta JSONB field
|
|
130
133
|
field_name = field.split(".", 1)[-1]
|
|
134
|
+
if not SAFE_META_FIELD_RE.match(field_name):
|
|
135
|
+
msg = (
|
|
136
|
+
f"Invalid metadata field name '{field_name}'. "
|
|
137
|
+
"Only alphanumeric characters, dashes, and underscores are allowed."
|
|
138
|
+
)
|
|
139
|
+
raise FilterError(msg)
|
|
131
140
|
field = f"meta->>'{field_name}'"
|
|
132
141
|
|
|
133
142
|
# meta fields are stored as strings in the JSONB field,
|
|
@@ -30,6 +30,13 @@ def test_from_haystack_to_pg_documents():
|
|
|
30
30
|
embedding=[0.7, 0.8, 0.9],
|
|
31
31
|
score=0.7,
|
|
32
32
|
),
|
|
33
|
+
Document(
|
|
34
|
+
id="4",
|
|
35
|
+
content="This is another text\x00",
|
|
36
|
+
meta={"meta_key": "meta_value"},
|
|
37
|
+
embedding=[0.7, 0.8, 0.9],
|
|
38
|
+
score=0.8,
|
|
39
|
+
),
|
|
33
40
|
]
|
|
34
41
|
|
|
35
42
|
pg_docs = _from_haystack_to_pg_documents(haystack_docs)
|
|
@@ -64,6 +71,16 @@ def test_from_haystack_to_pg_documents():
|
|
|
64
71
|
assert pg_docs[2]["embedding"] == [0.7, 0.8, 0.9]
|
|
65
72
|
assert "score" not in pg_docs[2]
|
|
66
73
|
|
|
74
|
+
assert pg_docs[3]["id"] == "4"
|
|
75
|
+
assert pg_docs[3]["content"] == "This is another text"
|
|
76
|
+
assert pg_docs[3]["blob_data"] is None
|
|
77
|
+
assert pg_docs[3]["blob_meta"] is None
|
|
78
|
+
assert pg_docs[3]["blob_mime_type"] is None
|
|
79
|
+
assert "dataframe" not in pg_docs[3]
|
|
80
|
+
assert pg_docs[3]["meta"].obj == {"meta_key": "meta_value"}
|
|
81
|
+
assert pg_docs[3]["embedding"] == [0.7, 0.8, 0.9]
|
|
82
|
+
assert "score" not in pg_docs[3]
|
|
83
|
+
|
|
67
84
|
|
|
68
85
|
def test_from_pg_to_haystack_documents():
|
|
69
86
|
pg_docs = [
|
|
@@ -9,14 +9,30 @@ 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.testing.document_store import
|
|
12
|
+
from haystack.testing.document_store import (
|
|
13
|
+
CountDocumentsTest,
|
|
14
|
+
DeleteAllTest,
|
|
15
|
+
DeleteByFilterTest,
|
|
16
|
+
DeleteDocumentsTest,
|
|
17
|
+
FilterableDocsFixtureMixin,
|
|
18
|
+
UpdateByFilterTest,
|
|
19
|
+
WriteDocumentsTest,
|
|
20
|
+
)
|
|
13
21
|
from haystack.utils import Secret
|
|
14
22
|
|
|
15
23
|
from haystack_integrations.document_stores.pgvector import PgvectorDocumentStore
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
@pytest.mark.integration
|
|
19
|
-
class TestDocumentStore(
|
|
27
|
+
class TestDocumentStore(
|
|
28
|
+
CountDocumentsTest,
|
|
29
|
+
DeleteAllTest,
|
|
30
|
+
DeleteByFilterTest,
|
|
31
|
+
DeleteDocumentsTest,
|
|
32
|
+
FilterableDocsFixtureMixin,
|
|
33
|
+
UpdateByFilterTest,
|
|
34
|
+
WriteDocumentsTest,
|
|
35
|
+
):
|
|
20
36
|
def test_write_documents(self, document_store: PgvectorDocumentStore):
|
|
21
37
|
docs = [Document(id="1")]
|
|
22
38
|
assert document_store.write_documents(docs) == 1
|
|
@@ -53,13 +69,6 @@ class TestDocumentStore(CountDocumentsTest, WriteDocumentsTest, DeleteDocumentsT
|
|
|
53
69
|
same_connection = document_store._connection
|
|
54
70
|
assert same_connection is document_store._connection
|
|
55
71
|
|
|
56
|
-
def test_delete_all_documents(self, document_store: PgvectorDocumentStore) -> None:
|
|
57
|
-
document_store.write_documents([Document(id=str(i)) for i in range(10)])
|
|
58
|
-
document_store.delete_all_documents()
|
|
59
|
-
assert document_store.count_documents() == 0
|
|
60
|
-
document_store.write_documents([Document(id="1")])
|
|
61
|
-
assert document_store.count_documents() == 1
|
|
62
|
-
|
|
63
72
|
def test_invalid_connection_string(self, monkeypatch):
|
|
64
73
|
monkeypatch.setenv("PG_CONN_STR", "invalid_connection_string")
|
|
65
74
|
document_store = PgvectorDocumentStore()
|
|
@@ -262,215 +271,6 @@ def test_delete_table_first_call(document_store):
|
|
|
262
271
|
document_store.delete_table() # if throw error, test fails
|
|
263
272
|
|
|
264
273
|
|
|
265
|
-
@pytest.mark.integration
|
|
266
|
-
def test_delete_by_filter(document_store: PgvectorDocumentStore):
|
|
267
|
-
docs = [
|
|
268
|
-
Document(content="Doc 1", meta={"category": "A", "year": 2023}),
|
|
269
|
-
Document(content="Doc 2", meta={"category": "B", "year": 2023}),
|
|
270
|
-
Document(content="Doc 3", meta={"category": "A", "year": 2024}),
|
|
271
|
-
]
|
|
272
|
-
document_store.write_documents(docs)
|
|
273
|
-
assert document_store.count_documents() == 3
|
|
274
|
-
|
|
275
|
-
deleted_count = document_store.delete_by_filter(filters={"field": "meta.category", "operator": "==", "value": "A"})
|
|
276
|
-
assert deleted_count == 2
|
|
277
|
-
assert document_store.count_documents() == 1
|
|
278
|
-
|
|
279
|
-
remaining_docs = document_store.filter_documents()
|
|
280
|
-
assert len(remaining_docs) == 1
|
|
281
|
-
assert remaining_docs[0].meta["category"] == "B"
|
|
282
|
-
|
|
283
|
-
deleted_count = document_store.delete_by_filter(filters={"field": "meta.year", "operator": "==", "value": 2023})
|
|
284
|
-
assert deleted_count == 1
|
|
285
|
-
assert document_store.count_documents() == 0
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
@pytest.mark.integration
|
|
289
|
-
def test_delete_by_filter_no_matches(document_store: PgvectorDocumentStore):
|
|
290
|
-
docs = [
|
|
291
|
-
Document(content="Doc 1", meta={"category": "A"}),
|
|
292
|
-
Document(content="Doc 2", meta={"category": "B"}),
|
|
293
|
-
]
|
|
294
|
-
document_store.write_documents(docs)
|
|
295
|
-
assert document_store.count_documents() == 2
|
|
296
|
-
|
|
297
|
-
deleted_count = document_store.delete_by_filter(filters={"field": "meta.category", "operator": "==", "value": "C"})
|
|
298
|
-
assert deleted_count == 0
|
|
299
|
-
assert document_store.count_documents() == 2
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
@pytest.mark.integration
|
|
303
|
-
def test_delete_by_filter_advanced_filters(document_store: PgvectorDocumentStore):
|
|
304
|
-
docs = [
|
|
305
|
-
Document(content="Doc 1", meta={"category": "A", "year": 2023, "status": "draft"}),
|
|
306
|
-
Document(content="Doc 2", meta={"category": "A", "year": 2024, "status": "published"}),
|
|
307
|
-
Document(content="Doc 3", meta={"category": "B", "year": 2023, "status": "draft"}),
|
|
308
|
-
]
|
|
309
|
-
document_store.write_documents(docs)
|
|
310
|
-
assert document_store.count_documents() == 3
|
|
311
|
-
|
|
312
|
-
# AND condition
|
|
313
|
-
deleted_count = document_store.delete_by_filter(
|
|
314
|
-
filters={
|
|
315
|
-
"operator": "AND",
|
|
316
|
-
"conditions": [
|
|
317
|
-
{"field": "meta.category", "operator": "==", "value": "A"},
|
|
318
|
-
{"field": "meta.year", "operator": "==", "value": 2023},
|
|
319
|
-
],
|
|
320
|
-
}
|
|
321
|
-
)
|
|
322
|
-
assert deleted_count == 1
|
|
323
|
-
assert document_store.count_documents() == 2
|
|
324
|
-
|
|
325
|
-
# OR condition
|
|
326
|
-
deleted_count = document_store.delete_by_filter(
|
|
327
|
-
filters={
|
|
328
|
-
"operator": "OR",
|
|
329
|
-
"conditions": [
|
|
330
|
-
{"field": "meta.category", "operator": "==", "value": "B"},
|
|
331
|
-
{"field": "meta.status", "operator": "==", "value": "published"},
|
|
332
|
-
],
|
|
333
|
-
}
|
|
334
|
-
)
|
|
335
|
-
assert deleted_count == 2
|
|
336
|
-
assert document_store.count_documents() == 0
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
@pytest.mark.integration
|
|
340
|
-
def test_update_by_filter(document_store: PgvectorDocumentStore):
|
|
341
|
-
docs = [
|
|
342
|
-
Document(content="Doc 1", meta={"category": "A", "status": "draft"}),
|
|
343
|
-
Document(content="Doc 2", meta={"category": "B", "status": "draft"}),
|
|
344
|
-
Document(content="Doc 3", meta={"category": "A", "status": "draft"}),
|
|
345
|
-
]
|
|
346
|
-
document_store.write_documents(docs)
|
|
347
|
-
assert document_store.count_documents() == 3
|
|
348
|
-
|
|
349
|
-
# update status for category="A" documents
|
|
350
|
-
updated_count = document_store.update_by_filter(
|
|
351
|
-
filters={"field": "meta.category", "operator": "==", "value": "A"}, meta={"status": "published"}
|
|
352
|
-
)
|
|
353
|
-
assert updated_count == 2
|
|
354
|
-
|
|
355
|
-
# verify
|
|
356
|
-
published_docs = document_store.filter_documents(
|
|
357
|
-
filters={"field": "meta.status", "operator": "==", "value": "published"}
|
|
358
|
-
)
|
|
359
|
-
assert len(published_docs) == 2
|
|
360
|
-
for doc in published_docs:
|
|
361
|
-
assert doc.meta["category"] == "A"
|
|
362
|
-
assert doc.meta["status"] == "published"
|
|
363
|
-
|
|
364
|
-
# Verify category B still has draft status
|
|
365
|
-
draft_docs = document_store.filter_documents(filters={"field": "meta.status", "operator": "==", "value": "draft"})
|
|
366
|
-
assert len(draft_docs) == 1
|
|
367
|
-
assert draft_docs[0].meta["category"] == "B"
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
@pytest.mark.integration
|
|
371
|
-
def test_update_by_filter_multiple_fields(document_store: PgvectorDocumentStore):
|
|
372
|
-
docs = [
|
|
373
|
-
Document(content="Doc 1", meta={"category": "A", "year": 2023}),
|
|
374
|
-
Document(content="Doc 2", meta={"category": "A", "year": 2023}),
|
|
375
|
-
Document(content="Doc 3", meta={"category": "B", "year": 2024}),
|
|
376
|
-
]
|
|
377
|
-
document_store.write_documents(docs)
|
|
378
|
-
assert document_store.count_documents() == 3
|
|
379
|
-
|
|
380
|
-
# update multiple fields for category="A" documents
|
|
381
|
-
updated_count = document_store.update_by_filter(
|
|
382
|
-
filters={"field": "meta.category", "operator": "==", "value": "A"},
|
|
383
|
-
meta={"status": "published", "priority": "high", "reviewed": True},
|
|
384
|
-
)
|
|
385
|
-
assert updated_count == 2
|
|
386
|
-
|
|
387
|
-
# verify
|
|
388
|
-
published_docs = document_store.filter_documents(filters={"field": "meta.category", "operator": "==", "value": "A"})
|
|
389
|
-
assert len(published_docs) == 2
|
|
390
|
-
for doc in published_docs:
|
|
391
|
-
assert doc.meta["status"] == "published"
|
|
392
|
-
assert doc.meta["priority"] == "high"
|
|
393
|
-
assert doc.meta["reviewed"] is True
|
|
394
|
-
assert doc.meta["year"] == 2023 # Original field should still be present
|
|
395
|
-
|
|
396
|
-
# verify category B was not updated
|
|
397
|
-
b_docs = document_store.filter_documents(filters={"field": "meta.category", "operator": "==", "value": "B"})
|
|
398
|
-
assert len(b_docs) == 1
|
|
399
|
-
assert "status" not in b_docs[0].meta
|
|
400
|
-
assert "priority" not in b_docs[0].meta
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
@pytest.mark.integration
|
|
404
|
-
def test_update_by_filter_no_matches(document_store: PgvectorDocumentStore):
|
|
405
|
-
docs = [
|
|
406
|
-
Document(content="Doc 1", meta={"category": "A"}),
|
|
407
|
-
Document(content="Doc 2", meta={"category": "B"}),
|
|
408
|
-
]
|
|
409
|
-
document_store.write_documents(docs)
|
|
410
|
-
assert document_store.count_documents() == 2
|
|
411
|
-
|
|
412
|
-
# update documents with category="C" (no matches)
|
|
413
|
-
updated_count = document_store.update_by_filter(
|
|
414
|
-
filters={"field": "meta.category", "operator": "==", "value": "C"}, meta={"status": "published"}
|
|
415
|
-
)
|
|
416
|
-
assert updated_count == 0
|
|
417
|
-
assert document_store.count_documents() == 2
|
|
418
|
-
|
|
419
|
-
# verify no documents were updated
|
|
420
|
-
published_docs = document_store.filter_documents(
|
|
421
|
-
filters={"field": "meta.status", "operator": "==", "value": "published"}
|
|
422
|
-
)
|
|
423
|
-
assert len(published_docs) == 0
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
@pytest.mark.integration
|
|
427
|
-
def test_update_by_filter_advanced_filters(document_store: PgvectorDocumentStore):
|
|
428
|
-
docs = [
|
|
429
|
-
Document(content="Doc 1", meta={"category": "A", "year": 2023, "status": "draft"}),
|
|
430
|
-
Document(content="Doc 2", meta={"category": "A", "year": 2024, "status": "draft"}),
|
|
431
|
-
Document(content="Doc 3", meta={"category": "B", "year": 2023, "status": "draft"}),
|
|
432
|
-
]
|
|
433
|
-
document_store.write_documents(docs)
|
|
434
|
-
assert document_store.count_documents() == 3
|
|
435
|
-
|
|
436
|
-
# AND condition
|
|
437
|
-
updated_count = document_store.update_by_filter(
|
|
438
|
-
filters={
|
|
439
|
-
"operator": "AND",
|
|
440
|
-
"conditions": [
|
|
441
|
-
{"field": "meta.category", "operator": "==", "value": "A"},
|
|
442
|
-
{"field": "meta.year", "operator": "==", "value": 2023},
|
|
443
|
-
],
|
|
444
|
-
},
|
|
445
|
-
meta={"status": "published"},
|
|
446
|
-
)
|
|
447
|
-
assert updated_count == 1
|
|
448
|
-
|
|
449
|
-
# verify only one document was updated
|
|
450
|
-
published_docs = document_store.filter_documents(
|
|
451
|
-
filters={"field": "meta.status", "operator": "==", "value": "published"}
|
|
452
|
-
)
|
|
453
|
-
assert len(published_docs) == 1
|
|
454
|
-
assert published_docs[0].meta["category"] == "A"
|
|
455
|
-
assert published_docs[0].meta["year"] == 2023
|
|
456
|
-
|
|
457
|
-
# OR condition
|
|
458
|
-
updated_count = document_store.update_by_filter(
|
|
459
|
-
filters={
|
|
460
|
-
"operator": "OR",
|
|
461
|
-
"conditions": [
|
|
462
|
-
{"field": "meta.category", "operator": "==", "value": "B"},
|
|
463
|
-
{"field": "meta.year", "operator": "==", "value": 2024},
|
|
464
|
-
],
|
|
465
|
-
},
|
|
466
|
-
meta={"featured": True},
|
|
467
|
-
)
|
|
468
|
-
assert updated_count == 2
|
|
469
|
-
|
|
470
|
-
featured_docs = document_store.filter_documents(filters={"field": "meta.featured", "operator": "==", "value": True})
|
|
471
|
-
assert len(featured_docs) == 2
|
|
472
|
-
|
|
473
|
-
|
|
474
274
|
@pytest.mark.integration
|
|
475
275
|
def test_update_by_filter_empty_meta_raises_error(document_store: PgvectorDocumentStore):
|
|
476
276
|
docs = [Document(content="Doc 1", meta={"category": "A"})]
|
|
@@ -144,6 +144,11 @@ def test_treat_meta_field():
|
|
|
144
144
|
assert _treat_meta_field(field="meta.name", value=None) == "meta->>'name'"
|
|
145
145
|
|
|
146
146
|
|
|
147
|
+
def test_treat_meta_field_rejects_unsafe_metadata_key():
|
|
148
|
+
with pytest.raises(FilterError, match="Invalid metadata field name"):
|
|
149
|
+
_treat_meta_field(field="meta.name' OR 1=1 --", value="x")
|
|
150
|
+
|
|
151
|
+
|
|
147
152
|
def test_comparison_condition_missing_operator():
|
|
148
153
|
condition = {"field": "meta.type", "value": "article"}
|
|
149
154
|
with pytest.raises(FilterError):
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
loaders:
|
|
2
|
-
- ignore_when_discovered:
|
|
3
|
-
- __init__
|
|
4
|
-
modules:
|
|
5
|
-
- haystack_integrations.components.retrievers.pgvector.embedding_retriever
|
|
6
|
-
- haystack_integrations.components.retrievers.pgvector.keyword_retriever
|
|
7
|
-
- haystack_integrations.document_stores.pgvector.document_store
|
|
8
|
-
search_path:
|
|
9
|
-
- ../src
|
|
10
|
-
type: haystack_pydoc_tools.loaders.CustomPythonLoader
|
|
11
|
-
processors:
|
|
12
|
-
- do_not_filter_modules: false
|
|
13
|
-
documented_only: true
|
|
14
|
-
expression: null
|
|
15
|
-
skip_empty_modules: true
|
|
16
|
-
type: filter
|
|
17
|
-
- type: smart
|
|
18
|
-
- type: crossref
|
|
19
|
-
renderer:
|
|
20
|
-
description: Pgvector integration for Haystack
|
|
21
|
-
id: integrations-pgvector
|
|
22
|
-
markdown:
|
|
23
|
-
add_member_class_prefix: false
|
|
24
|
-
add_method_class_prefix: true
|
|
25
|
-
classdef_code_block: false
|
|
26
|
-
descriptive_class_title: false
|
|
27
|
-
descriptive_module_title: true
|
|
28
|
-
filename: pgvector.md
|
|
29
|
-
title: Pgvector
|
|
30
|
-
type: haystack_pydoc_tools.renderers.DocusaurusRenderer
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|