using Invercargill.Expressions; using Invercargill.DataStructures; namespace InvercargillSql.Orm { /** * Represents an ORDER BY clause with expression and direction. */ public class OrderByClause : Object { /** * The expression to order by. */ public Expression expression { get; construct; } /** * Whether to order in descending order. */ public bool descending { get; construct; } /** * Creates a new OrderByClause. * * @param expression The expression to order by * @param descending True for descending order, false for ascending */ public OrderByClause(Expression expression, bool descending) { Object(expression: expression, descending: descending); } } /** * Abstract base class for all queries. * * Query provides a fluent interface for building database queries. * Subclasses implement specific query execution strategies for entities * and projections. * * Important: Query does NOT extend Enumerable. Instead, call * materialise() or materialise_async() to execute the query and get results. * * Example usage: * {{{ * var users = session.query() * .where(expr("age > $0", new NativeElement(18))) * .order_by(expr("name")) * .limit(10) * .materialise(); * }}} */ public abstract class Query : Object { // Shared state - protected for subclass access protected OrmSession _session; protected Vector _orderings; protected int64? _limit; protected int64? _offset; protected bool _use_or; protected Vector _where_expressions; /** * Creates a new Query for the given session. * * This constructor is protected - use OrmSession.query() to create queries. * * @param session The OrmSession that will execute this query */ protected Query(OrmSession session) { _session = session; _orderings = new Vector(); _where_expressions = new Vector(); _limit = null; _offset = null; _use_or = false; } // === Internal accessors for OrmSession compatibility === /** * The filter expression for this query (for entity queries). * Returns null for projection queries which use string-based where clauses. */ internal abstract owned Expression? filter { owned get; } /** * The ORDER BY clauses for this query. */ internal abstract Vector orderings { get; } /** * The LIMIT value for this query, or null if not set. */ internal abstract int64? limit_value { get; } /** * The OFFSET value for this query, or null if not set. */ internal abstract int64? offset_value { get; } // === Fluent query builders === /** * Adds a WHERE clause using an Expression. * * Use the expr() convenience function to create expressions: * {{{ * .where(expr("age > $0", new NativeElement(18))) * }}} * * @param expression The filter expression * @return This query for method chaining */ public virtual Query where(Expression expression) { _where_expressions.add(expression); return this; } /** * Adds an OR WHERE clause using an Expression. * * Multiple where clauses will be combined with OR instead of AND. * * @param expression The filter expression * @return This query for method chaining */ public virtual Query or_where(Expression expression) { _use_or = true; _where_expressions.add(expression); return this; } /** * Adds an ascending ORDER BY clause. * * @param expression The expression to order by * @return This query for method chaining */ public virtual Query order_by(Expression expression) { _orderings.add(new OrderByClause(expression, false)); return this; } /** * Adds a descending ORDER BY clause. * * @param expression The expression to order by * @return This query for method chaining */ public virtual Query order_by_desc(Expression expression) { _orderings.add(new OrderByClause(expression, true)); return this; } /** * Sets the maximum number of results to return. * * @param count The maximum number of results * @return This query for method chaining */ public virtual Query limit(int64 count) { _limit = count; return this; } /** * Sets the number of results to skip. * * @param count The number of results to skip * @return This query for method chaining */ public virtual Query offset(int64 count) { _offset = count; return this; } // === Execution methods === /** * Executes the query and returns the results as an ImmutableLot. * * @return An ImmutableLot containing the query results * @throws SqlError if query execution fails */ public abstract Invercargill.ImmutableLot materialise() throws SqlError; /** * Executes the query asynchronously and returns the results. * * @return An ImmutableLot containing the query results * @throws SqlError if query execution fails */ public abstract async Invercargill.ImmutableLot materialise_async() throws SqlError; /** * Executes the query and returns the first result. * * This method automatically applies a limit of 1 before executing. * * @return The first result, or null if no results * @throws SqlError if query execution fails */ public virtual T? first() throws SqlError { _limit = 1; var results = materialise(); if (results.length > 0) { return results.first(); } return null; } /** * Executes the query asynchronously and returns the first result. * * This method automatically applies a limit of 1 before executing. * * @return The first result, or null if no results * @throws SqlError if query execution fails */ public virtual async T? first_async() throws SqlError { _limit = 1; var results = yield materialise_async(); if (results.length > 0) { return results.first(); } return null; } /** * Returns the SQL for this query. * * @return The SQL string for this query */ public abstract string to_sql(); // === Protected helpers === /** * Combines all where expressions into a single Expression. * * If there are no where expressions, returns null. * If there is one expression, returns it directly. * If there are multiple expressions, combines them with AND or OR * based on the _use_or flag. * * @return The combined where expression, or null if no expressions */ protected Expression? get_combined_where() { if (_where_expressions.length == 0) { return null; } if (_where_expressions.length == 1) { return _where_expressions.get(0); } // Combine multiple expressions with AND or OR BinaryOperator op = _use_or ? BinaryOperator.OR : BinaryOperator.AND; Expression? result = _where_expressions.get(0); for (uint i = 1; i < _where_expressions.length; i++) { result = new BinaryExpression( result, _where_expressions.get(i), op ); } return result; } } }