pgvector-haystack 6.5.0__tar.gz → 6.6.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.5.0 → pgvector_haystack-6.6.0}/CHANGELOG.md +12 -0
  2. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/LICENSE.txt +1 -1
  3. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/PKG-INFO +4 -2
  4. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/README.md +2 -0
  5. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/document_stores/pgvector/document_store.py +49 -6
  6. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_document_store.py +43 -150
  7. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_document_store_async.py +43 -16
  8. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/.gitignore +0 -0
  9. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/examples/embedding_retrieval.py +0 -0
  10. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/examples/hybrid_retrieval.py +0 -0
  11. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/pydoc/config_docusaurus.yml +0 -0
  12. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/pyproject.toml +0 -0
  13. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/components/retrievers/pgvector/__init__.py +0 -0
  14. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/components/retrievers/pgvector/embedding_retriever.py +0 -0
  15. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/components/retrievers/pgvector/keyword_retriever.py +0 -0
  16. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/components/retrievers/py.typed +0 -0
  17. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/document_stores/pgvector/__init__.py +0 -0
  18. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/document_stores/pgvector/converters.py +0 -0
  19. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/document_stores/pgvector/filters.py +0 -0
  20. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/src/haystack_integrations/document_stores/py.typed +0 -0
  21. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/__init__.py +0 -0
  22. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/conftest.py +0 -0
  23. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_converters.py +0 -0
  24. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_filters.py +0 -0
  25. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_retrieval.py +0 -0
  26. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_retrieval_async.py +0 -0
  27. {pgvector_haystack-6.5.0 → pgvector_haystack-6.6.0}/tests/test_retrievers.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/pgvector-v6.5.0] - 2026-08-03
