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.
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/CHANGELOG.md +81 -16
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/PKG-INFO +1 -1
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +26 -11
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/filters.py +4 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_document_store.py +21 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_document_store_async.py +22 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_filters.py +18 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/.gitignore +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/LICENSE.txt +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/README.md +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/examples/embedding_retrieval.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/examples/hybrid_retrieval.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/pydoc/config_docusaurus.yml +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/pyproject.toml +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/pgvector/converters.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/__init__.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/conftest.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_converters.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_retrieval.py +0 -0
- {pgvector_haystack-6.6.0 → pgvector_haystack-7.0.0}/tests/test_retrieval_async.py +0 -0
- {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:
|
|
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
|
-
#
|
|
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
|
-
|
|
985
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
File without changes
|
|
File without changes
|