projection-query.vala 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. using Invercargill.DataStructures;
  2. using Invercargill.Expressions;
  3. using InvercargillSql.Dialects;
  4. using InvercargillSql.Orm;
  5. using InvercargillSql.Expressions;
  6. namespace InvercargillSql.Orm.Projections {
  7. /**
  8. * Query builder for projection types.
  9. *
  10. * ProjectionQuery<TProjection> provides a fluent interface for building
  11. * database queries that return projection objects instead of full entities.
  12. * It supports WHERE clauses, ORDER BY, LIMIT, and OFFSET.
  13. *
  14. * The key difference from EntityQuery<T> is that ProjectionQuery uses the
  15. * ProjectionSqlBuilder to generate SQL that includes JOINs and aggregates
  16. * based on the projection definition.
  17. *
  18. * Example usage:
  19. * {{{
  20. * var results = session.projection_query<UserOrderStats>()
  21. * .where(expr("user_id > $0", new NativeElement<int?>(100)))
  22. * .or_where(expr("order_count >= $0", new NativeElement<int?>(5)))
  23. * .order_by_desc(expr("total_spent"))
  24. * .limit(10)
  25. * .materialise();
  26. * }}}
  27. *
  28. * Note: This class extends Query<TProjection> and uses the base class
  29. * for common query operations. The where() method accepts Expression objects
  30. * which are stored in _where_expressions from the base class.
  31. *
  32. * @param TProjection The projection result type
  33. */
  34. public class ProjectionQuery<TProjection> : Query<TProjection> {
  35. /**
  36. * The projection definition for building SQL.
  37. */
  38. private ProjectionDefinition _query_definition;
  39. /**
  40. * The SQL builder for this projection.
  41. */
  42. private ProjectionSqlBuilder _query_sql_builder;
  43. /**
  44. * Creates a new ProjectionQuery.
  45. *
  46. * This constructor is internal - use OrmSession.projection_query<T>()
  47. * to create queries.
  48. *
  49. * @param session The OrmSession that will execute this query
  50. * @param definition The projection definition
  51. * @param sql_builder The SQL builder for this projection
  52. */
  53. internal ProjectionQuery(
  54. OrmSession session,
  55. ProjectionDefinition definition,
  56. ProjectionSqlBuilder sql_builder
  57. ) {
  58. base(session); // Initialize base class with session
  59. _query_definition = definition;
  60. _query_sql_builder = sql_builder;
  61. }
  62. // === Abstract property implementations ===
  63. /**
  64. * The filter expression for this query.
  65. *
  66. * Returns the combined WHERE expression from _where_expressions.
  67. */
  68. internal override owned Expression? filter {
  69. owned get { return get_combined_where(); }
  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. /**
  96. * Materializes results synchronously.
  97. *
  98. * Executes the query and returns all matching projections.
  99. *
  100. * @return An ImmutableLot of TProjection instances
  101. * @throws SqlError if query execution fails
  102. */
  103. public override Invercargill.ImmutableLot<TProjection> materialise() throws SqlError {
  104. // Get the combined WHERE expression from base class
  105. Expression? where_expr = get_combined_where();
  106. // Translate ORDER BY expressions to strings for ProjectionSqlBuilder
  107. Vector<OrderByClause>? translated_orderings = translate_orderings_for_builder();
  108. BuiltQuery built = _query_sql_builder.build_with_split(
  109. where_expr, // Pass Expression directly
  110. translated_orderings,
  111. _limit,
  112. _offset
  113. );
  114. string sql = built.sql;
  115. // Execute through session's connection using _session from base class
  116. var connection = _session.get_connection();
  117. var command = connection.create_command(sql);
  118. var results = command.execute_query();
  119. // Materialize the results using the projection mapper
  120. var mapper = new ProjectionMapper<TProjection>(_query_definition);
  121. try {
  122. var vector = mapper.map_all(results);
  123. return vector.to_immutable_buffer();
  124. } catch (ProjectionError e) {
  125. throw new SqlError.GENERAL_ERROR(
  126. @"Failed to materialize projection: $(e.message)"
  127. );
  128. }
  129. }
  130. /**
  131. * Materializes results asynchronously.
  132. *
  133. * Executes the query asynchronously and returns all matching projections.
  134. * Uses the async/yield pattern to properly propagate async behavior.
  135. *
  136. * @return An ImmutableLot of TProjection instances
  137. * @throws SqlError if query execution fails
  138. */
  139. public override async Invercargill.ImmutableLot<TProjection> materialise_async() throws SqlError {
  140. // Get the combined WHERE expression from base class
  141. Expression? where_expr = get_combined_where();
  142. // Translate ORDER BY expressions to strings for ProjectionSqlBuilder
  143. Vector<OrderByClause>? translated_orderings = translate_orderings_for_builder();
  144. BuiltQuery built = _query_sql_builder.build_with_split(
  145. where_expr, // Pass Expression directly
  146. translated_orderings,
  147. _limit,
  148. _offset
  149. );
  150. string sql = built.sql;
  151. // Execute through session's connection using _session from base class
  152. var connection = _session.get_connection();
  153. var command = connection.create_command(sql);
  154. // Execute asynchronously using yield
  155. var results = yield command.execute_query_async();
  156. // Materialize the results using the projection mapper
  157. var mapper = new ProjectionMapper<TProjection>(_query_definition);
  158. try {
  159. var vector = mapper.map_all(results);
  160. return vector.to_immutable_buffer();
  161. } catch (ProjectionError e) {
  162. throw new SqlError.GENERAL_ERROR(
  163. @"Failed to materialize projection: $(e.message)"
  164. );
  165. }
  166. }
  167. /**
  168. * Gets the SQL for this query.
  169. *
  170. * This method is useful for debugging and logging.
  171. *
  172. * @return The SQL SELECT statement
  173. */
  174. public override string to_sql() {
  175. // Get the combined WHERE expression from base class
  176. Expression? where_expr = get_combined_where();
  177. // Translate ORDER BY expressions to strings for ProjectionSqlBuilder
  178. Vector<OrderByClause>? translated_orderings = translate_orderings_for_builder();
  179. BuiltQuery built = _query_sql_builder.build_with_split(
  180. where_expr, // Pass Expression directly
  181. translated_orderings,
  182. _limit,
  183. _offset
  184. );
  185. return built.sql;
  186. }
  187. // === Internal accessor methods for compatibility ===
  188. /**
  189. * Gets the projection definition for this query.
  190. *
  191. * @return The ProjectionDefinition
  192. */
  193. internal ProjectionDefinition get_definition() {
  194. return _query_definition;
  195. }
  196. /**
  197. * Gets the SQL builder for this query.
  198. *
  199. * @return The ProjectionSqlBuilder
  200. */
  201. internal ProjectionSqlBuilder get_sql_builder() {
  202. return _query_sql_builder;
  203. }
  204. /**
  205. * Gets the WHERE expressions for this query.
  206. *
  207. * Returns the _where_expressions vector from the base class.
  208. *
  209. * @return The Vector of WHERE expressions
  210. */
  211. internal Vector<Expression> get_where_expressions() {
  212. return _where_expressions;
  213. }
  214. /**
  215. * Gets the ORDER BY clauses for this query.
  216. *
  217. * Returns the _orderings vector from the base class.
  218. *
  219. * @return The Vector of OrderByClause
  220. */
  221. internal Vector<OrderByClause> get_order_by_clauses() {
  222. return _orderings;
  223. }
  224. /**
  225. * Gets the LIMIT value for this query.
  226. *
  227. * Returns the _limit value from the base class.
  228. *
  229. * @return The LIMIT value, or null if not set
  230. */
  231. internal int64? get_limit_value() {
  232. return _limit;
  233. }
  234. /**
  235. * Gets the OFFSET value for this query.
  236. *
  237. * Returns the _offset value from the base class.
  238. *
  239. * @return The OFFSET value, or null if not set
  240. */
  241. internal int64? get_offset_value() {
  242. return _offset;
  243. }
  244. /**
  245. * Gets whether OR logic is used for WHERE clauses.
  246. *
  247. * Returns the _use_or value from the base class.
  248. *
  249. * @return True if OR logic is used
  250. */
  251. internal bool get_use_or() {
  252. return _use_or;
  253. }
  254. // === Private helper methods ===
  255. /**
  256. * Converts an Expression to a SQL string using ExpressionToSqlVisitor.
  257. *
  258. * This method uses the projection context to properly translate
  259. * variable names and resolve friendly names.
  260. *
  261. * @param expr The expression to convert
  262. * @return The SQL string representation
  263. */
  264. private string expression_to_sql_string(Expression expr) {
  265. // Get the entity mapper for the primary source type
  266. // For projections, we use a simplified approach that leverages
  267. // the ProjectionSqlBuilder's translate_expression method
  268. return _query_sql_builder.translate_expression(expr);
  269. }
  270. /**
  271. * Translates ORDER BY clauses for use with ProjectionSqlBuilder.
  272. *
  273. * The ProjectionSqlBuilder expects OrderByClause with string expressions,
  274. * so we need to convert our Expression-based OrderByClause to string-based.
  275. *
  276. * @return A new vector of OrderByClause with string expressions
  277. */
  278. private Vector<OrderByClause>? translate_orderings_for_builder() {
  279. if (_orderings.length == 0) {
  280. return null;
  281. }
  282. var result = new Vector<OrderByClause>();
  283. foreach (var clause in _orderings) {
  284. string expr_str = expression_to_sql_string(clause.expression);
  285. // Create a new OrderByClause with the translated string expression
  286. // Note: We create a string-based OrderByClause using a LiteralExpression
  287. // as a carrier for the string
  288. result.add(new OrderByClause(new LiteralExpression(new Invercargill.NativeElement<string?>(expr_str)), clause.descending));
  289. }
  290. return result;
  291. }
  292. }
  293. }