QueryBuilder & Advanced Patterns
The QueryBuilder is the centerpiece of Framefoxâs ORM system, providing a fluent interface for constructing complex queries without writing raw SQL. It integrates seamlessly with repositories and offers advanced patterns for sophisticated database operations.
QueryBuilder Fundamentals
Section titled âQueryBuilder FundamentalsâBasic QueryBuilder Usage
Section titled âBasic QueryBuilder UsageâThe QueryBuilder is available through repository methods and provides a fluent interface for query construction:
from framefox.core.orm.abstract_repository import AbstractRepositoryfrom framefox.core.orm.query_builder import QueryBuilderfrom src.entity.user import User
class UserRepository(AbstractRepository[User]): def find_active_users(self) -> list[User]: return (self.query_builder() .where("is_active", True) .order_by("created_at", "desc") .limit(50) .get())
def find_by_email(self, email: str) -> Optional[User]: return (self.query_builder() .where("email", email) .first())
def search_users(self, search_term: str) -> list[User]: return (self.query_builder() .where("username", "like", f"%{search_term}%") .or_where("email", "like", f"%{search_term}%") .where("is_active", True) .order_by("username", "asc") .get())
Where Conditions and Filtering
Section titled âWhere Conditions and FilteringâThe QueryBuilder supports various types of where conditions:
class GameRepository(AbstractRepository[Game]): def find_games_by_criteria(self, filters: dict) -> list[Game]: qb = self.query_builder()
# Simple equality if filters.get('user_id'): qb.where("user_id", filters['user_id'])
# Comparison operators if filters.get('min_score'): qb.where("score", ">=", filters['min_score'])
if filters.get('max_score'): qb.where("score", "<=", filters['max_score'])
# LIKE operations for text search if filters.get('title_search'): qb.where("title", "like", f"%{filters['title_search']}%")
# IN operations for multiple values if filters.get('statuses'): qb.where_in("status", filters['statuses'])
# NOT conditions if filters.get('exclude_user_ids'): qb.where_not_in("user_id", filters['exclude_user_ids'])
# NULL checks if filters.get('has_description'): qb.where_not_null("description") else: qb.where_null("description")
# Date range filtering if filters.get('created_after'): qb.where("created_at", ">=", filters['created_after'])
if filters.get('created_before'): qb.where("created_at", "<=", filters['created_before'])
return qb.get()
Advanced Query Patterns
Section titled âAdvanced Query PatternsâComplex Conditions with Grouping
Section titled âComplex Conditions with Groupingâdef find_premium_users(self) -> list[User]: """Find users who are either VIP or have high-scoring games""" return (self.query_builder() .where_group(lambda q: ( q.where("subscription_type", "VIP") .or_where_exists( self.query_builder() .select("1") .from_table("games") .where_raw("games.user_id = users.id") .where("games.score", ">", 1000) ) )) .where("is_active", True) .get())
def advanced_user_search(self, criteria: dict) -> list[User]: """Complex search with nested conditions""" return (self.query_builder() .where_group(lambda q: ( q.where("username", "like", f"%{criteria['search']}%") .or_where("email", "like", f"%{criteria['search']}%") .or_where("full_name", "like", f"%{criteria['search']}%") )) .where_group(lambda q: ( q.where("account_type", "premium") .or_where("total_spent", ">", 100) )) .where("is_active", True) .order_by("last_login", "desc") .get())
Joins and Relationships
Section titled âJoins and Relationshipsâdef find_users_with_games(self) -> list[User]: """Find users with their associated games using joins""" return (self.query_builder() .join("games", "users.id", "=", "games.user_id") .where("games.score", ">", 500) .where("users.is_active", True) .distinct() .with_relations(["games"]) .get())
def get_high_scoring_users_with_categories(self) -> list[User]: """Complex join across multiple tables""" return (self.query_builder() .join("games", "users.id", "=", "games.user_id") .join("game_categories", "games.category_id", "=", "game_categories.id") .where("games.score", ">", 750) .where("game_categories.difficulty", ">=", 3) .group_by("users.id") .having("AVG(games.score)", ">", 800) .order_by("AVG(games.score)", "desc") .get())
Aggregations and Statistics
Section titled âAggregations and Statisticsâdef get_user_statistics(self, user_id: int) -> dict: """Get comprehensive user statistics using aggregations""" return (self.query_builder() .select([ "users.id", "users.username", "COUNT(games.id) as total_games", "AVG(games.score) as average_score", "MAX(games.score) as highest_score", "SUM(CASE WHEN games.completed = 1 THEN 1 ELSE 0 END) as completed_games", "MIN(games.created_at) as first_game", "MAX(games.created_at) as latest_game" ]) .join("games", "users.id", "=", "games.user_id") .where("users.id", user_id) .group_by("users.id", "users.username") .first())
def get_category_analytics(self) -> list[dict]: """Analytics across game categories""" return (self.query_builder() .select([ "gc.name as category_name", "COUNT(g.id) as total_games", "AVG(g.score) as avg_score", "COUNT(DISTINCT g.user_id) as unique_players", "SUM(g.play_time) as total_play_time" ]) .from_table("game_categories gc") .left_join("games g", "gc.id", "=", "g.category_id") .where("g.is_active", True) .group_by("gc.id", "gc.name") .having("COUNT(g.id)", ">", 0) .order_by("total_games", "desc") .get())
Subqueries and Exists Operations
Section titled âSubqueries and Exists Operationsâdef find_users_without_recent_activity(self, days: int = 30) -> list[User]: """Find users without recent game activity using NOT EXISTS""" cutoff_date = datetime.now() - timedelta(days=days)
return (self.query_builder() .where("is_active", True) .where_not_exists( self.query_builder() .select("1") .from_table("games") .where_raw("games.user_id = users.id") .where("games.created_at", ">", cutoff_date) ) .order_by("last_login", "asc") .get())
def find_top_performers_in_category(self, category_id: int, limit: int = 10) -> list[User]: """Find top performers using correlated subqueries""" return (self.query_builder() .where_exists( self.query_builder() .select("1") .from_table("games") .where_raw("games.user_id = users.id") .where("games.category_id", category_id) .where_raw( "games.score >= (SELECT AVG(score) * 1.5 FROM games WHERE category_id = ?)", [category_id] ) ) .order_by_raw( "(SELECT MAX(score) FROM games WHERE user_id = users.id AND category_id = ?) DESC", [category_id] ) .limit(limit) .get())
Advanced Repository Patterns
Section titled âAdvanced Repository PatternsâSpecification Pattern for Business Logic
Section titled âSpecification Pattern for Business LogicâImplement the Specification pattern to encapsulate complex business rules:
from abc import ABC, abstractmethodfrom typing import Protocol
class Specification(Protocol): """Specification interface for business rules"""
def is_satisfied_by(self, entity: Any) -> bool: """Check if entity satisfies the specification""" ...
def to_query_builder(self, qb: QueryBuilder) -> QueryBuilder: """Apply specification to query builder""" ...
class ActiveUserSpecification: """Specification for active users"""
def is_satisfied_by(self, user: User) -> bool: return user.is_active and user.email_verified
def to_query_builder(self, qb: QueryBuilder) -> QueryBuilder: return qb.where("is_active", True).where("email_verified", True)
class HighScoringPlayerSpecification: """Specification for high-scoring players"""
def __init__(self, min_average_score: float = 750.0): self.min_average_score = min_average_score
def is_satisfied_by(self, user: User) -> bool: if not user.games: return False avg_score = sum(game.score for game in user.games) / len(user.games) return avg_score >= self.min_average_score
def to_query_builder(self, qb: QueryBuilder) -> QueryBuilder: return (qb.join("games", "users.id", "=", "games.user_id") .group_by("users.id") .having("AVG(games.score)", ">=", self.min_average_score))
class RecentActivitySpecification: """Specification for users with recent activity"""
def __init__(self, days: int = 7): self.cutoff_date = datetime.now() - timedelta(days=days)
def to_query_builder(self, qb: QueryBuilder) -> QueryBuilder: return qb.where("last_login", ">=", self.cutoff_date)
class CompositeSpecification: """Combine multiple specifications with AND logic"""
def __init__(self, *specifications: Specification): self.specifications = specifications
def to_query_builder(self, qb: QueryBuilder) -> QueryBuilder: for spec in self.specifications: qb = spec.to_query_builder(qb) return qb
# Repository using specificationsclass UserRepository(AbstractRepository[User]): def find_by_specification(self, specification: Specification, limit: int = None, offset: int = None) -> list[User]: """Find users matching business specification""" qb = self.query_builder() qb = specification.to_query_builder(qb)
if limit: qb = qb.limit(limit) if offset: qb = qb.offset(offset)
return qb.get()
def get_elite_active_players(self) -> list[User]: """Business query: active users with high scores and recent activity""" elite_spec = CompositeSpecification( ActiveUserSpecification(), HighScoringPlayerSpecification(800.0), RecentActivitySpecification(14) )
return self.find_by_specification(elite_spec)