using Invercargill.DataStructures; using Invercargill.Expressions; using InvercargillSql.Dialects; using InvercargillSql.Orm; namespace InvercargillSql.Orm.Projections { /** * Query builder for projection types. * * ProjectionQuery provides a fluent interface for building * database queries that return projection objects instead of full entities. * It supports WHERE clauses, ORDER BY, LIMIT, and OFFSET. * * The key difference from EntityQuery is that ProjectionQuery uses the * ProjectionSqlBuilder to generate SQL that includes JOINs and aggregates * based on the projection definition. * * Example usage: * {{{ * var results = session.projection_query() * .where("user_id > 100") * .or_where("order_count >= 5") * .order_by_desc("total_spent") * .limit(10) * .materialise(); * }}} * * Note: This class extends Query and uses the base class * for common query operations. The where_expr() method is not supported * for projections - use where() with string expressions instead. * * @param TProjection The projection result type */ public class ProjectionQuery : Query { /** * The projection definition for building SQL. */ private ProjectionDefinition _query_definition; /** * The SQL builder for this projection. */ private ProjectionSqlBuilder _query_sql_builder; /** * Creates a new ProjectionQuery. * * This constructor is internal - use OrmSession.projection_query() * to create queries. * * @param session The OrmSession that will execute this query * @param definition The projection definition * @param sql_builder The SQL builder for this projection */ internal ProjectionQuery( OrmSession session, ProjectionDefinition definition, ProjectionSqlBuilder sql_builder ) { base(session); // Initialize base class with session _query_definition = definition; _query_sql_builder = sql_builder; } // === Abstract property implementations === /** * The filter expression for this query. * * Returns null for projection queries which use string-based where clauses * stored in _where_clauses from the base class. */ internal override Expression? filter { get { return null; } } /** * The ORDER BY clauses for this query. * * Returns the _orderings vector from the base class. */ internal override Vector orderings { get { return _orderings; } } /** * The LIMIT value for this query, or null if not set. * * Returns the _limit value from the base class. */ internal override int64? limit_value { get { return _limit; } } /** * The OFFSET value for this query, or null if not set. * * Returns the _offset value from the base class. */ internal override int64? offset_value { get { return _offset; } } // === Abstract method implementations === /** * Adds a WHERE clause using a pre-parsed Expression. * * This method is not supported for projection queries because they * need to translate expressions through the projection definition. * Use where() with a string expression instead. * * Note: This method always throws an error. The base class doesn't declare * throws, so we throw without declaration - callers should be aware this * method may fail at runtime. * * @param expression The filter expression * @return This query for method chaining (never returns - always throws) */ public override Query where_expr(Expression expression) { // Throw without declaration - base class doesn't declare throws throw new SqlError.GENERAL_ERROR( "ProjectionQuery does not support where_expr(). Use where() with a string expression instead." ); } /** * Materializes results synchronously. * * Executes the query and returns all matching projections. * * @return An ImmutableLot of TProjection instances * @throws SqlError if query execution fails */ public override Invercargill.ImmutableLot materialise() throws SqlError { // Build the SQL using the projection SQL builder // Uses get_combined_where() from base class string? where_expr = get_combined_where(); BuiltQuery built = _query_sql_builder.build_with_split( where_expr, _orderings, // Use base class field _limit, // Use base class field _offset // Use base class field ); string sql = built.sql; // Execute through session's connection using _session from base class var connection = _session.get_connection(); var command = connection.create_command(sql); var results = command.execute_query(); // Materialize the results using the projection mapper var mapper = new ProjectionMapper(_query_definition); try { var vector = mapper.map_all(results); return vector.to_immutable_buffer(); } catch (ProjectionError e) { throw new SqlError.GENERAL_ERROR( @"Failed to materialize projection: $(e.message)" ); } } /** * Materializes results asynchronously. * * Executes the query asynchronously and returns all matching projections. * Uses the async/yield pattern to properly propagate async behavior. * * @return An ImmutableLot of TProjection instances * @throws SqlError if query execution fails */ public override async Invercargill.ImmutableLot materialise_async() throws SqlError { // Build the SQL using the projection SQL builder // Uses get_combined_where() from base class string? where_expr = get_combined_where(); BuiltQuery built = _query_sql_builder.build_with_split( where_expr, _orderings, // Use base class field _limit, // Use base class field _offset // Use base class field ); string sql = built.sql; // Execute through session's connection using _session from base class var connection = _session.get_connection(); var command = connection.create_command(sql); // Execute asynchronously using yield var results = yield command.execute_query_async(); // Materialize the results using the projection mapper var mapper = new ProjectionMapper(_query_definition); try { var vector = mapper.map_all(results); return vector.to_immutable_buffer(); } catch (ProjectionError e) { throw new SqlError.GENERAL_ERROR( @"Failed to materialize projection: $(e.message)" ); } } /** * Gets the SQL for this query. * * This method is useful for debugging and logging. * * @return The SQL SELECT statement */ public override string to_sql() { // Uses get_combined_where() from base class string? where_expr = get_combined_where(); BuiltQuery built = _query_sql_builder.build_with_split( where_expr, _orderings, // Use base class field _limit, // Use base class field _offset // Use base class field ); return built.sql; } // === Internal accessor methods for compatibility === /** * Gets the projection definition for this query. * * @return The ProjectionDefinition */ internal ProjectionDefinition get_definition() { return _query_definition; } /** * Gets the SQL builder for this query. * * @return The ProjectionSqlBuilder */ internal ProjectionSqlBuilder get_sql_builder() { return _query_sql_builder; } /** * Gets the WHERE clauses for this query. * * Returns the _where_clauses vector from the base class. * * @return The Vector of WHERE expressions */ internal Vector get_where_clauses() { return _where_clauses; } /** * Gets the ORDER BY clauses for this query. * * Returns the _orderings vector from the base class. * * @return The Vector of OrderByClause */ internal Vector get_order_by_clauses() { return _orderings; } /** * Gets the LIMIT value for this query. * * Returns the _limit value from the base class. * * @return The LIMIT value, or null if not set */ internal int64? get_limit_value() { return _limit; } /** * Gets the OFFSET value for this query. * * Returns the _offset value from the base class. * * @return The OFFSET value, or null if not set */ internal int64? get_offset_value() { return _offset; } /** * Gets whether OR logic is used for WHERE clauses. * * Returns the _use_or value from the base class. * * @return True if OR logic is used */ internal bool get_use_or() { return _use_or; } } }