Skip to content
73 changes: 62 additions & 11 deletions examples/pg_vectorstore_how_to.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"id": "irl7eMFnSPZr",
"metadata": {
"id": "irl7eMFnSPZr"
Expand Down Expand Up @@ -117,7 +117,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -131,7 +131,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -151,7 +151,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -178,7 +178,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"metadata": {
"id": "avlyHEMn6gzU"
},
Expand Down Expand Up @@ -219,7 +219,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
Expand Down Expand Up @@ -247,7 +247,7 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": null,
"metadata": {
"id": "z-AZyzAQ7bsf"
},
Expand Down Expand Up @@ -333,7 +333,9 @@
"source": [
"### Delete documents\n",
"\n",
"Documents can be deleted using ids."
"Documents can be deleted using IDs or metadata filters.\n",
"\n",
"#### Delete by IDs"
]
},
{
Expand All @@ -345,6 +347,47 @@
"await store.adelete([ids[1]])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Delete by metadata filter\n",
"\n",
"You can delete documents based on metadata filters. This is useful for bulk deletion operations."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Delete all documents with a specific metadata value\n",
"await store.adelete(filter={\"source\": \"documentation\"})"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Delete documents matching complex filter criteria\n",
"await store.adelete(\n",
" filter={\"$and\": [{\"category\": \"obsolete\"}, {\"year\": {\"$lt\": 2020}}]}\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Delete by both IDs and filter (must match both criteria)\n",
"await store.adelete(ids=[\"id1\", \"id2\"], filter={\"status\": \"archived\"})"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -496,7 +539,7 @@
},
{
"cell_type": "code",
"execution_count": 19,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
Expand Down Expand Up @@ -959,6 +1002,13 @@
"source": [
"await pg_engine.adrop_table(TABLE_NAME)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
Expand All @@ -967,7 +1017,8 @@
"toc_visible": true
},
"kernelspec": {
"display_name": "Python 3",
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
Expand All @@ -980,7 +1031,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.8"
"version": "3.12.11"
}
},
"nbformat": 4,
Expand Down
76 changes: 72 additions & 4 deletions langchain_postgres/v2/async_vectorstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,19 +415,60 @@ async def aadd_documents(
async def adelete(
self,
ids: Optional[list] = None,
filter: Optional[dict] = None,
**kwargs: Any,
) -> Optional[bool]:
"""Delete records from the table.

Args:
ids: List of document IDs to delete.
filter: Metadata filter dictionary for bulk deletion.
Supports the same filter syntax as similarity_search.
Note: Filters only work on fields defined in metadata_columns,
not on fields stored in the metadata_json_column.

Returns:
True if deletion was successful, False if no criteria provided.

Raises:
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.

Examples:
Delete by IDs:
await vectorstore.adelete(ids=["id1", "id2"])

Delete by metadata filter (requires metadata_columns):
await vectorstore.adelete(filter={"source": "documentation"})
await vectorstore.adelete(filter={"$and": [{"category": "obsolete"}, {"year": {"$lt": 2020}}]})

Delete by both IDs and filter (must match both criteria):
await vectorstore.adelete(ids=["id1", "id2"], filter={"status": "archived"})
"""
if not ids:
if not ids and not filter:
return False

placeholders = ", ".join(f":id_{i}" for i in range(len(ids)))
param_dict = {f"id_{i}": id for i, id in enumerate(ids)}
query = f'DELETE FROM "{self.schema_name}"."{self.table_name}" WHERE {self.id_column} in ({placeholders})'
where_clauses = []
param_dict = {}

# Handle ID-based deletion
if ids:
placeholders = ", ".join(f":id_{i}" for i in range(len(ids)))
id_params = {f"id_{i}": id for i, id in enumerate(ids)}
param_dict.update(id_params)
where_clauses.append(f"{self.id_column} in ({placeholders})")

# Handle filter-based deletion
if filter:
filter_clause, filter_params = self._create_filter_clause(filter)
param_dict.update(filter_params)
where_clauses.append(filter_clause)

# Combine WHERE clauses with AND if both are present
where_clause = " AND ".join(where_clauses)
query = (
f'DELETE FROM "{self.schema_name}"."{self.table_name}" WHERE {where_clause}'
)

async with self.engine.connect() as conn:
await conn.execute(text(query), param_dict)
await conn.commit()
Expand Down Expand Up @@ -1337,8 +1378,35 @@ def add_documents(
def delete(
self,
ids: Optional[list] = None,
filter: Optional[dict] = None,
**kwargs: Any,
) -> Optional[bool]:
"""Delete records from the table.

Args:
ids: List of document IDs to delete.
filter: Metadata filter dictionary for bulk deletion.
Supports the same filter syntax as similarity_search.
Note: Filters only work on fields defined in metadata_columns,
not on fields stored in the metadata_json_column.

Returns:
True if deletion was successful, False if no criteria provided.

Raises:
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.

Examples:
Delete by IDs:
vectorstore.delete(ids=["id1", "id2"])

Delete by metadata filter (requires metadata_columns):
vectorstore.delete(filter={"source": "documentation"})
vectorstore.delete(filter={"$and": [{"category": "obsolete"}, {"year": {"$lt": 2020}}]})

Delete by both IDs and filter (must match both criteria):
vectorstore.delete(ids=["id1", "id2"], filter={"status": "archived"})
"""
raise NotImplementedError(
"Sync methods are not implemented for AsyncPGVectorStore. Use PGVectorStore interface instead."
)
Expand Down
52 changes: 50 additions & 2 deletions langchain_postgres/v2/vectorstores.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,26 +266,74 @@ def add_documents(
async def adelete(
self,
ids: Optional[list] = None,
filter: Optional[dict] = None,
**kwargs: Any,
) -> Optional[bool]:
"""Delete records from the table.

Args:
ids: List of document IDs to delete.
filter: Metadata filter dictionary for bulk deletion.
Supports the same filter syntax as similarity_search.
Note: Filters only work on fields defined in metadata_columns,
not on fields stored in the metadata_json_column.

Returns:
True if deletion was successful, False if no criteria provided.

Raises:
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.

Examples:
Delete by IDs:
await vectorstore.adelete(ids=["id1", "id2"])

Delete by metadata filter (requires metadata_columns):
await vectorstore.adelete(filter={"source": "documentation"})
await vectorstore.adelete(filter={"$and": [{"category": "obsolete"}, {"year": {"$lt": 2020}}]})

Delete by both IDs and filter (must match both criteria):
await vectorstore.adelete(ids=["id1", "id2"], filter={"status": "archived"})
"""
return await self._engine._run_as_async(self.__vs.adelete(ids, **kwargs))
return await self._engine._run_as_async(
self.__vs.adelete(ids, filter=filter, **kwargs)
)

def delete(
self,
ids: Optional[list] = None,
filter: Optional[dict] = None,
**kwargs: Any,
) -> Optional[bool]:
"""Delete records from the table.

Args:
ids: List of document IDs to delete.
filter: Metadata filter dictionary for bulk deletion.
Supports the same filter syntax as similarity_search.
Note: Filters only work on fields defined in metadata_columns,
not on fields stored in the metadata_json_column.

Returns:
True if deletion was successful, False if no criteria provided.

Raises:
:class:`InvalidTextRepresentationError <asyncpg.exceptions.InvalidTextRepresentationError>`: if the `ids` data type does not match that of the `id_column`.

Examples:
Delete by IDs:
vectorstore.delete(ids=["id1", "id2"])

Delete by metadata filter (requires metadata_columns):
vectorstore.delete(filter={"source": "documentation"})
vectorstore.delete(filter={"$and": [{"category": "obsolete"}, {"year": {"$lt": 2020}}]})

Delete by both IDs and filter (must match both criteria):
vectorstore.delete(ids=["id1", "id2"], filter={"status": "archived"})
"""
return self._engine._run_as_sync(self.__vs.adelete(ids, **kwargs))
return self._engine._run_as_sync(
self.__vs.adelete(ids, filter=filter, **kwargs)
)

@classmethod
async def afrom_texts( # type: ignore[override]
Expand Down
Loading