163 lines
5.2 KiB
Python
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
|