projection-query.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using Invercargill.DataStructures;
  2. using Invercargill.Expressions;
  3. using InvercargillSql.Dialects;
  4. using InvercargillSql.Orm;
  5. namespace InvercargillSql.Orm.Projections {
  6. /**
  7. * Query builder for projection types.
  8. *
  9. * ProjectionQuery<TProjection> provides a fluent interface for building
  10. * database queries that return projection objects instead of full entities.
  11. * It supports WHERE clauses, ORDER BY, LIMIT, and OFFSET.
  12. *
  13. * The key difference from EntityQuery<T> is that ProjectionQuery uses the
  14. * ProjectionSqlBuilder to generate SQL that includes JOINs and aggregates
  15. * based on the projection definition.
  16. *
  17. * Example usage:
  18. * {{{
  19. * var results = session.projection_query<UserOrderStats>()
  20. * .where("user_id > 100")
  21. * .or_where("order_count >= 5")
  22. * .order_by_desc("total_spent")
  23. * .limit(10)
  24. * .materialise();
  25. * }}}
  26. *
  27. * Note: This class extends Query<TProjection> and uses the base class
  28. * for common query operations. The where_expr() method is not supported
  29. * for projections - use where() with string expressions instead.
  30. *
  31. * @param TProjection The projection result type
  32. */
  33. public class ProjectionQuery<TProjection> : Query<TProjection> {
  34. /**
  35. * The projection definition for building SQL.
  36. */
  37. private ProjectionDefinition _query_definition;
  38. /**
  39. * The SQL builder for this projection.
  40. */
  41. private ProjectionSqlBuilder _query_sql_builder;
  42. /**
  43. * Creates a new ProjectionQuery.
  44. *
  45. * This constructor is internal - use OrmSession.projection_query<T>()
  46. * to create queries.
  47. *
  48. * @param session The OrmSession that will execute this query
  49. * @param definition The projection definition
  50. * @param sql_builder The SQL builder for this projection
  51. */
  52. internal ProjectionQuery(
  53. OrmSession session,
  54. ProjectionDefinition definition,
  55. ProjectionSqlBuilder sql_builder
  56. ) {
  57. base(session); // Initialize base class with session
  58. _query_definition = definition;
  59. _query_sql_builder = sql_builder;
  60. }
  61. // === Abstract property implementations ===
  62. /**
  63. * The filter expression for this query.
  64. *
  65. * Returns null for projection queries which use string-based where clauses
  66. * stored in _where_clauses from the base class.
  67. */
  68. internal override Expression? filter {
  69. get { return null; }
  70. }
  71. /**
  72. * The ORDER BY clauses for this query.
  73. *
  74. * Returns the _orderings vector from the base class.
  75. */
  76. internal override Vector<OrderByClause> orderings {
  77. get { return _orderings; }
  78. }
  79. /**
  80. * The LIMIT value for this query, or null if not set.
  81. *
  82. * Returns the _limit value from the base class.
  83. */
  84. internal override int64? limit_value {
  85. get { return _limit; }
  86. }
  87. /**
  88. * The OFFSET value for this query, or null if not set.
  89. *
  90. * Returns the _offset value from the base class.
  91. */
  92. internal override int64? offset_value {
  93. get { return _offset; }
  94. }
  95. // === Abstract method implementations ===
  96. /**
  97. * Adds a WHERE clause using a pre-parsed Expression.
  98. *
  99. * This method is not supported for projection queries because they
  100. * need to translate expressions through the projection definition.
  101. * Use where() with a string expression instead.
  102. *
  103. * Note: This method always throws an error. The base class doesn't declare
  104. * throws, so we throw without declaration - callers should be aware this
  105. * method may fail at runtime.
  106. *
  107. * @param expression The filter expression
  108. * @return This query for method chaining (never returns - always throws)
  109. */
  110. public override Query<TProjection> where_expr(Expression expression) {
  111. // Throw without declaration - base class doesn't declare throws
  112. throw new SqlError.GENERAL_ERROR(
  113. "ProjectionQuery does not support where_expr(). Use where() with a string expression instead."
  114. );
  115. }
  116. /**
  117. * Materializes results synchronously.
  118. *
  119. * Executes the query and returns all matching projections.
  120. *
  121. * @return An ImmutableLot of TProjection instances
  122. * @throws SqlError if query execution fails
  123. */
  124. public override Invercargill.ImmutableLot<TProjection> materialise() throws SqlError {
  125. // Build the SQL using the projection SQL builder
  126. // Uses get_combined_where() from base class
  127. string? where_expr = get_combined_where();
  128. BuiltQuery built = _query_sql_builder.build_with_split(
  129. where_expr,
  130. _orderings, // Use base class field
  131. _limit, // Use base class field
  132. _offset // Use base class field
  133. );
  134. string sql = built.sql;
  135. // Execute through session's connection using _session from base class
  136. var connection = _session.get_connection();
  137. var command = connection.create_command(sql);
  138. var results = command.execute_query();
  139. // Materialize the results using the projection mapper
  140. var mapper = new ProjectionMapper<TProjection>(_query_definition);
  141. try {
  142. var vector = mapper.map_all(results);
  143. return vector.to_immutable_buffer();
  144. } catch (ProjectionError e) {
  145. throw new SqlError.GENERAL_ERROR(
  146. @"Failed to materialize projection: $(e.message)"
  147. );
  148. }
  149. }
  150. /**
  151. * Materializes results asynchronously.
  152. *
  153. * Executes the query asynchronously and returns all matching projections.
  154. * Uses the async/yield pattern to properly propagate async behavior.
  155. *
  156. * @return An ImmutableLot of TProjection instances
  157. * @throws SqlError if query execution fails
  158. */
  159. public override async Invercargill.ImmutableLot<TProjection> materialise_async() throws SqlError {
  160. // Build the SQL using the projection SQL builder
  161. // Uses get_combined_where() from base class
  162. string? where_expr = get_combined_where();
  163. BuiltQuery built = _query_sql_builder.build_with_split(
  164. where_expr,
  165. _orderings, // Use base class field
  166. _limit, // Use base class field
  167. _offset // Use base class field
  168. );
  169. string sql = built.sql;
  170. // Execute through session's connection using _session from base class
  171. var connection = _session.get_connection();
  172. var command = connection.create_command(sql);
  173. // Execute asynchronously using yield
  174. var results = yield command.execute_query_async();
  175. // Materialize the results using the projection mapper
  176. var mapper = new ProjectionMapper<TProjection>(_query_definition);
  177. try {
  178. var vector = mapper.map_all(results);
  179. return vector.to_immutable_buffer();
  180. } catch (ProjectionError e) {
  181. throw new SqlError.GENERAL_ERROR(
  182. @"Failed to materialize projection: $(e.message)"
  183. );
  184. }
  185. }
  186. /**
  187. * Gets the SQL for this query.
  188. *
  189. * This method is useful for debugging and logging.
  190. *
  191. * @return The SQL SELECT statement
  192. */
  193. public override string to_sql() {
  194. // Uses get_combined_where() from base class
  195. string? where_expr = get_combined_where();
  196. BuiltQuery built = _query_sql_builder.build_with_split(
  197. where_expr,
  198. _orderings, // Use base class field
  199. _limit, // Use base class field
  200. _offset // Use base class field
  201. );
  202. return built.sql;
  203. }
  204. // === Internal accessor methods for compatibility ===
  205. /**
  206. * Gets the projection definition for this query.
  207. *
  208. * @return The ProjectionDefinition
  209. */
  210. internal ProjectionDefinition get_definition() {
  211. return _query_definition;
  212. }
  213. /**
  214. * Gets the SQL builder for this query.
  215. *
  216. * @return The ProjectionSqlBuilder
  217. */
  218. internal ProjectionSqlBuilder get_sql_builder() {
  219. return _query_sql_builder;
  220. }
  221. /**
  222. * Gets the WHERE clauses for this query.
  223. *
  224. * Returns the _where_clauses vector from the base class.
  225. *
  226. * @return The Vector of WHERE expressions
  227. */
  228. internal Vector<string> get_where_clauses() {
  229. return _where_clauses;
  230. }
  231. /**
  232. * Gets the ORDER BY clauses for this query.
  233. *
  234. * Returns the _orderings vector from the base class.
  235. *
  236. * @return The Vector of OrderByClause
  237. */
  238. internal Vector<OrderByClause> get_order_by_clauses() {
  239. return _orderings;
  240. }
  241. /**
  242. * Gets the LIMIT value for this query.
  243. *
  244. * Returns the _limit value from the base class.
  245. *
  246. * @return The LIMIT value, or null if not set
  247. */
  248. internal int64? get_limit_value() {
  249. return _limit;
  250. }
  251. /**
  252. * Gets the OFFSET value for this query.
  253. *
  254. * Returns the _offset value from the base class.
  255. *
  256. * @return The OFFSET value, or null if not set
  257. */
  258. internal int64? get_offset_value() {
  259. return _offset;
  260. }
  261. /**
  262. * Gets whether OR logic is used for WHERE clauses.
  263. *
  264. * Returns the _use_or value from the base class.
  265. *
  266. * @return True if OR logic is used
  267. */
  268. internal bool get_use_or() {
  269. return _use_or;
  270. }
  271. }
  272. }