Skip to content

There is no type cast in the values of filters with operators. #248

@luminosoda

Description

@luminosoda

Describe the bug or question
If filter of non-string column contains an operator, the request will fail.

To Reproduce

Example (the necessary data has already been inserted into the table in lifetime):
import datetime
from typing import AsyncGenerator

from fastapi import FastAPI
from fastcrud import crud_router
from pydantic import BaseModel
from sqlalchemy import Column, DateTime, Integer, Numeric, String, func, insert, select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker


class Base(DeclarativeBase):
    pass


class Item(Base):
    __tablename__ = "items"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    description = Column(String)
    category = Column(String)
    price = Column(Numeric)
    last_sold = Column(DateTime)
    created_at = Column(DateTime, default=func.now())


class ItemSchema(BaseModel):
    name: str | None = None
    description: str | None = None
    category: str | None = None
    price: float | None = None
    last_sold: datetime.datetime | None = None


DATABASE_URL = "postgresql+asyncpg://user:password@host:port/database"
engine = create_async_engine(DATABASE_URL, echo=True)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)


# Database session dependency
async def get_session() -> AsyncGenerator[AsyncSession, None]:
    async with async_session() as session:
        yield session


# Create tables before the app start
async def lifespan(app: FastAPI):
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

        # Filling table
        count = (await conn.execute(select(func.count()).select_from(Item))).scalar()
        if count != 10:
            for i in range(10):
                await conn.execute(insert(Item).values(price=i))

    yield


# FastAPI app
app = FastAPI(lifespan=lifespan)

item_router = crud_router(
    session=get_session,
    model=Item,
    create_schema=ItemSchema,
    update_schema=ItemSchema,
    path="/items",
    tags=["Items"],
    filter_config={
        "price": None,
        "price__gt": None,
    },
)

app.include_router(item_router)

Run with uvicorn main:app

Perform GET request on http://localhost:8000/items?price__gt=5

Description
Request with a filter and any operator leads to error, due to the lack of cast before querying to database.

sqlalchemy.exc.ProgrammingError: (sqlalchemy.dialects.postgresql.asyncpg.ProgrammingError) <class 'asyncpg.exceptions.UndefinedFunctionError'>: operator does not exist: numeric > character varying
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
[SQL: SELECT items.id, items.name, items.description, items.category, items.price, items.last_sold, items.created_at 
FROM items 
WHERE items.price > $1::VARCHAR 
 LIMIT $2::INTEGER]
[parameters: ('5', 100)]

Screenshots
Image
Image

Additional context
Please note that it is necessary to use a database backend with strict type comparison (e.g. PostgreSQL).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions