ibm-db-haystack 0.3.0__tar.gz → 0.4.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.
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/CHANGELOG.md +16 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/PKG-INFO +1 -1
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/document_stores/ibm_db/document_store.py +17 -5
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/tests/test_document_store.py +181 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/.gitignore +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/LICENSE.txt +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/README.md +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/docker-compose.yml +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/pydoc/config_docusaurus.yml +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/pyproject.toml +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/components/retrievers/ibm_db/__init__.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/components/retrievers/ibm_db/embedding_retriever.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/document_stores/ibm_db/__init__.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/document_stores/ibm_db/filters.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/document_stores/py.typed +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/tests/__init__.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/tests/conftest.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/tests/test_embedding_retriever.py +0 -0
- {ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/tests/test_filters.py +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [integrations/ibm_db-v0.3.0] - 2026-09-01
|
|
4
|
+
|
|
5
|
+
### 🐛 Bug Fixes
|
|
6
|
+
|
|
7
|
+
- `IBMDB2DocumentStore` search_term + pagination (#3689)
|
|
8
|
+
- IBMDB adding optional parameter `filters` to `get_metadata_field_unique_values()` (#3739)
|
|
9
|
+
|
|
10
|
+
### 🧪 Testing
|
|
11
|
+
|
|
12
|
+
- `IBMDBDocumentStore` removing duplicated tests already covered by haystack Mixin tests (#3779)
|
|
13
|
+
|
|
14
|
+
### 🧹 Chores
|
|
15
|
+
|
|
16
|
+
- Re-standardize READMEs (#3768)
|
|
17
|
+
|
|
18
|
+
|
|
3
19
|
## [integrations/ibm_db-v0.2.0] - 2026-07-27
|
|
4
20
|
|
|
5
21
|
### 🚀 Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ibm-db-haystack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Haystack integration for IBM Db2
|
|
5
5
|
Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/ibm_db#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
|
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
"""IBM Db2 Document Store for Haystack."""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
import logging
|
|
9
8
|
import threading
|
|
10
9
|
from collections.abc import Iterator
|
|
11
10
|
from contextlib import contextmanager, suppress
|
|
12
11
|
from typing import Any, Literal
|
|
13
12
|
|
|
14
13
|
import ibm_db_dbi # type: ignore[import-untyped]
|
|
15
|
-
from haystack import default_from_dict, default_to_dict
|
|
14
|
+
from haystack import default_from_dict, default_to_dict, logging
|
|
16
15
|
from haystack.dataclasses import Document
|
|
17
16
|
from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
|
|
18
17
|
from haystack.document_stores.types import DuplicatePolicy
|
|
@@ -22,6 +21,8 @@ from .filters import FilterTranslator
|
|
|
22
21
|
|
|
23
22
|
logger = logging.getLogger(__name__)
|
|
24
23
|
|
|
24
|
+
VALID_DISTANCE_METRICS = frozenset({"COSINE", "DOT", "EUCLIDEAN", "EUCLIDEAN_SQUARED", "HAMMING", "MANHATTAN"})
|
|
25
|
+
|
|
25
26
|
|
|
26
27
|
def _parse_embedding(embedding: Any) -> list[float] | None:
|
|
27
28
|
"""
|
|
@@ -103,7 +104,14 @@ class IBMDb2DocumentStore:
|
|
|
103
104
|
connection_options: dict[str, Any] | None = None,
|
|
104
105
|
table_name: str = "haystack_documents",
|
|
105
106
|
embedding_dim: int = 768,
|
|
106
|
-
distance_metric: Literal[
|
|
107
|
+
distance_metric: Literal[
|
|
108
|
+
"COSINE",
|
|
109
|
+
"DOT",
|
|
110
|
+
"EUCLIDEAN",
|
|
111
|
+
"EUCLIDEAN_SQUARED",
|
|
112
|
+
"HAMMING",
|
|
113
|
+
"MANHATTAN",
|
|
114
|
+
] = "COSINE",
|
|
107
115
|
recreate_table: bool = False,
|
|
108
116
|
):
|
|
109
117
|
"""
|
|
@@ -121,7 +129,8 @@ class IBMDb2DocumentStore:
|
|
|
121
129
|
:param connection_options: Additional connection options as dict (optional)
|
|
122
130
|
:param table_name: Name of the table to store documents (default: "haystack_documents")
|
|
123
131
|
:param embedding_dim: Dimension of embedding vectors (default: 768)
|
|
124
|
-
:param distance_metric: Distance metric for similarity search (default: "COSINE")
|
|
132
|
+
:param distance_metric: Distance metric for similarity search (default: "COSINE"). Supported metrics in Db2:
|
|
133
|
+
"COSINE", "DOT", "EUCLIDEAN", "EUCLIDEAN_SQUARED", "HAMMING", "MANHATTAN".
|
|
125
134
|
:param recreate_table: If True, drop and recreate the table (default: False)
|
|
126
135
|
"""
|
|
127
136
|
self.database = database
|
|
@@ -136,6 +145,9 @@ class IBMDb2DocumentStore:
|
|
|
136
145
|
self.connection_options = connection_options
|
|
137
146
|
self.table_name = table_name
|
|
138
147
|
self.embedding_dim = embedding_dim
|
|
148
|
+
if distance_metric not in VALID_DISTANCE_METRICS:
|
|
149
|
+
msg = f"Invalid distance_metric '{distance_metric}'. Must be one of {sorted(VALID_DISTANCE_METRICS)}."
|
|
150
|
+
raise ValueError(msg)
|
|
139
151
|
self.distance_metric = distance_metric
|
|
140
152
|
self.recreate_table = recreate_table
|
|
141
153
|
|
|
@@ -269,7 +281,7 @@ class IBMDb2DocumentStore:
|
|
|
269
281
|
try:
|
|
270
282
|
cur.execute(create_sql)
|
|
271
283
|
conn.commit()
|
|
272
|
-
logger.info(
|
|
284
|
+
logger.info("Created table {table_name}", table_name=self.table_name)
|
|
273
285
|
except Exception:
|
|
274
286
|
conn.rollback()
|
|
275
287
|
# If it still fails, raise the error
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
"""Integration tests for IBM DB2 Document Store using Haystack mixin tests."""
|
|
6
6
|
|
|
7
7
|
import math
|
|
8
|
+
import sys
|
|
8
9
|
from datetime import datetime, timedelta, timezone
|
|
9
10
|
from unittest.mock import MagicMock, Mock
|
|
10
11
|
|
|
11
12
|
import pytest
|
|
12
13
|
from haystack.dataclasses import Document
|
|
13
14
|
from haystack.document_stores.errors import DocumentStoreError, DuplicateDocumentError
|
|
15
|
+
from haystack.document_stores.types import DuplicatePolicy
|
|
14
16
|
from haystack.testing.document_store import (
|
|
15
17
|
CountDocumentsByFilterTest,
|
|
16
18
|
CountDocumentsTest,
|
|
@@ -239,6 +241,120 @@ class TestDocumentStore(
|
|
|
239
241
|
assert [doc.id for doc in results] == ["a", "c"]
|
|
240
242
|
assert all(doc.score is not None for doc in results)
|
|
241
243
|
|
|
244
|
+
@pytest.mark.parametrize(
|
|
245
|
+
"metric",
|
|
246
|
+
["COSINE", "DOT", "EUCLIDEAN", "EUCLIDEAN_SQUARED", "HAMMING", "MANHATTAN"],
|
|
247
|
+
)
|
|
248
|
+
def test_all_distance_metrics_return_ranked_results(self, metric: str, connection_config):
|
|
249
|
+
"""
|
|
250
|
+
Verify that _embedding_retrieval works correctly for each of the 6 Db2
|
|
251
|
+
distance metrics: correct ranking, finite scores, top_k respected.
|
|
252
|
+
|
|
253
|
+
A fresh store with embedding_dim=4 is created for each metric so the
|
|
254
|
+
test is independent of the default `document_store` fixture.
|
|
255
|
+
All Db2 distance metrics return scores ordered ASC (smaller = more similar).
|
|
256
|
+
"""
|
|
257
|
+
table_name = f"haystack_metric_{metric.lower()}_{sys.version_info.major}_{sys.version_info.minor}"
|
|
258
|
+
store = IBMDb2DocumentStore(
|
|
259
|
+
**connection_config,
|
|
260
|
+
table_name=table_name,
|
|
261
|
+
embedding_dim=4,
|
|
262
|
+
distance_metric=metric,
|
|
263
|
+
recreate_table=True,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
# Embeddings chosen so ranking is unambiguous for every metric:
|
|
268
|
+
# most_similar == query → distance 0 (or near 0) for all metrics
|
|
269
|
+
# second_best is close → small distance
|
|
270
|
+
# least_similar is far → large distance
|
|
271
|
+
query = [1.0, 1.0, 1.0, 1.0]
|
|
272
|
+
most_similar = [1.0, 1.0, 1.0, 1.0]
|
|
273
|
+
second_best = [1.0, 1.0, 0.5, 0.5]
|
|
274
|
+
least_similar = [0.0, 0.0, 0.0, 1.0]
|
|
275
|
+
|
|
276
|
+
docs = [
|
|
277
|
+
Document(content="most_similar", embedding=most_similar),
|
|
278
|
+
Document(content="second_best", embedding=second_best),
|
|
279
|
+
Document(content="least_similar", embedding=least_similar),
|
|
280
|
+
]
|
|
281
|
+
store.write_documents(docs, policy=DuplicatePolicy.OVERWRITE)
|
|
282
|
+
|
|
283
|
+
results = store._embedding_retrieval(query_embedding=query, top_k=2)
|
|
284
|
+
|
|
285
|
+
# Exactly top_k documents returned
|
|
286
|
+
assert len(results) == 2, f"[{metric}] Expected 2 results, got {len(results)}"
|
|
287
|
+
|
|
288
|
+
# most_similar must be in the top-2; least_similar must be excluded
|
|
289
|
+
contents = [r.content for r in results]
|
|
290
|
+
assert "most_similar" in contents, f"[{metric}] 'most_similar' missing from top-2: {contents}"
|
|
291
|
+
assert "least_similar" not in contents, f"[{metric}] 'least_similar' appeared in top-2: {contents}"
|
|
292
|
+
|
|
293
|
+
# Scores must be finite floats and ordered ASC (ORDER BY score ASC)
|
|
294
|
+
for doc in results:
|
|
295
|
+
assert doc.score is not None, f"[{metric}] score is None"
|
|
296
|
+
assert math.isfinite(doc.score), f"[{metric}] non-finite score: {doc.score}"
|
|
297
|
+
assert results[0].content == "most_similar", (
|
|
298
|
+
f"[{metric}] Expected 'most_similar' at rank-1, got '{results[0].content}' "
|
|
299
|
+
f"(scores: {[r.score for r in results]})"
|
|
300
|
+
)
|
|
301
|
+
assert results[0].score <= results[1].score, (
|
|
302
|
+
f"[{metric}] Score ordering wrong: {results[0].score} > {results[1].score}"
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
finally:
|
|
306
|
+
try:
|
|
307
|
+
conn = store._get_connection()
|
|
308
|
+
with conn.cursor() as cur:
|
|
309
|
+
cur.execute(f"DROP TABLE {store.table_name}")
|
|
310
|
+
conn.commit()
|
|
311
|
+
except Exception:
|
|
312
|
+
pass
|
|
313
|
+
store.close()
|
|
314
|
+
|
|
315
|
+
@pytest.mark.parametrize(
|
|
316
|
+
"metric",
|
|
317
|
+
["COSINE", "DOT", "EUCLIDEAN", "EUCLIDEAN_SQUARED", "HAMMING", "MANHATTAN"],
|
|
318
|
+
)
|
|
319
|
+
def test_all_distance_metrics_respect_filters(self, metric: str, connection_config):
|
|
320
|
+
"""
|
|
321
|
+
Verify that metadata filters work correctly alongside each distance metric.
|
|
322
|
+
"""
|
|
323
|
+
table_name = f"haystack_metric_flt_{metric.lower()}_{sys.version_info.major}_{sys.version_info.minor}"
|
|
324
|
+
store = IBMDb2DocumentStore(
|
|
325
|
+
**connection_config,
|
|
326
|
+
table_name=table_name,
|
|
327
|
+
embedding_dim=4,
|
|
328
|
+
distance_metric=metric,
|
|
329
|
+
recreate_table=True,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
query = [1.0, 1.0, 1.0, 1.0]
|
|
334
|
+
docs = [
|
|
335
|
+
Document(content="in_1", embedding=[1.0, 1.0, 1.0, 1.0], meta={"cat": "A"}),
|
|
336
|
+
Document(content="in_2", embedding=[0.9, 0.9, 0.9, 0.9], meta={"cat": "A"}),
|
|
337
|
+
Document(content="out", embedding=[1.0, 1.0, 1.0, 1.0], meta={"cat": "B"}),
|
|
338
|
+
]
|
|
339
|
+
store.write_documents(docs, policy=DuplicatePolicy.OVERWRITE)
|
|
340
|
+
|
|
341
|
+
filters = {"field": "meta.cat", "operator": "==", "value": "A"}
|
|
342
|
+
results = store._embedding_retrieval(query_embedding=query, top_k=5, filters=filters)
|
|
343
|
+
|
|
344
|
+
assert len(results) == 2, f"[{metric}] Expected 2 filtered results, got {len(results)}"
|
|
345
|
+
for doc in results:
|
|
346
|
+
assert doc.meta.get("cat") == "A", f"[{metric}] Doc with wrong category returned: {doc.meta}"
|
|
347
|
+
|
|
348
|
+
finally:
|
|
349
|
+
try:
|
|
350
|
+
conn = store._get_connection()
|
|
351
|
+
with conn.cursor() as cur:
|
|
352
|
+
cur.execute(f"DROP TABLE {store.table_name}")
|
|
353
|
+
conn.commit()
|
|
354
|
+
except Exception:
|
|
355
|
+
pass
|
|
356
|
+
store.close()
|
|
357
|
+
|
|
242
358
|
def test_get_metadata_field_unique_values_pagination(self, document_store: IBMDb2DocumentStore):
|
|
243
359
|
docs = [
|
|
244
360
|
Document(content=f"doc {i}", meta={"category": c}) for i, c in enumerate(["A", "B", "A", "C", "B", "A"])
|
|
@@ -541,6 +657,47 @@ class TestIBMDb2DocumentStoreUnit:
|
|
|
541
657
|
assert isinstance(new_store.username, Secret)
|
|
542
658
|
assert isinstance(new_store.password, Secret)
|
|
543
659
|
|
|
660
|
+
@pytest.mark.parametrize(
|
|
661
|
+
"metric",
|
|
662
|
+
[
|
|
663
|
+
"COSINE",
|
|
664
|
+
"DOT",
|
|
665
|
+
"EUCLIDEAN",
|
|
666
|
+
"EUCLIDEAN_SQUARED",
|
|
667
|
+
"HAMMING",
|
|
668
|
+
"MANHATTAN",
|
|
669
|
+
],
|
|
670
|
+
)
|
|
671
|
+
def test_supported_distance_metrics(self, metric, monkeypatch):
|
|
672
|
+
"""Test that all 6 supported Db2 distance metrics can be configured and serialized."""
|
|
673
|
+
monkeypatch.setenv("DB2_USERNAME", "db2inst1")
|
|
674
|
+
monkeypatch.setenv("DB2_PASSWORD", "Passw0rd123!")
|
|
675
|
+
store = IBMDb2DocumentStore(
|
|
676
|
+
database="testdb",
|
|
677
|
+
hostname="localhost",
|
|
678
|
+
username=Secret.from_env_var("DB2_USERNAME"),
|
|
679
|
+
password=Secret.from_env_var("DB2_PASSWORD"),
|
|
680
|
+
distance_metric=metric,
|
|
681
|
+
)
|
|
682
|
+
assert store.distance_metric == metric
|
|
683
|
+
data = store.to_dict()
|
|
684
|
+
assert data["init_parameters"]["distance_metric"] == metric
|
|
685
|
+
restored = IBMDb2DocumentStore.from_dict(data)
|
|
686
|
+
assert restored.distance_metric == metric
|
|
687
|
+
|
|
688
|
+
def test_invalid_distance_metric_raises(self, monkeypatch):
|
|
689
|
+
"""Test that an invalid distance_metric raises ValueError at construction time."""
|
|
690
|
+
monkeypatch.setenv("DB2_USERNAME", "db2inst1")
|
|
691
|
+
monkeypatch.setenv("DB2_PASSWORD", "Passw0rd123!")
|
|
692
|
+
with pytest.raises(ValueError, match="Invalid distance_metric"):
|
|
693
|
+
IBMDb2DocumentStore(
|
|
694
|
+
database="testdb",
|
|
695
|
+
hostname="localhost",
|
|
696
|
+
username=Secret.from_env_var("DB2_USERNAME"),
|
|
697
|
+
password=Secret.from_env_var("DB2_PASSWORD"),
|
|
698
|
+
distance_metric="L2", # type: ignore[arg-type]
|
|
699
|
+
)
|
|
700
|
+
|
|
544
701
|
def test_to_row_with_none_metadata(self, unit_store):
|
|
545
702
|
"""Test _to_row with None metadata."""
|
|
546
703
|
doc = Document(id="1", content="test", meta=None, embedding=[0.1] * 768)
|
|
@@ -638,6 +795,30 @@ class TestIBMDb2DocumentStoreUnit:
|
|
|
638
795
|
executed_sql = cur.execute.call_args.args[0]
|
|
639
796
|
assert "AND embedding IS NOT NULL" in executed_sql
|
|
640
797
|
|
|
798
|
+
@pytest.mark.parametrize(
|
|
799
|
+
"metric",
|
|
800
|
+
[
|
|
801
|
+
"COSINE",
|
|
802
|
+
"DOT",
|
|
803
|
+
"EUCLIDEAN",
|
|
804
|
+
"EUCLIDEAN_SQUARED",
|
|
805
|
+
"HAMMING",
|
|
806
|
+
"MANHATTAN",
|
|
807
|
+
],
|
|
808
|
+
)
|
|
809
|
+
def test_embedding_retrieval_uses_configured_metric(self, mocked_store, metric):
|
|
810
|
+
store, _, cur = mocked_store
|
|
811
|
+
store.distance_metric = metric
|
|
812
|
+
cur.fetchall.return_value = []
|
|
813
|
+
|
|
814
|
+
store._embedding_retrieval([0.1, 0.2], top_k=5)
|
|
815
|
+
|
|
816
|
+
executed_sql = cur.execute.call_args.args[0]
|
|
817
|
+
expected_fragment = (
|
|
818
|
+
f"VECTOR_DISTANCE(embedding, VECTOR(CAST(? AS CLOB(100000)), {store.embedding_dim}, FLOAT32), {metric})"
|
|
819
|
+
)
|
|
820
|
+
assert expected_fragment in executed_sql
|
|
821
|
+
|
|
641
822
|
@pytest.mark.parametrize(
|
|
642
823
|
"fetch_error",
|
|
643
824
|
[
|
|
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
|
{ibm_db_haystack-0.3.0 → ibm_db_haystack-0.4.0}/src/haystack_integrations/document_stores/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|