| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- 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<T> provides a fluent interface for building database queries.
- * Subclasses implement specific query execution strategies for entities
- * and projections.
- *
- * Important: Query<T> does NOT extend Enumerable<T>. Instead, call
- * materialise() or materialise_async() to execute the query and get results.
- *
- * Example usage:
- * {{{
- * var users = session.query<User>()
- * .where(expr("age > $0", new NativeElement<int?>(18)))
- * .order_by(expr("name"))
- * .limit(10)
- * .materialise();
- * }}}
- */
- public abstract class Query<T> : Object {
-
- // Shared state - protected for subclass access
- protected OrmSession _session;
- protected Vector<OrderByClause> _orderings;
- protected int64? _limit;
- protected int64? _offset;
- protected bool _use_or;
- protected Vector<Expression> _where_expressions;
-
- /**
- * Creates a new Query for the given session.
- *
- * This constructor is protected - use OrmSession.query<T>() to create queries.
- *
- * @param session The OrmSession that will execute this query
- */
- protected Query(OrmSession session) {
- _session = session;
- _orderings = new Vector<OrderByClause>();
- _where_expressions = new Vector<Expression>();
- _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<OrderByClause> 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<int?>(18)))
- * }}}
- *
- * @param expression The filter expression
- * @return This query for method chaining
- */
- public virtual Query<T> 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<T> 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<T> 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<T> 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<T> 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<T> offset(int64 count) {
- _offset = count;
- return this;
- }
-
- // === Execution methods ===
-
- /**
- * Executes the query and returns the results as an ImmutableLot.
- *
- * @return An ImmutableLot<T> containing the query results
- * @throws SqlError if query execution fails
- */
- public abstract Invercargill.ImmutableLot<T> materialise() throws SqlError;
-
- /**
- * Executes the query asynchronously and returns the results.
- *
- * @return An ImmutableLot<T> containing the query results
- * @throws SqlError if query execution fails
- */
- public abstract async Invercargill.ImmutableLot<T> 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;
- }
- }
- }
|