4
+
5
+ ### 🚀 Features
6
+
7
+ - Add array_contains and array_overlaps operators to pgvector filters (#3459)
8
+
9
+ ### 🐛 Bug Fixes
10
+
11
+ - Fix new issues raised by ruff 0.16.0 (#3670)
12
+ - `PGVectorDocumentStore` get_metadata_field_unique_values setting list of values to return to `Any` (#3709)
13
+
14
+
3
15
  ## [integrations/pgvector-v6.4.1] - 2026-07-23
4
16
 
5
17
  ### 🐛 Bug Fixes
@@ -58,7 +58,7 @@ APPENDIX: How to apply the Apache License to your work.
58
58
 
59
59
  To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
60
60
 
61
- Copyright [yyyy] [name of copyright owner]
61
+ Copyright 2023-present deepset GmbH
62
62
 
63
63
  Licensed under the Apache License, Version 2.0 (the "License");
64
64
  you may not use this file except in compliance with the License.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pgvector-haystack
3
- Version: 6.5.0
3
+ Version: 6.6.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
@@ -34,6 +34,8 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  ---
36
36
 
37
+ ## Contributing
38
+
37
39
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
38
40
 
39
41
  To run integration tests locally, you need to have a PostgreSQL DB running with the `pgvector` extension.
@@ -8,6 +8,8 @@
8
8
 
9
9
  ---
10
10
 
11
+ ## Contributing
12
+
11
13
  Refer to the general [Contribution Guidelines](https://github.com/deepset-ai/haystack-core-integrations/blob/main/CONTRIBUTING.md).
12
14
 
13
15
  To run integration tests locally, you need to have a PostgreSQL DB running with the `pgvector` extension.
@@ -1926,7 +1926,12 @@ class PgvectorDocumentStore:
1926
1926
  return {"min": min_value, "max": max_value}
1927
1927
 
1928
1928
  def _build_unique_values_queries(
1929
- self, normalized_field: str, search_term: str | None, from_: int, size: int
1929
+ self,
1930
+ normalized_field: str,
1931
+ search_term: str | None,
1932
+ from_: int,
1933
+ size: int,
1934
+ filters: dict[str, Any] | None = None,
1930
1935
  ) -> tuple[Composed, Composed, tuple]:
1931
1936
  """
1932
1937
  Builds SQL queries for getting unique metadata field values.
@@ -1936,6 +1941,7 @@ class PgvectorDocumentStore:
1936
1941
  against the metadata field's own value.
1937
1942
  :param from_: The offset for pagination (0-based).
1938
1943
  :param size: The number of unique values to return.
1944
+ :param filters: Optional filters to restrict the documents considered.
1939
1945
  :returns: A tuple containing (count_query, select_query, params).
1940
1946
  """
1941
1947
  field_literal = SQLLiteral(normalized_field)
@@ -1944,6 +1950,9 @@ class PgvectorDocumentStore:
1944
1950
  # Use the `->` (jsonb) operator rather than `->>` (text) so DISTINCT/ORDER BY operate on the
1945
1951
  # value's original JSON type (numbers sort numerically, not lexicographically) and psycopg
1946
1952
  # decodes the result to its native Python type instead of a string.
1953
+ #
1954
+ # Not fixable here: JSONB equality treats a whole-number float (1.0) as identical to a
1955
+ # numerically equal int (1), so DISTINCT still collapses them - see this method's docstring.
1947
1956
  sql_select = SQL("SELECT DISTINCT meta->{} AS value").format(field_literal)
1948
1957
  sql_from = SQL(" FROM {schema_name}.{table_name}").format(
1949
1958
  schema_name=Identifier(self.schema_name), table_name=Identifier(self.table_name)
@@ -1951,10 +1960,16 @@ class PgvectorDocumentStore:
1951
1960
  sql_where = SQL(" WHERE meta->>{} IS NOT NULL").format(field_literal)
1952
1961
 
1953
1962
  params: tuple = ()
1963
+ if filters:
1964
+ _validate_filters(filters)
1965
+ filters_where_clause, filters_params = _convert_filters_to_where_clause_and_params(filters, operator="AND")
1966
+ sql_where += filters_where_clause
1967
+ params += filters_params
1968
+
1954
1969
  if search_term:
1955
1970
  # Case-insensitive substring match against the metadata field's own (text) value.
1956
1971
  sql_where += SQL(" AND meta->>{} ILIKE %s").format(field_literal)
1957
- params = (f"%{search_term}%",)
1972
+ params += (f"%{search_term}%",)
1958
1973
 
1959
1974
  # count query
1960
1975
  sql_count = SQL("SELECT COUNT(DISTINCT meta->{} ) AS total").format(field_literal)
@@ -1984,22 +1999,36 @@ class PgvectorDocumentStore:
1984
1999
  return unique_values, total_count
1985
2000
 
1986
2001
  def get_metadata_field_unique_values(
1987
- self, metadata_field: str, search_term: str | None = None, from_: int = 0, size: int = 10
2002
+ self,
2003
+ metadata_field: str,
2004
+ search_term: str | None = None,
2005
+ from_: int = 0,
2006
+ size: int = 10,
2007
+ filters: dict[str, Any] | None = None,
1988
2008
  ) -> tuple[list[Any], int]:
1989
2009
  """
1990
2010
  Returns unique values for a given metadata field, optionally filtered by a search term.
1991
2011
 
2012
+ **Note**: values of different JSON type categories are kept distinct - a string, a number and
2013
+ a boolean never collapse into each other, even when they share a textual form (e.g. the string
2014
+ `"1"` and the number `1`). One exception: the ``meta`` column is JSONB, whose equality treats a
2015
+ whole-number float (`1.0`) as identical to a numerically equal int (`1`), so those two collapse
2016
+ into a single value. Floats with a fractional part (e.g. `1.5`) are unaffected.
2017
+
1992
2018
  :param metadata_field: The name of the metadata field. Can include or omit the "meta." prefix.
1993
2019
  :param search_term: Optional search term to filter unique values by a case-insensitive substring
1994
2020
  match against the metadata field's own value. If None, all values are considered.
1995
2021
  :param from_: The offset for pagination (0-based).
1996
2022
  :param size: The number of unique values to return.
2023
+ :param filters: Optional filters to restrict the documents considered.
1997
2024
  :returns: A tuple containing:
1998
2025
  - A list of unique values in their original type
1999
2026
  - The total count of unique values
2000
2027
  """
2001
2028
  normalized_field = PgvectorDocumentStore._normalize_metadata_field_name(metadata_field)
2002
- sql_count, sql_query, params = self._build_unique_values_queries(normalized_field, search_term, from_, size)
2029
+ sql_count, sql_query, params = self._build_unique_values_queries(
2030
+ normalized_field, search_term, from_, size, filters
2031
+ )
2003
2032
 
2004
2033
  self._ensure_db_setup()
2005
2034
  assert self._dict_cursor is not None
@@ -2022,22 +2051,36 @@ class PgvectorDocumentStore:
2022
2051
  return PgvectorDocumentStore._process_unique_values_result(count_result, records)
2023
2052
 
2024
2053
  async def get_metadata_field_unique_values_async(
2025
- self, metadata_field: str, search_term: str | None = None, from_: int = 0, size: int = 10
2054
+ self,
2055
+ metadata_field: str,
2056
+ search_term: str | None = None,
2057
+ from_: int = 0,
2058
+ size: int = 10,
2059
+ filters: dict[str, Any] | None = None,
2026
2060
  ) -> tuple[list[Any], int]:
2027
2061
  """
2028
2062
  Asynchronously returns unique values for a given metadata field, optionally filtered by a search term.
2029
2063
 
2064
+ **Note**: values of different JSON type categories are kept distinct - a string, a number and
2065
+ a boolean never collapse into each other, even when they share a textual form (e.g. the string
2066
+ `"1"` and the number `1`). One exception: the ``meta`` column is JSONB, whose equality treats a
2067
+ whole-number float (`1.0`) as identical to a numerically equal int (`1`), so those two collapse
2068
+ into a single value. Floats with a fractional part (e.g. `1.5`) are unaffected.
2069
+
2030
2070
  :param metadata_field: The name of the metadata field. Can include or omit the "meta." prefix.
2031
2071
  :param search_term: Optional search term to filter unique values by a case-insensitive substring
2032
2072
  match against the metadata field's own value. If None, all values are considered.
2033
2073
  :param from_: The offset for pagination (0-based).
2034
2074
  :param size: The number of unique values to return.
2075
+ :param filters: Optional filters to restrict the documents considered.
2035
2076
  :returns: A tuple containing:
2036
2077
  - A list of unique values in their original type
2037
2078
  - The total count of unique values
2038
2079
  """
2039
2080
  normalized_field = PgvectorDocumentStore._normalize_metadata_field_name(metadata_field)
2040
- sql_count, sql_query, params = self._build_unique_values_queries(normalized_field, search_term, from_, size)
2081
+ sql_count, sql_query, params = self._build_unique_values_queries(
2082
+ normalized_field, search_term, from_, size, filters
2083
+ )
2041
2084
 
2042
2085
  await self._ensure_db_setup_async()
2043
2086
  assert self._async_dict_cursor is not None
@@ -66,6 +66,41 @@ class TestDocumentStore(
66
66
  with pytest.raises(DuplicateDocumentError):
67
67
  document_store.write_documents(docs, DuplicatePolicy.FAIL)
68
68
 
69
+ def test_get_metadata_field_unique_values_distinct_types(self, document_store: PgvectorDocumentStore):
70
+ """
71
+ Override: the base mixin test stores int, float, str and bool under the *same* metadata field
72
+ name and expects all four back as distinct values. This store's ``meta`` column is JSONB, and
73
+ PostgreSQL's JSONB equality treats a whole-number float (e.g. 1.0) and a numerically equal int
74
+ (1) as the same value, so ``SELECT DISTINCT meta->'field'`` collapses them regardless of which
75
+ other values share that field.
76
+
77
+ This adapts the same intent - int, float, str and bool must come back as distinct, unmangled
78
+ types via get_metadata_field_unique_values() - using one field per type instead of one shared
79
+ field, which is what this store can actually support.
80
+
81
+ The float value is a non-whole number (1.5, not 1.0): a whole-number float would still collapse
82
+ with an int under PostgreSQL's JSONB numeric equality even in its own field, so a fractional
83
+ value is used to sidestep that ambiguity entirely.
84
+ """
85
+ docs = [
86
+ Document(content="Doc 1", meta={"priority_int": 1}),
87
+ Document(content="Doc 2", meta={"priority_str": "1"}),
88
+ Document(content="Doc 3", meta={"priority_float": 1.5}),
89
+ Document(content="Doc 4", meta={"priority_bool": True}),
90
+ ]
91
+ document_store.write_documents(docs)
92
+
93
+ int_values, int_count = document_store.get_metadata_field_unique_values(metadata_field="priority_int")
94
+ str_values, str_count = document_store.get_metadata_field_unique_values(metadata_field="priority_str")
95
+ float_values, float_count = document_store.get_metadata_field_unique_values(metadata_field="priority_float")
96
+ bool_values, bool_count = document_store.get_metadata_field_unique_values(metadata_field="priority_bool")
97
+
98
+ assert (int_count, str_count, float_count, bool_count) == (1, 1, 1, 1)
99
+ assert int_values == [1] and type(int_values[0]) is int
100
+ assert str_values == ["1"] and type(str_values[0]) is str
101
+ assert float_values == [1.5] and type(float_values[0]) is float
102
+ assert bool_values == [True] and type(bool_values[0]) is bool
103
+
69
104
  def test_write_blob(self, document_store: PgvectorDocumentStore):
70
105
  bytestream = ByteStream(b"test", meta={"meta_key": "meta_value"}, mime_type="mime_type")
71
106
  docs = [Document(id="1", blob=bytestream)]
@@ -596,172 +631,30 @@ def test_get_metadata_field_min_max(document_store: PgvectorDocumentStore):
596
631
 
597
632
 
598
633
  @pytest.mark.integration
599
- def test_get_metadata_field_unique_values(document_store: PgvectorDocumentStore):
600
- # Test with string values
634
+ def test_get_metadata_field_unique_values_pagination_edge_cases(document_store: PgvectorDocumentStore):
635
+ """
636
+ Edge cases not covered by the shared GetMetadataFieldUniqueValuesTest mixin's pagination test:
637
+ size larger than the total count, from_ beyond the total count, and single-item-per-page paging.
638
+ """
601
639
  docs = [
602
- Document(content="Python programming", meta={"category": "A", "language": "Python"}),
603
- Document(content="Java programming", meta={"category": "B", "language": "Java"}),
604
- Document(content="Python scripting", meta={"category": "A", "language": "Python"}),
605
- Document(content="JavaScript development", meta={"category": "C", "language": "JavaScript"}),
606
- Document(content="Python data science", meta={"category": "A", "language": "Python"}),
607
- Document(content="Java backend", meta={"category": "B", "language": "Java"}),
640
+ Document(content="Python programming", meta={"category": "A"}),
641
+ Document(content="Java programming", meta={"category": "B"}),
642
+ Document(content="JavaScript development", meta={"category": "C"}),
608
643
  ]
609
644
  document_store.write_documents(docs)
610
645
 
611
- # Test getting all unique values without search term
612
- unique_values, total_count = document_store.get_metadata_field_unique_values("meta.category", None, 0, 10)
613
- assert set(unique_values) == {"A", "B", "C"}
614
- assert total_count == 3
615
-
616
- # Test with "meta." prefix
617
- unique_languages, total_languages = document_store.get_metadata_field_unique_values("meta.language", None, 0, 10)
618
- assert set(unique_languages) == {"Python", "Java", "JavaScript"}
619
- assert total_languages == 3
620
-
621
- # Test pagination - first page
622
- unique_values_page1, total_count_page1 = document_store.get_metadata_field_unique_values(
623
- "meta.category", None, 0, 2
624
- )
625
- assert len(unique_values_page1) == 2
626
- assert all(val in ["A", "B", "C"] for val in unique_values_page1)
627
- assert total_count_page1 == 3
628
-
629
- # Test pagination - second page
630
- unique_values_page2, total_count_page2 = document_store.get_metadata_field_unique_values(
631
- "meta.category", None, 2, 2
632
- )
633
- assert len(unique_values_page2) == 1
634
- assert unique_values_page2[0] in ["A", "B", "C"]
635
- assert total_count_page2 == 3
636
-
637
- # Test pagination - verify pages don't overlap
638
- assert not set(unique_values_page1).intersection(set(unique_values_page2))
639
-
640
- # Test pagination - verify all values are covered
641
- all_values = set(unique_values_page1) | set(unique_values_page2)
642
- assert all_values == {"A", "B", "C"}
643
-
644
- # Test pagination - size larger than total count
645
646
  unique_values_large, total_large = document_store.get_metadata_field_unique_values("meta.category", None, 0, 100)
646
- assert len(unique_values_large) == 3
647
647
  assert set(unique_values_large) == {"A", "B", "C"}
648
648
  assert total_large == 3
649
649
 
650
- # Test pagination - from_ beyond total count (should return empty)
651
650
  unique_values_beyond, total_beyond = document_store.get_metadata_field_unique_values("meta.category", None, 10, 10)
652
651
  assert len(unique_values_beyond) == 0
653
652
  assert total_beyond == 3
654
653
 
655
- # Test pagination - single item per page
656
654
  unique_values_single1, _ = document_store.get_metadata_field_unique_values("meta.category", None, 0, 1)
657
655
  unique_values_single2, _ = document_store.get_metadata_field_unique_values("meta.category", None, 1, 1)
658
656
  unique_values_single3, _ = document_store.get_metadata_field_unique_values("meta.category", None, 2, 1)
659
657
  assert len(unique_values_single1) == 1
660
658
  assert len(unique_values_single2) == 1
661
659
  assert len(unique_values_single3) == 1
662
- # All three pages should be different
663
660
  assert len(set(unique_values_single1 + unique_values_single2 + unique_values_single3)) == 3
664
-
665
- # Test with search term - case-insensitive substring match against the metadata field's OWN value
666
- # (not against document content).
667
- unique_values_filtered, total_filtered = document_store.get_metadata_field_unique_values(
668
- "meta.language", "Python", 0, 10
669
- )
670
- assert set(unique_values_filtered) == {"Python"}
671
- assert total_filtered == 1
672
-
673
- # Case-insensitivity: lowercase search term should still match "Python"
674
- unique_values_lower, total_lower = document_store.get_metadata_field_unique_values("meta.language", "python", 0, 10)
675
- assert set(unique_values_lower) == {"Python"}
676
- assert total_lower == 1
677
-
678
- # Substring matching: "Java" is a substring of both "Java" and "JavaScript"
679
- unique_values_java, total_java = document_store.get_metadata_field_unique_values("meta.language", "Java", 0, 10)
680
- assert set(unique_values_java) == {"Java", "JavaScript"}
681
- assert total_java == 2
682
-
683
- # Substring matching in the middle/end of a value
684
- unique_values_script, total_script = document_store.get_metadata_field_unique_values(
685
- "meta.language", "Script", 0, 10
686
- )
687
- assert set(unique_values_script) == {"JavaScript"}
688
- assert total_script == 1
689
-
690
- # Test pagination with search term
691
- unique_values_search_page1, total_search = document_store.get_metadata_field_unique_values(
692
- "meta.language", "Python", 0, 1
693
- )
694
- assert len(unique_values_search_page1) == 1
695
- assert unique_values_search_page1[0] == "Python"
696
- assert total_search == 1
697
-
698
- # Test pagination with search term - beyond results
699
- unique_values_search_empty, total_search_empty = document_store.get_metadata_field_unique_values(
700
- "meta.language", "Python", 10, 10
701
- )
702
- assert len(unique_values_search_empty) == 0
703
- assert total_search_empty == 1
704
-
705
- # Test that search_term matches metadata VALUE, not content
706
- content_vs_metadata_docs = [
707
- Document(content="This document mentions Python explicitly", meta={"topic": "cooking"}),
708
- Document(content="Unrelated content about recipes", meta={"topic": "python-tutorial"}),
709
- ]
710
- document_store.write_documents(content_vs_metadata_docs)
711
-
712
- unique_topics, total_topics = document_store.get_metadata_field_unique_values("meta.topic", "python", 0, 10)
713
- assert set(unique_topics) == {"python-tutorial"}
714
- assert total_topics == 1
715
-
716
- # Test with integer values
717
- int_docs = [
718
- Document(content="Doc 1", meta={"priority": 1}),
719
- Document(content="Doc 2", meta={"priority": 2}),
720
- Document(content="Doc 3", meta={"priority": 1}),
721
- Document(content="Doc 4", meta={"priority": 3}),
722
- ]
723
- document_store.write_documents(int_docs)
724
- unique_priorities, total_priorities = document_store.get_metadata_field_unique_values("meta.priority", None, 0, 10)
725
- assert set(unique_priorities) == {1, 2, 3}
726
- assert total_priorities == 3
727
-
728
- # Test with search term on integer field - substring match against the field's own (stringified)
729
- # value, e.g. "Doc 1" (content) no longer matches; "1" (the value itself) does. The returned
730
- # values themselves keep their original type (int here), only the match is textual.
731
- unique_priorities_filtered, total_priorities_filtered = document_store.get_metadata_field_unique_values(
732
- "meta.priority", "1", 0, 10
733
- )
734
- assert set(unique_priorities_filtered) == {1}
735
- assert total_priorities_filtered == 1
736
-
737
-
738
- @pytest.mark.integration
739
- def test_get_metadata_field_unique_values_no_search_term(document_store: PgvectorDocumentStore):
740
- docs = [
741
- Document(content="mentions tsvector and content in the body", meta={"category": "A"}),
742
- Document(content="another document", meta={"category": "B"}),
743
- ]
744
- document_store.write_documents(docs)
745
-
746
- values, total = document_store.get_metadata_field_unique_values("meta.category")
747
-
748
- assert set(values) == {"A", "B"}
749
- assert total == 2
750
-
751
-
752
- @pytest.mark.integration
753
- def test_get_metadata_field_unique_values_search_term_matches_field_value_not_content(
754
- document_store: PgvectorDocumentStore,
755
- ):
756
- docs = [
757
- # "python" appears in the content but NOT in the category value -> must be EXCLUDED
758
- Document(content="Python programming guide", meta={"category": "Backend"}),
759
- # "python" appears in the category value but NOT in the content -> must be INCLUDED
760
- Document(content="General purpose scripting language", meta={"category": "Python-based"}),
761
- ]
762
- document_store.write_documents(docs)
763
-
764
- values, total = document_store.get_metadata_field_unique_values("meta.category", search_term="python")
765
-
766
- assert values == ["Python-based"]
767
- assert total == 1
@@ -71,6 +71,49 @@ class TestDocumentStoreAsync(
71
71
  with pytest.raises(DuplicateDocumentError):
72
72
  await document_store.write_documents_async(docs, DuplicatePolicy.FAIL)
73
73
 
74
+ async def test_get_metadata_field_unique_values_distinct_types_async(self, document_store: PgvectorDocumentStore):
75
+ """
76
+ Override: the base mixin test stores int, float, str and bool under the *same* metadata field
77
+ name and expects all four back as distinct values. This store's ``meta`` column is JSONB, and
78
+ PostgreSQL's JSONB equality treats a whole-number float (e.g. 1.0) and a numerically equal int
79
+ (1) as the same value, so ``SELECT DISTINCT meta->'field'`` collapses them regardless of which
80
+ other values share that field.
81
+
82
+ This adapts the same intent - int, float, str and bool must come back as distinct, unmangled
83
+ types via get_metadata_field_unique_values_async() - using one field per type instead of one
84
+ shared field, which is what this store can actually support.
85
+
86
+ The float value is a non-whole number (1.5, not 1.0): a whole-number float would still collapse
87
+ with an int under PostgreSQL's JSONB numeric equality even in its own field, so a fractional
88
+ value is used to sidestep that ambiguity entirely.
89
+ """
90
+ docs = [
91
+ Document(content="Doc 1", meta={"priority_int": 1}),
92
+ Document(content="Doc 2", meta={"priority_str": "1"}),
93
+ Document(content="Doc 3", meta={"priority_float": 1.5}),
94
+ Document(content="Doc 4", meta={"priority_bool": True}),
95
+ ]
96
+ await document_store.write_documents_async(docs)
97
+
98
+ int_values, int_count = await document_store.get_metadata_field_unique_values_async(
99
+ metadata_field="priority_int"
100
+ )
101
+ str_values, str_count = await document_store.get_metadata_field_unique_values_async(
102
+ metadata_field="priority_str"
103
+ )
104
+ float_values, float_count = await document_store.get_metadata_field_unique_values_async(
105
+ metadata_field="priority_float"
106
+ )
107
+ bool_values, bool_count = await document_store.get_metadata_field_unique_values_async(
108
+ metadata_field="priority_bool"
109
+ )
110
+
111
+ assert (int_count, str_count, float_count, bool_count) == (1, 1, 1, 1)
112
+ assert int_values == [1] and type(int_values[0]) is int
113
+ assert str_values == ["1"] and type(str_values[0]) is str
114
+ assert float_values == [1.5] and type(float_values[0]) is float
115
+ assert bool_values == [True] and type(bool_values[0]) is bool
116
+
74
117
  async def test_count_not_empty_async(self, document_store: PgvectorDocumentStore):
75
118
  """Override: mixin method is missing 'self', causing fixture injection to fail."""
76
119
  await document_store.write_documents_async(
@@ -124,22 +167,6 @@ class TestDocumentStoreAsync(
124
167
  filters={"field": "meta.category", "operator": "==", "value": "A"}, meta={}
125
168
  )
126
169
 
127
- async def test_get_metadata_field_unique_values_async_preserves_non_string_types(
128
- self, document_store: PgvectorDocumentStore
129
- ):
130
- """Non-string metadata values (e.g. ints) are returned in their original type, not stringified."""
131
- docs = [
132
- Document(content="Doc 1", meta={"priority": 1}),
133
- Document(content="Doc 2", meta={"priority": 2}),
134
- Document(content="Doc 3", meta={"priority": 1}),
135
- ]
136
- await document_store.write_documents_async(docs)
137
-
138
- values, total = await document_store.get_metadata_field_unique_values_async("meta.priority")
139
-
140
- assert set(values) == {1, 2}
141
- assert total == 2
142
-
143
170
  async def test_delete_table_async_first_call(self, document_store: PgvectorDocumentStore):
144
171
  """
145
172
  Test that delete_table_async can be executed as the initial operation on the Document Store