langflow icon indicating copy to clipboard operation
langflow copied to clipboard

Unable to Delete Flows

Open mbbyn opened this issue 1 year ago • 3 comments

Bug Description

Whenever I try to delete a flow, there is an exception and I am unable to delete the flow.

Reproduction

Create a flow, add some components to it, run it a few times (send data using playground). Try to delete it.

Expected behavior

It should be deletable.

Who can help?

@italojohnny

Operating System

RHEL 8 Langflow Docker image

Langflow Version

1.0.14

Python Version

None

Screenshot

No response

Flow File

No response

mbbyn avatar Aug 06 '24 10:08 mbbyn

The logs are not being printed properly for some reason, but here is the most relevant part. If I had to guess, attempt to delete a flow is cascading NULL to other tables, however, there is a not-null constrain on them. So, they should either be cascade delete or nullable.

NotNullViolation: null value in column
                              "flow_id" of relation "transaction"
                              violates not-null constraint
                              DETAIL:  Failing row contains
                              (2024-08-06 10:28:46.131792,
                              CustomComponent-OBvDV,
                              ChatOutput-rzfAn, {"code": "# from
                              langflow.field_typing import
                              Data\nfrom langflo..., {"results": {},
                              "artifacts": {"success_result":
                              {"repr": "{\n   ..., success, null,
                              null,
                              059c084a-51c5-4465-9e41-a34cef3e1a0e).

mbbyn avatar Aug 06 '24 10:08 mbbyn

It looks like the issue you're encountering when trying to delete a flow is due to a NotNullViolation exception. This happens because deleting the flow is causing NULL values to be cascaded to other tables that have not-null constraints.

To resolve this, you need to ensure that related records are properly handled before deleting the flow. This can be done by either setting the related records to a default value or deleting them as well. Here is an example of how you might modify the delete_flow function to handle related records:

from fastapi import HTTPException, Depends
from sqlmodel import Session, select
from uuid import UUID
from langflow.services.database.models.flow import Flow
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_session, get_settings_service
from langflow.api.v1.flows import read_flow

def delete_flow(
    *,
    session: Session = Depends(get_session),
    flow_id: UUID,
    current_user: User = Depends(get_current_active_user),
    settings_service=Depends(get_settings_service),
):
    """Delete a flow."""
    flow = read_flow(
        session=session,
        flow_id=flow_id,
        current_user=current_user,
        settings_service=settings_service,
    )
    if not flow:
        raise HTTPException(status_code=404, detail="Flow not found")
    
    # Handle related records before deleting the flow
    # Example: If there are related records in another table, handle them here
    # related_records = session.exec(select(RelatedModel).where(RelatedModel.flow_id == flow_id)).all()
    # for record in related_records:
    #     session.delete(record)
    
    session.delete(flow)
    session.commit()
    return {"message": "Flow deleted successfully"}

Make sure to replace the commented section with the actual handling of related records in your database schema. This ensures that when a flow is deleted, any related records are either deleted or updated to prevent NotNullViolation exceptions [1][2].

Additionally, ensure that your database schema is correctly set up to handle these relationships. For example, the Alembic migration script provided shows how to add and remove columns and constraints, which can be useful for managing foreign key relationships and ensuring data integrity:

def upgrade() -> None:
    conn = op.get_bind()
    inspector = Inspector.from_engine(conn)
    tables = inspector.get_table_names()
    try:
        if "flowstyle" in tables:
            op.drop_table("flowstyle")
        with op.batch_alter_table("flow", schema=None) as batch_op:
            flow_columns = [column["name"] for column in inspector.get_columns("flow")]
            if "is_component" not in flow_columns:
                batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True))
            if "updated_at" not in flow_columns:
                batch_op.add_column(sa.Column("updated_at", sa.DateTime(), nullable=True))
            if "folder" not in flow_columns:
                batch_op.add_column(sa.Column("folder", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
            if "user_id" not in flow_columns:
                batch_op.add_column(sa.Column("user_id", sqlmodel.sql.sqltypes.GUID(), nullable=True))
            indices = inspector.get_indexes("flow")
            indices_names = [index["name"] for index in indices]
            if "ix_flow_user_id" not in indices_names:
                batch_op.create_index(batch_op.f("ix_flow_user_id"), ["user_id"], unique=False)
            if "fk_flow_user_id_user" not in indices_names:
                batch_op.create_foreign_key("fk_flow_user_id_user", "user", ["user_id"], ["id"])
    except Exception:
        pass

def downgrade() -> None:
    conn = op.get_bind()
    inspector = Inspector.from_engine(conn)
    try:
        if "flowstyle" not in inspector.get_table_names():
            op.create_table(
                "flowstyle",
                sa.Column("color", sa.String(), nullable=False),
                sa.Column("emoji", sa.String(), nullable=False),
                sa.Column("flow_id", sqlmodel.sql.sqltypes.GUID(), nullable=True),
                sa.Column("id", sqlmodel.sql.sqltypes.GUID(), nullable=False),
                sa.ForeignKeyConstraint(["flow_id"], ["flow.id"]),
                sa.PrimaryKeyConstraint("id"),
                sa.UniqueConstraint("id"),
            )
        with op.batch_alter_table("flow", schema=None) as batch_op:
            flow_columns = [column["name"] for column in inspector.get_columns("flow")]
            if "user_id" in flow_columns:
                batch_op.drop_column("user_id")
            if "folder" in flow_columns:
                batch_op.drop_column("folder")
            if "updated_at" in flow_columns:
                batch_op.drop_column("updated_at")
            if "is_component" in flow_columns:
                batch_op.drop_column("is_component")
            indices = inspector.get_indexes("flow")
            indices_names = [index["name"] for index in indices]
            if "ix_flow_user_id" in indices_names:
                batch_op.drop_index("ix_flow_user_id")
            constraints = inspector.get_foreign_keys("flow")
            constraint_names = [constraint["name"] for constraint in constraints]
            if "fk_flow_user_id_user" in constraint_names:
                batch_op.drop_constraint("fk_flow_user_id_user", type_="foreignkey")
    except Exception as e:
        print(f"Error during downgrade: {e}")

This script shows how to manage columns and constraints, which can help ensure that your database schema supports the necessary relationships and constraints to avoid NotNullViolation exceptions [3].

For further assistance, @ogabrielluiz, your expertise would be greatly appreciated here.

To continue talking to Dosu, mention @dosu.

dosubot[bot] avatar Aug 06 '24 10:08 dosubot[bot]

I'm getting the same issue. What is the fix for this?

unoriginalscreenname avatar Aug 07 '24 19:08 unoriginalscreenname

Is anybody else experiencing this?

unoriginalscreenname avatar Aug 12 '24 07:08 unoriginalscreenname

Same issue for me.. image

Any solutions?

w3XeWIRpuND avatar Aug 12 '24 08:08 w3XeWIRpuND

I've worked around this by stopping Langflow, going into the sqlite database command line, and deleting:

delete from 'transaction' where flow_id = 'e57cdc407439432cb448d7b4a5f1eb59';
delete from flow where id = 'e57cdc407439432cb448d7b4a5f1eb59';

But it definitely looks to be a problem in 1.0.14 (not sure what other versions affected)

You can find the SQLite path with --log-level=debug

mieslep avatar Aug 12 '24 12:08 mieslep

This issue does not reproduce on v1.0.15 (just released).

mieslep avatar Aug 13 '24 08:08 mieslep

Yes, my issue is gone in the latest release. Thank you!

w3XeWIRpuND avatar Aug 14 '24 14:08 w3XeWIRpuND