hesabixArc/hesabixAPI/app/services/query_service.py

163 lines
5.2 KiB
Python

from __future__ import annotations
from typing import Any, Type, TypeVar
from sqlalchemy import select, func, or_, and_
from sqlalchemy.orm import Session
from sqlalchemy.sql import Select
from adapters.api.v1.schemas import QueryInfo, FilterItem
T = TypeVar('T')
class QueryBuilder:
"""سرویس برای ساخت کوئری‌های دینامیک بر اساس QueryInfo"""
def __init__(self, model_class: Type[T], db_session: Session) -> None:
self.model_class = model_class
self.db = db_session
self.stmt: Select = select(model_class)
def apply_filters(self, filters: list[FilterItem] | None) -> 'QueryBuilder':
"""اعمال فیلترها بر روی کوئری"""
if not filters:
return self
conditions = []
for filter_item in filters:
try:
column = getattr(self.model_class, filter_item.property)
condition = self._build_condition(column, filter_item.operator, filter_item.value)
conditions.append(condition)
except AttributeError:
# اگر فیلد وجود نداشته باشد، آن را نادیده بگیر
continue
if conditions:
self.stmt = self.stmt.where(and_(*conditions))
return self
def apply_search(self, search: str | None, search_fields: list[str] | None) -> 'QueryBuilder':
"""اعمال جستجو بر روی فیلدهای مشخص شده"""
if not search or not search_fields:
return self
conditions = []
for field in search_fields:
try:
column = getattr(self.model_class, field)
conditions.append(column.ilike(f"%{search}%"))
except AttributeError:
# اگر فیلد وجود نداشته باشد، آن را نادیده بگیر
continue
if conditions:
self.stmt = self.stmt.where(or_(*conditions))
return self
def apply_sorting(self, sort_by: str | None, sort_desc: bool) -> 'QueryBuilder':
"""اعمال مرتب‌سازی بر روی کوئری"""
if not sort_by:
return self
try:
column = getattr(self.model_class, sort_by)
if sort_desc:
self.stmt = self.stmt.order_by(column.desc())
else:
self.stmt = self.stmt.order_by(column.asc())
except AttributeError:
# اگر فیلد وجود نداشته باشد، مرتب‌سازی را نادیده بگیر
pass
return self
def apply_pagination(self, skip: int, take: int) -> 'QueryBuilder':
"""اعمال صفحه‌بندی بر روی کوئری"""
self.stmt = self.stmt.offset(skip).limit(take)
return self
def apply_query_info(self, query_info: QueryInfo) -> 'QueryBuilder':
"""اعمال تمام تنظیمات QueryInfo بر روی کوئری"""
return (self
.apply_filters(query_info.filters)
.apply_search(query_info.search, query_info.search_fields)
.apply_sorting(query_info.sort_by, query_info.sort_desc)
.apply_pagination(query_info.skip, query_info.take))
def _build_condition(self, column, operator: str, value: Any):
"""ساخت شرط بر اساس عملگر و مقدار"""
if operator == "=":
return column == value
elif operator == ">":
return column > value
elif operator == ">=":
return column >= value
elif operator == "<":
return column < value
elif operator == "<=":
return column <= value
elif operator == "!=":
return column != value
elif operator == "*": # contains
return column.ilike(f"%{value}%")
elif operator == "?*": # ends with
return column.ilike(f"%{value}")
elif operator == "*?": # starts with
return column.ilike(f"{value}%")
elif operator == "in":
if not isinstance(value, list):
raise ValueError("برای عملگر 'in' مقدار باید آرایه باشد")
return column.in_(value)
else:
raise ValueError(f"عملگر پشتیبانی نشده: {operator}")
def get_count_query(self) -> Select:
"""دریافت کوئری شمارش (بدون pagination)"""
return select(func.count()).select_from(self.stmt.subquery())
def execute(self) -> list[T]:
"""اجرای کوئری و بازگرداندن نتایج"""
return list(self.db.execute(self.stmt).scalars().all())
def execute_count(self) -> int:
"""اجرای کوئری شمارش"""
count_stmt = self.get_count_query()
return int(self.db.execute(count_stmt).scalar() or 0)
class QueryService:
"""سرویس اصلی برای مدیریت کوئری‌های فیلتر شده"""
@staticmethod
def query_with_filters(
model_class: Type[T],
db: Session,
query_info: QueryInfo
) -> tuple[list[T], int]:
"""
اجرای کوئری با فیلتر و بازگرداندن نتایج و تعداد کل
Args:
model_class: کلاس مدل SQLAlchemy
db: جلسه پایگاه داده
query_info: اطلاعات کوئری شامل فیلترها، مرتب‌سازی و صفحه‌بندی
Returns:
tuple: (لیست نتایج, تعداد کل رکوردها)
"""
# کوئری شمارش (بدون pagination)
count_builder = QueryBuilder(model_class, db)
count_builder.apply_filters(query_info.filters)
count_builder.apply_search(query_info.search, query_info.search_fields)
total_count = count_builder.execute_count()
# کوئری داده‌ها (با pagination)
data_builder = QueryBuilder(model_class, db)
data_builder.apply_query_info(query_info)
results = data_builder.execute()
return results, total_count