Skip to content

Commit c854713

Browse files
authored
feat: Add support for ANY/ALL postgres operators (#19553)
1 parent 83a0dec commit c854713

File tree

4 files changed

+2283
-2183
lines changed

4 files changed

+2283
-2183
lines changed

llama-index-integrations/vector_stores/llama-index-vector-stores-postgres/llama_index/vector_stores/postgres/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,10 @@ def _to_postgres_operator(self, operator: FilterOperator) -> str:
644644
return "ILIKE"
645645
elif operator == FilterOperator.IS_EMPTY:
646646
return "IS NULL"
647+
elif operator == FilterOperator.ANY:
648+
return "?|"
649+
elif operator == FilterOperator.ALL:
650+
return "?&"
647651
else:
648652
_logger.warning(f"Unknown operator: {operator}, fallback to '='")
649653
return "="
@@ -663,6 +667,17 @@ def _build_filter_clause(self, filter_: MetadataFilter) -> Any:
663667
f"{self._to_postgres_operator(filter_.operator)} "
664668
f"({filter_value})"
665669
)
670+
elif filter_.operator in [FilterOperator.ANY, FilterOperator.ALL]:
671+
# Expects a list stored in the metadata, and a single value to compare
672+
673+
# We apply same logic as above, but as an array
674+
filter_value = ", ".join(f"'{e}'" for e in filter_.value)
675+
676+
return text(
677+
f"metadata_::jsonb->'{filter_.key}' "
678+
f"{self._to_postgres_operator(filter_.operator)} "
679+
f"array[{filter_value}]"
680+
)
666681
elif filter_.operator == FilterOperator.CONTAINS:
667682
# Expects a list stored in the metadata, and a single value to compare
668683
return text(

llama-index-integrations/vector_stores/llama-index-vector-stores-postgres/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ dev = [
2727

2828
[project]
2929
name = "llama-index-vector-stores-postgres"
30-
version = "0.5.4"
30+
version = "0.5.5"
3131
description = "llama-index vector_stores postgres integration"
3232
authors = [{name = "Your Name", email = "[email protected]"}]
3333
requires-python = ">=3.9,<4.0"

llama-index-integrations/vector_stores/llama-index-vector-stores-postgres/tests/test_postgres.py

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def node_embeddings() -> List[TextNode]:
222222
text="consectetur adipiscing elit",
223223
id_="ccc",
224224
relationships={NodeRelationship.SOURCE: RelatedNodeInfo(node_id="ccc")},
225-
extra_info={"test_key_list": ["test_value"]},
225+
extra_info={"test_key_list": ["test_value_1", "test_value_2"]},
226226
embedding=_get_sample_vector(0.1),
227227
),
228228
TextNode(
@@ -518,6 +518,91 @@ async def test_add_to_db_and_query_with_metadata_filters_with_in_operator_and_si
518518
assert res.nodes[0].node_id == "bbb"
519519

520520

521+
@pytest.mark.skipif(postgres_not_available, reason="postgres db is not available")
522+
@pytest.mark.asyncio
523+
@pytest.mark.parametrize("pg_fixture", ["pg", "pg_halfvec"], indirect=True)
524+
@pytest.mark.parametrize("use_async", [True, False])
525+
async def test_add_to_db_and_query_with_metadata_filters_with_any_operator(
526+
pg_fixture: PGVectorStore, node_embeddings: List[TextNode], use_async: bool
527+
) -> None:
528+
if use_async:
529+
await pg_fixture.async_add(node_embeddings)
530+
else:
531+
pg_fixture.add(node_embeddings)
532+
assert isinstance(pg_fixture, PGVectorStore)
533+
assert hasattr(pg_fixture, "_engine")
534+
filters = MetadataFilters(
535+
filters=[
536+
MetadataFilter(
537+
key="test_key_list",
538+
value=["test_value_1", "test_value_new"],
539+
operator=FilterOperator.ANY,
540+
)
541+
]
542+
)
543+
q = VectorStoreQuery(
544+
query_embedding=_get_sample_vector(0.5), similarity_top_k=10, filters=filters
545+
)
546+
if use_async:
547+
res = await pg_fixture.aquery(q)
548+
else:
549+
res = pg_fixture.query(q)
550+
assert res.nodes
551+
assert len(res.nodes) == 1
552+
assert res.nodes[0].node_id == "ccc"
553+
554+
555+
@pytest.mark.skipif(postgres_not_available, reason="postgres db is not available")
556+
@pytest.mark.asyncio
557+
@pytest.mark.parametrize("pg_fixture", ["pg", "pg_halfvec"], indirect=True)
558+
@pytest.mark.parametrize("use_async", [True, False])
559+
async def test_add_to_db_and_query_with_metadata_filters_with_all_operator(
560+
pg_fixture: PGVectorStore, node_embeddings: List[TextNode], use_async: bool
561+
) -> None:
562+
if use_async:
563+
await pg_fixture.async_add(node_embeddings)
564+
else:
565+
pg_fixture.add(node_embeddings)
566+
assert isinstance(pg_fixture, PGVectorStore)
567+
assert hasattr(pg_fixture, "_engine")
568+
filters = MetadataFilters(
569+
filters=[
570+
MetadataFilter(
571+
key="test_key_list",
572+
value=["test_value_1", "test_value_2"],
573+
operator=FilterOperator.ALL,
574+
)
575+
]
576+
)
577+
filters_no_all_match = MetadataFilters(
578+
filters=[
579+
MetadataFilter(
580+
key="test_key_list",
581+
value=["test_value_1", "test_value_3"],
582+
operator=FilterOperator.ALL,
583+
)
584+
]
585+
)
586+
q = VectorStoreQuery(
587+
query_embedding=_get_sample_vector(0.5), similarity_top_k=10, filters=filters
588+
)
589+
q2 = VectorStoreQuery(
590+
query_embedding=_get_sample_vector(0.5),
591+
similarity_top_k=10,
592+
filters=filters_no_all_match,
593+
)
594+
if use_async:
595+
res = await pg_fixture.aquery(q)
596+
res_no_match = await pg_fixture.aquery(q2)
597+
else:
598+
res = pg_fixture.query(q)
599+
res_no_match = pg_fixture.query(q2)
600+
assert res.nodes
601+
assert len(res.nodes) == 1
602+
assert res.nodes[0].node_id == "ccc"
603+
assert not res_no_match.nodes
604+
605+
521606
@pytest.mark.skipif(postgres_not_available, reason="postgres db is not available")
522607
@pytest.mark.asyncio
523608
@pytest.mark.parametrize("pg_fixture", ["pg", "pg_halfvec"], indirect=True)
@@ -535,7 +620,7 @@ async def test_add_to_db_and_query_with_metadata_filters_with_contains_operator(
535620
filters=[
536621
MetadataFilter(
537622
key="test_key_list",
538-
value="test_value",
623+
value="test_value_1",
539624
operator=FilterOperator.CONTAINS,
540625
)
541626
]

0 commit comments

Comments
 (0)