@undefineds.co/drizzle-solid 0.2.11 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +255 -439
  2. package/dist/core/expressions.d.ts +4 -0
  3. package/dist/core/expressions.d.ts.map +1 -1
  4. package/dist/core/expressions.js +8 -1
  5. package/dist/core/expressions.js.map +1 -1
  6. package/dist/core/order-by.d.ts +9 -0
  7. package/dist/core/order-by.d.ts.map +1 -0
  8. package/dist/core/order-by.js +20 -0
  9. package/dist/core/order-by.js.map +1 -0
  10. package/dist/core/pod-database.d.ts +6 -0
  11. package/dist/core/pod-database.d.ts.map +1 -1
  12. package/dist/core/pod-database.js +121 -31
  13. package/dist/core/pod-database.js.map +1 -1
  14. package/dist/core/pod-dialect.d.ts +10 -1
  15. package/dist/core/pod-dialect.d.ts.map +1 -1
  16. package/dist/core/pod-dialect.js +81 -2
  17. package/dist/core/pod-dialect.js.map +1 -1
  18. package/dist/core/query-builders/delete-query-builder.d.ts +7 -12
  19. package/dist/core/query-builders/delete-query-builder.d.ts.map +1 -1
  20. package/dist/core/query-builders/delete-query-builder.js +47 -20
  21. package/dist/core/query-builders/delete-query-builder.js.map +1 -1
  22. package/dist/core/query-builders/helpers.d.ts +7 -0
  23. package/dist/core/query-builders/helpers.d.ts.map +1 -1
  24. package/dist/core/query-builders/helpers.js +169 -0
  25. package/dist/core/query-builders/helpers.js.map +1 -1
  26. package/dist/core/query-builders/insert-query-builder.d.ts +10 -3
  27. package/dist/core/query-builders/insert-query-builder.d.ts.map +1 -1
  28. package/dist/core/query-builders/insert-query-builder.js +50 -6
  29. package/dist/core/query-builders/insert-query-builder.js.map +1 -1
  30. package/dist/core/query-builders/select-query-builder.d.ts +29 -1
  31. package/dist/core/query-builders/select-query-builder.d.ts.map +1 -1
  32. package/dist/core/query-builders/select-query-builder.js +405 -85
  33. package/dist/core/query-builders/select-query-builder.js.map +1 -1
  34. package/dist/core/query-builders/types.d.ts +5 -3
  35. package/dist/core/query-builders/types.d.ts.map +1 -1
  36. package/dist/core/query-builders/update-query-builder.d.ts +8 -12
  37. package/dist/core/query-builders/update-query-builder.d.ts.map +1 -1
  38. package/dist/core/query-builders/update-query-builder.js +63 -21
  39. package/dist/core/query-builders/update-query-builder.js.map +1 -1
  40. package/dist/core/query-conditions.d.ts +17 -16
  41. package/dist/core/query-conditions.d.ts.map +1 -1
  42. package/dist/core/query-conditions.js.map +1 -1
  43. package/dist/core/resource-resolver/base-resolver.d.ts +8 -4
  44. package/dist/core/resource-resolver/base-resolver.d.ts.map +1 -1
  45. package/dist/core/resource-resolver/base-resolver.js +39 -36
  46. package/dist/core/resource-resolver/base-resolver.js.map +1 -1
  47. package/dist/core/resource-resolver/document-resolver.d.ts.map +1 -1
  48. package/dist/core/resource-resolver/document-resolver.js +75 -78
  49. package/dist/core/resource-resolver/document-resolver.js.map +1 -1
  50. package/dist/core/schema/pod-table.d.ts +1 -0
  51. package/dist/core/schema/pod-table.d.ts.map +1 -1
  52. package/dist/core/schema/pod-table.js +67 -0
  53. package/dist/core/schema/pod-table.js.map +1 -1
  54. package/dist/core/select-plan.d.ts +6 -3
  55. package/dist/core/select-plan.d.ts.map +1 -1
  56. package/dist/core/sparql/builder/expression-builder.d.ts +10 -0
  57. package/dist/core/sparql/builder/expression-builder.d.ts.map +1 -1
  58. package/dist/core/sparql/builder/expression-builder.js +27 -4
  59. package/dist/core/sparql/builder/expression-builder.js.map +1 -1
  60. package/dist/core/sparql/builder/select-builder.d.ts +9 -0
  61. package/dist/core/sparql/builder/select-builder.d.ts.map +1 -1
  62. package/dist/core/sparql/builder/select-builder.js +147 -2
  63. package/dist/core/sparql/builder/select-builder.js.map +1 -1
  64. package/dist/core/utils/debug-logger.d.ts +19 -0
  65. package/dist/core/utils/debug-logger.d.ts.map +1 -0
  66. package/dist/core/utils/debug-logger.js +59 -0
  67. package/dist/core/utils/debug-logger.js.map +1 -0
  68. package/dist/driver.d.ts +4 -0
  69. package/dist/driver.d.ts.map +1 -1
  70. package/dist/driver.js +1 -0
  71. package/dist/driver.js.map +1 -1
  72. package/dist/esm/core/expressions.d.ts +4 -0
  73. package/dist/esm/core/expressions.d.ts.map +1 -1
  74. package/dist/esm/core/expressions.js +6 -0
  75. package/dist/esm/core/expressions.js.map +1 -1
  76. package/dist/esm/core/order-by.d.ts +9 -0
  77. package/dist/esm/core/order-by.d.ts.map +1 -0
  78. package/dist/esm/core/order-by.js +15 -0
  79. package/dist/esm/core/order-by.js.map +1 -0
  80. package/dist/esm/core/pod-database.d.ts +6 -0
  81. package/dist/esm/core/pod-database.d.ts.map +1 -1
  82. package/dist/esm/core/pod-database.js +122 -32
  83. package/dist/esm/core/pod-database.js.map +1 -1
  84. package/dist/esm/core/pod-dialect.d.ts +10 -1
  85. package/dist/esm/core/pod-dialect.d.ts.map +1 -1
  86. package/dist/esm/core/pod-dialect.js +81 -2
  87. package/dist/esm/core/pod-dialect.js.map +1 -1
  88. package/dist/esm/core/query-builders/delete-query-builder.d.ts +7 -12
  89. package/dist/esm/core/query-builders/delete-query-builder.d.ts.map +1 -1
  90. package/dist/esm/core/query-builders/delete-query-builder.js +48 -21
  91. package/dist/esm/core/query-builders/delete-query-builder.js.map +1 -1
  92. package/dist/esm/core/query-builders/helpers.d.ts +7 -0
  93. package/dist/esm/core/query-builders/helpers.d.ts.map +1 -1
  94. package/dist/esm/core/query-builders/helpers.js +164 -0
  95. package/dist/esm/core/query-builders/helpers.js.map +1 -1
  96. package/dist/esm/core/query-builders/insert-query-builder.d.ts +10 -3
  97. package/dist/esm/core/query-builders/insert-query-builder.d.ts.map +1 -1
  98. package/dist/esm/core/query-builders/insert-query-builder.js +50 -6
  99. package/dist/esm/core/query-builders/insert-query-builder.js.map +1 -1
  100. package/dist/esm/core/query-builders/select-query-builder.d.ts +29 -1
  101. package/dist/esm/core/query-builders/select-query-builder.d.ts.map +1 -1
  102. package/dist/esm/core/query-builders/select-query-builder.js +407 -87
  103. package/dist/esm/core/query-builders/select-query-builder.js.map +1 -1
  104. package/dist/esm/core/query-builders/types.d.ts +5 -3
  105. package/dist/esm/core/query-builders/types.d.ts.map +1 -1
  106. package/dist/esm/core/query-builders/update-query-builder.d.ts +8 -12
  107. package/dist/esm/core/query-builders/update-query-builder.d.ts.map +1 -1
  108. package/dist/esm/core/query-builders/update-query-builder.js +64 -22
  109. package/dist/esm/core/query-builders/update-query-builder.js.map +1 -1
  110. package/dist/esm/core/query-conditions.d.ts +17 -16
  111. package/dist/esm/core/query-conditions.d.ts.map +1 -1
  112. package/dist/esm/core/query-conditions.js.map +1 -1
  113. package/dist/esm/core/resource-resolver/base-resolver.d.ts +8 -4
  114. package/dist/esm/core/resource-resolver/base-resolver.d.ts.map +1 -1
  115. package/dist/esm/core/resource-resolver/base-resolver.js +39 -36
  116. package/dist/esm/core/resource-resolver/base-resolver.js.map +1 -1
  117. package/dist/esm/core/resource-resolver/document-resolver.d.ts.map +1 -1
  118. package/dist/esm/core/resource-resolver/document-resolver.js +75 -78
  119. package/dist/esm/core/resource-resolver/document-resolver.js.map +1 -1
  120. package/dist/esm/core/schema/pod-table.d.ts +1 -0
  121. package/dist/esm/core/schema/pod-table.d.ts.map +1 -1
  122. package/dist/esm/core/schema/pod-table.js +66 -0
  123. package/dist/esm/core/schema/pod-table.js.map +1 -1
  124. package/dist/esm/core/select-plan.d.ts +6 -3
  125. package/dist/esm/core/select-plan.d.ts.map +1 -1
  126. package/dist/esm/core/sparql/builder/expression-builder.d.ts +10 -0
  127. package/dist/esm/core/sparql/builder/expression-builder.d.ts.map +1 -1
  128. package/dist/esm/core/sparql/builder/expression-builder.js +27 -4
  129. package/dist/esm/core/sparql/builder/expression-builder.js.map +1 -1
  130. package/dist/esm/core/sparql/builder/select-builder.d.ts +9 -0
  131. package/dist/esm/core/sparql/builder/select-builder.d.ts.map +1 -1
  132. package/dist/esm/core/sparql/builder/select-builder.js +148 -3
  133. package/dist/esm/core/sparql/builder/select-builder.js.map +1 -1
  134. package/dist/esm/core/utils/debug-logger.d.ts +19 -0
  135. package/dist/esm/core/utils/debug-logger.d.ts.map +1 -0
  136. package/dist/esm/core/utils/debug-logger.js +53 -0
  137. package/dist/esm/core/utils/debug-logger.js.map +1 -0
  138. package/dist/esm/driver.d.ts +4 -0
  139. package/dist/esm/driver.d.ts.map +1 -1
  140. package/dist/esm/driver.js +1 -0
  141. package/dist/esm/driver.js.map +1 -1
  142. package/dist/esm/index.d.ts +2 -1
  143. package/dist/esm/index.d.ts.map +1 -1
  144. package/dist/esm/index.js +3 -1
  145. package/dist/esm/index.js.map +1 -1
  146. package/dist/index.d.ts +2 -1
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +8 -2
  149. package/dist/index.js.map +1 -1
  150. package/package.json +17 -6
@@ -8,6 +8,8 @@ const query_conditions_1 = require("../query-conditions");
8
8
  const aggregates_1 = require("../aggregates");
9
9
  const helpers_1 = require("./helpers");
10
10
  const uri_1 = require("../uri");
11
+ const order_by_1 = require("../order-by");
12
+ const expressions_1 = require("../expressions");
11
13
  class SelectQueryBuilder {
12
14
  constructor(session, fields) {
13
15
  this.session = session;
@@ -132,17 +134,35 @@ class SelectQueryBuilder {
132
134
  fullJoin(table, condition) {
133
135
  return this.addJoin('fullJoin', table, condition);
134
136
  }
137
+ crossJoin(table) {
138
+ return this.addJoin('crossJoin', table);
139
+ }
135
140
  groupBy(...fields) {
136
141
  const refs = fields.map((field) => this.resolveColumnReference(field));
137
142
  this.groupByColumns.push(...refs);
138
143
  return this;
139
144
  }
145
+ having(condition) {
146
+ this.havingCondition = typeof condition === 'function'
147
+ ? condition(this.createSelectedFieldAliases())
148
+ : condition;
149
+ return this;
150
+ }
151
+ createSelectedFieldAliases() {
152
+ const aliases = {};
153
+ for (const alias of Object.keys(this.selectedFields ?? {})) {
154
+ aliases[alias] = new expressions_1.SelectionAliasExpression(alias);
155
+ }
156
+ return aliases;
157
+ }
140
158
  addJoin(type, table, condition) {
141
159
  if (type === 'rightJoin' || type === 'fullJoin') {
142
160
  throw new Error(`${type} is not yet supported in Solid dialect`);
143
161
  }
144
162
  const alias = this.ensureAliasForTable(table);
145
- const resolvedConditions = this.resolveJoinConditions(condition, alias);
163
+ const resolvedConditions = type === 'crossJoin'
164
+ ? undefined
165
+ : this.resolveJoinConditions(condition, alias);
146
166
  this.joins.push({
147
167
  type,
148
168
  table,
@@ -167,8 +187,11 @@ class SelectQueryBuilder {
167
187
  return alias;
168
188
  }
169
189
  resolveJoinConditions(condition, joinAlias) {
190
+ if (this.isQueryCondition(condition)) {
191
+ return this.resolveJoinConditionExpression(condition, joinAlias);
192
+ }
170
193
  if (!condition || typeof condition !== 'object') {
171
- throw new Error('JOIN condition must be an object mapping columns');
194
+ throw new Error('JOIN condition must be an equality expression or column mapping');
172
195
  }
173
196
  const entries = Object.entries(condition);
174
197
  if (entries.length === 0) {
@@ -183,6 +206,44 @@ class SelectQueryBuilder {
183
206
  return { left: leftRef, right: rightRef };
184
207
  });
185
208
  }
209
+ resolveJoinConditionExpression(condition, joinAlias) {
210
+ if (condition.type === 'logical_expr') {
211
+ const operator = (condition.operator ?? '').toUpperCase();
212
+ const expressions = (condition.expressions ?? []);
213
+ if (operator !== 'AND' || expressions.length === 0) {
214
+ throw new Error('JOIN condition only supports equality expressions combined with AND');
215
+ }
216
+ return expressions.flatMap((expression) => this.resolveJoinConditionExpression(expression, joinAlias));
217
+ }
218
+ if (condition.type !== 'binary_expr' || condition.operator !== '=') {
219
+ throw new Error('JOIN condition only supports equality expressions');
220
+ }
221
+ const leftRef = this.getConditionColumnReference(condition.left);
222
+ const rightRef = this.getConditionColumnReference(condition.right);
223
+ if (!leftRef || !rightRef) {
224
+ throw new Error('JOIN equality must compare two table columns');
225
+ }
226
+ if (leftRef.alias !== joinAlias && rightRef.alias !== joinAlias) {
227
+ throw new Error('JOIN condition must reference the joined table in at least one side');
228
+ }
229
+ return [{ left: leftRef, right: rightRef }];
230
+ }
231
+ getConditionColumnReference(field, fallbackAlias) {
232
+ if (field instanceof schema_1.PodColumnBase) {
233
+ return this.resolveColumnReference(field, fallbackAlias);
234
+ }
235
+ if (typeof field === 'string') {
236
+ if (field.includes('.')) {
237
+ return this.resolveColumnReference(field, fallbackAlias);
238
+ }
239
+ const alias = fallbackAlias ?? this.primaryAlias;
240
+ const table = alias ? this.aliasToTable.get(alias) : undefined;
241
+ if (table && field in (table.columns ?? {})) {
242
+ return this.resolveColumnReference(field, fallbackAlias);
243
+ }
244
+ }
245
+ return undefined;
246
+ }
186
247
  resolveColumnReference(field, fallbackAlias) {
187
248
  if (field instanceof schema_1.PodColumnBase) {
188
249
  const table = field.table;
@@ -277,6 +338,7 @@ class SelectQueryBuilder {
277
338
  joins: planJoins.length > 0 ? planJoins : undefined,
278
339
  joinFilters: this.joinFilters.length > 0 ? [...this.joinFilters] : undefined,
279
340
  groupBy: this.groupByColumns.length > 0 ? [...this.groupByColumns] : undefined,
341
+ having: this.havingCondition,
280
342
  orderBy: this.orderByClauses.length > 0
281
343
  ? this.orderByClauses.map((clause) => ({
282
344
  rawColumn: clause.column,
@@ -304,49 +366,115 @@ class SelectQueryBuilder {
304
366
  this.offsetCount = count;
305
367
  return this;
306
368
  }
307
- orderBy(column, direction = 'asc') {
369
+ addOrderByClause(column, direction = 'asc') {
308
370
  const columnName = typeof column === 'string' ? column : column.name;
309
371
  if (!columnName) {
310
372
  throw new Error('ORDER BY requires a valid column name');
311
373
  }
312
374
  this.orderByClauses.push({ column: columnName, direction });
375
+ }
376
+ orderBy(...args) {
377
+ if (args.length === 0) {
378
+ throw new Error('ORDER BY requires at least one column or expression');
379
+ }
380
+ if (args.length === 2
381
+ && (args[0] instanceof schema_1.PodColumnBase || typeof args[0] === 'string')
382
+ && (args[1] === 'asc' || args[1] === 'desc')) {
383
+ this.addOrderByClause(args[0], args[1]);
384
+ return this;
385
+ }
386
+ for (const arg of args) {
387
+ if ((0, order_by_1.isOrderByExpression)(arg)) {
388
+ this.addOrderByClause(arg.column, arg.direction);
389
+ continue;
390
+ }
391
+ if (arg instanceof schema_1.PodColumnBase || typeof arg === 'string') {
392
+ this.addOrderByClause(arg, 'asc');
393
+ continue;
394
+ }
395
+ throw new Error('ORDER BY received an unsupported argument');
396
+ }
313
397
  return this;
314
398
  }
315
399
  distinct(enable = true) {
316
400
  this.isDistinct = enable;
317
401
  return this;
318
402
  }
403
+ buildSPARQLQuery(methodName = 'toSPARQL()') {
404
+ if (this.sql) {
405
+ const query = this.sql.queryChunks.join('');
406
+ const type = (0, helpers_1.inferSPARQLQueryType)(query);
407
+ if (!type) {
408
+ throw new Error(`${methodName} could not infer SPARQL query type from raw AST input`);
409
+ }
410
+ return { type, query, prefixes: {} };
411
+ }
412
+ if (!this.selectedTable) {
413
+ throw new Error('No table specified for SELECT query');
414
+ }
415
+ if (this.joins.length > 0) {
416
+ throw new Error(`${methodName} is not yet supported for JOIN queries in Solid dialect`);
417
+ }
418
+ if (this.shouldUseProjectionFallback()) {
419
+ throw new Error(`${methodName} does not support structured selections in Solid dialect`);
420
+ }
421
+ const converter = this.session.getDialect().getSPARQLConverter?.();
422
+ if (!converter) {
423
+ throw new Error(`${methodName} requires dialect SPARQL converter support`);
424
+ }
425
+ return converter.convertSelectPlan(this.toIR());
426
+ }
427
+ toSPARQL() {
428
+ return this.buildSPARQLQuery('toSPARQL()');
429
+ }
430
+ toSparql() {
431
+ return this.toSPARQL();
432
+ }
319
433
  async execute() {
320
434
  if (!this.selectedTable) {
321
435
  throw new Error('No table specified for SELECT query');
322
436
  }
437
+ if (this.limitCount === 0) {
438
+ return [];
439
+ }
323
440
  if (this.sql) {
324
441
  return await this.session.executeSql(this.sql, this.selectedTable);
325
442
  }
326
443
  else {
327
444
  const plan = this.toIR();
328
445
  const wherePayload = plan.conditionTree;
446
+ const hasJoins = this.joins.length > 0;
447
+ const useAggregateFallback = this.shouldUseAggregateFallback();
448
+ const useProjectionFallback = this.shouldUseProjectionFallback();
449
+ const shouldDeferQueryModifiers = hasJoins || useAggregateFallback;
450
+ if (hasJoins || useAggregateFallback || useProjectionFallback) {
451
+ plan.select = undefined;
452
+ plan.selectAll = true;
453
+ }
454
+ const executionPlan = shouldDeferQueryModifiers
455
+ ? {
456
+ ...plan,
457
+ limit: undefined,
458
+ offset: undefined,
459
+ orderBy: undefined,
460
+ ...(useAggregateFallback ? { groupBy: undefined, having: undefined } : {}),
461
+ }
462
+ : plan;
329
463
  const operation = {
330
464
  type: 'select',
331
465
  table: this.selectedTable,
332
466
  where: wherePayload,
333
- limit: this.limitCount,
334
- offset: this.offsetCount,
335
- orderBy: this.orderByClauses.length > 0 ? this.orderByClauses : undefined,
467
+ limit: shouldDeferQueryModifiers ? undefined : this.limitCount,
468
+ offset: shouldDeferQueryModifiers ? undefined : this.offsetCount,
469
+ orderBy: shouldDeferQueryModifiers || this.orderByClauses.length === 0 ? undefined : this.orderByClauses,
336
470
  distinct: this.isDistinct || undefined
337
471
  };
338
- const hasJoins = this.joins.length > 0;
339
- const useAggregateFallback = this.shouldUseAggregateFallback();
340
- if (hasJoins || useAggregateFallback) {
341
- plan.select = undefined;
342
- plan.selectAll = true;
343
- }
344
- operation.plan = plan;
472
+ operation.plan = executionPlan;
345
473
  if (this.groupByColumns.length === 0 && this.hasMixedAggregateSelection()) {
346
474
  throw new Error('Mixed aggregate and non-aggregate selections require groupBy columns');
347
475
  }
348
476
  let intermediateRows;
349
- if (!hasJoins && !useAggregateFallback) {
477
+ if (!hasJoins && !useAggregateFallback && !useProjectionFallback) {
350
478
  if (this.selectedFields) {
351
479
  operation.select = this.selectedFields;
352
480
  }
@@ -357,7 +485,7 @@ class SelectQueryBuilder {
357
485
  if (hasJoins) {
358
486
  operation.select = undefined;
359
487
  }
360
- else if (!useAggregateFallback) {
488
+ else if (!useAggregateFallback && !useProjectionFallback) {
361
489
  operation.select = this.selectedFields;
362
490
  }
363
491
  else {
@@ -374,51 +502,141 @@ class SelectQueryBuilder {
374
502
  }
375
503
  }
376
504
  if (useAggregateFallback) {
377
- return this.handleAggregateFallback(intermediateRows);
505
+ let aggregateRows = this.handleAggregateFallback(intermediateRows);
506
+ aggregateRows = this.applyHavingFilter(aggregateRows);
507
+ if (shouldDeferQueryModifiers) {
508
+ aggregateRows = this.applyDeferredOrderBy(aggregateRows);
509
+ aggregateRows = this.applyDeferredOffsetAndLimit(aggregateRows);
510
+ }
511
+ return aggregateRows;
378
512
  }
379
513
  let finalRows = intermediateRows;
380
514
  if (!hasJoins) {
381
515
  finalRows = this.mergeRowsBySubject(finalRows);
382
516
  }
383
517
  if (this.selectedTable) {
384
- finalRows = await this.hydrateInlineColumns(finalRows, this.selectedTable);
518
+ finalRows = await this.hydrateInlineColumns(finalRows, this.selectedTable, !hasJoins);
385
519
  // 处理引用字段:将 URI 转换回 ID
386
520
  finalRows = this.resolveReferenceIds(finalRows, this.selectedTable);
387
521
  }
522
+ if (hasJoins) {
523
+ finalRows = this.applyDeferredOrderBy(finalRows);
524
+ }
388
525
  if (this.selectedFields) {
389
526
  finalRows = finalRows.map((row) => this.projectSelectedRow(row));
390
527
  }
391
- finalRows = this.mergeRowsBySubject(finalRows);
528
+ finalRows = this.applyDistinctRows(finalRows);
529
+ if (!hasJoins) {
530
+ finalRows = this.mergeRowsBySubject(finalRows);
531
+ }
532
+ if (hasJoins) {
533
+ finalRows = this.applyDeferredOffsetAndLimit(finalRows);
534
+ }
392
535
  return finalRows;
393
536
  }
394
537
  }
395
538
  projectSelectedRow(row) {
396
- const projected = {};
397
539
  if (!this.selectedFields) {
398
540
  return row;
399
541
  }
400
- for (const key of Object.keys(this.selectedFields)) {
401
- const field = this.selectedFields[key];
402
- if ((0, aggregates_1.isAggregateExpression)(field)) {
403
- projected[key] = row[key];
404
- continue;
405
- }
406
- let assigned = false;
407
- for (const candidate of this.resolveFieldBindingCandidates(key, field)) {
408
- if (row[candidate] !== undefined) {
409
- projected[key] = row[candidate];
410
- assigned = true;
411
- break;
542
+ return this.projectFieldMap(row, this.selectedFields);
543
+ }
544
+ projectFieldMap(row, fields, allowAliasFallback = true) {
545
+ const projected = {};
546
+ for (const [key, field] of Object.entries(fields)) {
547
+ projected[key] = this.projectFieldValue(row, key, field, allowAliasFallback);
548
+ }
549
+ return projected;
550
+ }
551
+ projectFieldValue(row, alias, field, allowAliasFallback = true) {
552
+ if ((0, aggregates_1.isAggregateExpression)(field)) {
553
+ return allowAliasFallback ? row[alias] : undefined;
554
+ }
555
+ if (field instanceof schema_1.PodTable) {
556
+ return this.projectTableValue(row, field);
557
+ }
558
+ if (this.isSelectFieldMap(field)) {
559
+ const aliases = this.collectSelectionAliases(field);
560
+ const isJoinedOnlySelection = aliases.size === 1 && !aliases.has(this.primaryAlias ?? '');
561
+ const projected = this.projectFieldMap(row, field, !isJoinedOnlySelection);
562
+ if (aliases.size === 1) {
563
+ const [targetAlias] = Array.from(aliases);
564
+ if (targetAlias && targetAlias !== this.primaryAlias && this.isProjectedValueEmpty(projected)) {
565
+ return null;
412
566
  }
413
567
  }
414
- if (!assigned) {
415
- projected[key] = row[key];
568
+ return projected;
569
+ }
570
+ for (const candidate of this.resolveFieldBindingCandidates(alias, field)) {
571
+ if (row[candidate] !== undefined) {
572
+ return row[candidate];
416
573
  }
417
574
  }
575
+ return allowAliasFallback ? row[alias] : undefined;
576
+ }
577
+ projectTableValue(row, table) {
578
+ const alias = this.tableAliases.get(table) ?? table.config.name;
579
+ const projected = {};
580
+ let hasValue = false;
581
+ for (const columnName of Object.keys(table.columns)) {
582
+ const candidates = alias === this.primaryAlias
583
+ ? [columnName, `${alias}.${columnName}`]
584
+ : [`${alias}.${columnName}`];
585
+ const match = candidates.find((candidate) => row[candidate] !== undefined);
586
+ projected[columnName] = match ? row[match] : undefined;
587
+ if (projected[columnName] !== undefined) {
588
+ hasValue = true;
589
+ }
590
+ }
591
+ if (alias !== this.primaryAlias && !hasValue) {
592
+ return null;
593
+ }
418
594
  return projected;
419
595
  }
596
+ isSelectFieldMap(field) {
597
+ return !!field
598
+ && typeof field === 'object'
599
+ && !(field instanceof schema_1.PodColumnBase)
600
+ && !(field instanceof schema_1.PodTable)
601
+ && !(0, aggregates_1.isAggregateExpression)(field);
602
+ }
603
+ collectSelectionAliases(field) {
604
+ if (typeof field === 'string') {
605
+ const { alias } = this.parseColumnReferenceString(field);
606
+ return new Set(alias ? [alias] : this.primaryAlias ? [this.primaryAlias] : []);
607
+ }
608
+ if (field instanceof schema_1.PodColumnBase) {
609
+ return new Set([this.resolveColumnReference(field).alias]);
610
+ }
611
+ if (field instanceof schema_1.PodTable) {
612
+ return new Set([this.tableAliases.get(field) ?? field.config.name]);
613
+ }
614
+ if (!this.isSelectFieldMap(field)) {
615
+ return new Set();
616
+ }
617
+ const aliases = new Set();
618
+ for (const child of Object.values(field)) {
619
+ for (const alias of this.collectSelectionAliases(child)) {
620
+ aliases.add(alias);
621
+ }
622
+ }
623
+ return aliases;
624
+ }
625
+ isProjectedValueEmpty(value) {
626
+ if (value === undefined || value === null) {
627
+ return true;
628
+ }
629
+ if (Array.isArray(value)) {
630
+ return value.length === 0;
631
+ }
632
+ if (typeof value === 'object') {
633
+ const entries = Object.values(value);
634
+ return entries.length > 0 && entries.every((entry) => this.isProjectedValueEmpty(entry));
635
+ }
636
+ return false;
637
+ }
420
638
  resolveFieldBindingCandidates(alias, field) {
421
- const candidates = new Set([alias]);
639
+ const candidates = new Set();
422
640
  if (typeof field === 'string') {
423
641
  const { alias: refAlias, column } = this.parseColumnReferenceString(field);
424
642
  candidates.add(field);
@@ -429,8 +647,10 @@ class SelectQueryBuilder {
429
647
  }
430
648
  else if (field instanceof schema_1.PodColumnBase) {
431
649
  const columnRef = this.resolveColumnReference(field);
432
- candidates.add(field.name);
433
650
  candidates.add(`${columnRef.alias}.${columnRef.column}`);
651
+ if (!columnRef.alias || columnRef.alias === this.primaryAlias) {
652
+ candidates.add(field.name);
653
+ }
434
654
  }
435
655
  else if (field && typeof field === 'object') {
436
656
  const candidateName = field.name;
@@ -440,13 +660,31 @@ class SelectQueryBuilder {
440
660
  }
441
661
  return Array.from(candidates);
442
662
  }
663
+ shouldUseProjectionFallback() {
664
+ if (!this.selectedFields) {
665
+ return false;
666
+ }
667
+ const containsStructuredField = (field) => {
668
+ if (field instanceof schema_1.PodTable) {
669
+ return true;
670
+ }
671
+ if (this.isSelectFieldMap(field)) {
672
+ return true;
673
+ }
674
+ return false;
675
+ };
676
+ return Object.values(this.selectedFields).some((field) => containsStructuredField(field));
677
+ }
443
678
  shouldUseAggregateFallback() {
444
- // GROUP BY is handled at SPARQL level (both LDP/Comunica and SPARQL endpoint support it)
679
+ if (this.havingCondition) {
680
+ return true;
681
+ }
682
+ if (this.joins.length > 0) {
683
+ return this.groupByColumns.length > 0 || this.hasAggregateSelection();
684
+ }
445
685
  if (this.groupByColumns.length > 0) {
446
686
  return false;
447
687
  }
448
- // For pure aggregate queries (no GROUP BY), Comunica seems to have issues with multiple aggregates
449
- // So we use JS fallback for these cases
450
688
  if (!this.selectedFields) {
451
689
  return false;
452
690
  }
@@ -657,6 +895,12 @@ class SelectQueryBuilder {
657
895
  }
658
896
  return undefined;
659
897
  }
898
+ hasAggregateSelection() {
899
+ if (!this.selectedFields) {
900
+ return false;
901
+ }
902
+ return Object.values(this.selectedFields).some((field) => (0, aggregates_1.isAggregateExpression)(field));
903
+ }
660
904
  hasMixedAggregateSelection() {
661
905
  if (!this.selectedFields) {
662
906
  return false;
@@ -721,15 +965,17 @@ class SelectQueryBuilder {
721
965
  return result;
722
966
  });
723
967
  }
724
- async hydrateInlineColumns(rows, table) {
968
+ async hydrateInlineColumns(rows, table, mergeRows = true) {
725
969
  if (!rows.length) {
726
970
  return rows;
727
971
  }
728
- rows = this.mergeRowsBySubject(rows);
729
972
  const inlineColumns = Object.values(table.columns ?? {}).filter((col) => this.isInlineObjectColumn(col));
730
973
  if (inlineColumns.length === 0) {
731
974
  return rows;
732
975
  }
976
+ if (mergeRows) {
977
+ rows = this.mergeRowsBySubject(rows);
978
+ }
733
979
  const predicateToColumn = new Map();
734
980
  inlineColumns.forEach((col) => {
735
981
  const predicate = col.getPredicate(table.config.namespace);
@@ -782,7 +1028,7 @@ class SelectQueryBuilder {
782
1028
  if (!childIri || !pred)
783
1029
  return;
784
1030
  const child = childMap.get(childIri) ?? { '@id': childIri, id: this.extractIdFromSubject(childIri, table) };
785
- const key = inlineNamespace && pred.startsWith(inlineNamespace) ? pred.slice(inlineNamespace.length) : pred;
1031
+ const key = this.normalizeInlinePredicateKey(pred, inlineNamespace);
786
1032
  if (obj === undefined) {
787
1033
  childMap.set(childIri, child);
788
1034
  return;
@@ -868,6 +1114,20 @@ class SelectQueryBuilder {
868
1114
  return result;
869
1115
  });
870
1116
  }
1117
+ normalizeInlinePredicateKey(predicate, inlineNamespace) {
1118
+ if (inlineNamespace && predicate.startsWith(inlineNamespace)) {
1119
+ return predicate.slice(inlineNamespace.length);
1120
+ }
1121
+ const hashIndex = predicate.lastIndexOf('#');
1122
+ if (hashIndex !== -1 && hashIndex < predicate.length - 1) {
1123
+ return predicate.slice(hashIndex + 1);
1124
+ }
1125
+ const slashIndex = predicate.lastIndexOf('/');
1126
+ if (slashIndex !== -1 && slashIndex < predicate.length - 1) {
1127
+ return predicate.slice(slashIndex + 1);
1128
+ }
1129
+ return predicate;
1130
+ }
871
1131
  normalizeInlineObjectValue(value) {
872
1132
  if (value === null || value === undefined)
873
1133
  return undefined;
@@ -890,6 +1150,73 @@ class SelectQueryBuilder {
890
1150
  return value.value;
891
1151
  return undefined;
892
1152
  }
1153
+ applyDeferredOrderBy(rows) {
1154
+ if (this.orderByClauses.length === 0 || rows.length < 2) {
1155
+ return rows;
1156
+ }
1157
+ const sorted = [...rows];
1158
+ sorted.sort((leftRow, rightRow) => {
1159
+ for (const clause of this.orderByClauses) {
1160
+ const leftValue = this.getOrderByValue(leftRow, clause.column);
1161
+ const rightValue = this.getOrderByValue(rightRow, clause.column);
1162
+ const comparison = this.compareOrderByValues(leftValue, rightValue);
1163
+ if (comparison !== 0) {
1164
+ return clause.direction === 'desc' ? -comparison : comparison;
1165
+ }
1166
+ }
1167
+ return 0;
1168
+ });
1169
+ return sorted;
1170
+ }
1171
+ applyDistinctRows(rows) {
1172
+ if (!this.isDistinct || rows.length < 2) {
1173
+ return rows;
1174
+ }
1175
+ const seen = new Set();
1176
+ const deduped = [];
1177
+ for (const row of rows) {
1178
+ const key = this.serializeValueForKey(row);
1179
+ if (seen.has(key)) {
1180
+ continue;
1181
+ }
1182
+ seen.add(key);
1183
+ deduped.push(row);
1184
+ }
1185
+ return deduped;
1186
+ }
1187
+ applyDeferredOffsetAndLimit(rows) {
1188
+ const offset = this.offsetCount ?? 0;
1189
+ const offsetRows = offset > 0 ? rows.slice(offset) : rows;
1190
+ if (this.limitCount === undefined) {
1191
+ return offsetRows;
1192
+ }
1193
+ return offsetRows.slice(0, this.limitCount);
1194
+ }
1195
+ getOrderByValue(row, columnName) {
1196
+ if (columnName in row) {
1197
+ return row[columnName];
1198
+ }
1199
+ const aliasKey = Object.keys(row).find((key) => key.endsWith(`.${columnName}`));
1200
+ if (aliasKey) {
1201
+ return row[aliasKey];
1202
+ }
1203
+ return undefined;
1204
+ }
1205
+ compareOrderByValues(left, right) {
1206
+ if (left === right) {
1207
+ return 0;
1208
+ }
1209
+ if (left === undefined || left === null) {
1210
+ return 1;
1211
+ }
1212
+ if (right === undefined || right === null) {
1213
+ return -1;
1214
+ }
1215
+ if (typeof left === 'number' && typeof right === 'number') {
1216
+ return left - right;
1217
+ }
1218
+ return String(left).localeCompare(String(right));
1219
+ }
893
1220
  mergeRowsBySubject(rows) {
894
1221
  const merged = new Map();
895
1222
  const order = [];
@@ -987,11 +1314,11 @@ class SelectQueryBuilder {
987
1314
  isInlineObjectColumn(column) {
988
1315
  if (!column)
989
1316
  return false;
990
- if (column.dataType === 'object')
1317
+ if (column.dataType === 'object' || column.dataType === 'json')
991
1318
  return true;
992
1319
  if (column.dataType === 'array') {
993
1320
  const elementType = column.elementType ?? column.options?.baseType;
994
- return elementType === 'object';
1321
+ return elementType === 'object' || elementType === 'json';
995
1322
  }
996
1323
  return false;
997
1324
  }
@@ -1004,6 +1331,10 @@ class SelectQueryBuilder {
1004
1331
  return combinedRows;
1005
1332
  }
1006
1333
  async fetchJoinRows(join, baseRows) {
1334
+ if (join.type === 'crossJoin') {
1335
+ const joinRows = await this.session.select().from(join.table);
1336
+ return this.normalizeJoinRows(join, joinRows);
1337
+ }
1007
1338
  const conditions = join.resolvedConditions ?? [];
1008
1339
  if (conditions.length === 0) {
1009
1340
  return [];
@@ -1057,6 +1388,18 @@ class SelectQueryBuilder {
1057
1388
  });
1058
1389
  }
1059
1390
  mergeRowsWithJoin(baseRows, join, joinRows) {
1391
+ if (join.type === 'crossJoin') {
1392
+ if (baseRows.length === 0 || joinRows.length === 0) {
1393
+ return [];
1394
+ }
1395
+ const merged = [];
1396
+ for (const baseRow of baseRows) {
1397
+ for (const joinRow of joinRows) {
1398
+ merged.push({ ...baseRow, ...joinRow });
1399
+ }
1400
+ }
1401
+ return merged;
1402
+ }
1060
1403
  const conditions = join.resolvedConditions ?? [];
1061
1404
  if (conditions.length === 0) {
1062
1405
  return baseRows;
@@ -1109,6 +1452,12 @@ class SelectQueryBuilder {
1109
1452
  }
1110
1453
  return rows.filter((row) => this.joinFilters.every((condition) => this.evaluateCondition(row, condition)));
1111
1454
  }
1455
+ applyHavingFilter(rows) {
1456
+ if (!this.havingCondition) {
1457
+ return rows;
1458
+ }
1459
+ return rows.filter((row) => this.evaluateCondition(row, this.havingCondition));
1460
+ }
1112
1461
  evaluateCondition(row, condition) {
1113
1462
  switch (condition.type) {
1114
1463
  case 'binary_expr':
@@ -1128,27 +1477,8 @@ class SelectQueryBuilder {
1128
1477
  if (!left) {
1129
1478
  return true;
1130
1479
  }
1131
- // Extract column name and table alias from left
1132
- let colName;
1133
- let tableAlias;
1134
- if (typeof left === 'string') {
1135
- if (left.includes('.')) {
1136
- [tableAlias, colName] = left.split('.');
1137
- }
1138
- else {
1139
- colName = left;
1140
- }
1141
- }
1142
- else if (left && typeof left === 'object') {
1143
- colName = left.name;
1144
- tableAlias = left.table;
1145
- }
1146
- else {
1147
- return true;
1148
- }
1149
- const columnRef = this.resolveColumnReference(`${tableAlias ?? this.primaryAlias}.${colName}`);
1150
- const value = this.getRowValueForColumn(row, columnRef);
1151
- const target = right;
1480
+ const value = this.resolveConditionOperandValue(row, left, this.primaryAlias);
1481
+ const target = this.resolveConditionOperandValue(row, right);
1152
1482
  switch (op.toUpperCase()) {
1153
1483
  case '=':
1154
1484
  return value === target;
@@ -1176,33 +1506,13 @@ class SelectQueryBuilder {
1176
1506
  evaluateUnaryCondition(row, condition) {
1177
1507
  const op = condition.operator;
1178
1508
  const val = condition.value;
1179
- // For NOT operator, value is another condition
1180
1509
  if (op.toUpperCase() === 'NOT') {
1181
1510
  return !this.evaluateCondition(row, val);
1182
1511
  }
1183
- // For IS NULL / IS NOT NULL, value is the column reference
1184
1512
  if (!val) {
1185
1513
  return true;
1186
1514
  }
1187
- let colName;
1188
- let tableAlias;
1189
- if (typeof val === 'string') {
1190
- if (val.includes('.')) {
1191
- [tableAlias, colName] = val.split('.');
1192
- }
1193
- else {
1194
- colName = val;
1195
- }
1196
- }
1197
- else if (val && typeof val === 'object' && 'name' in val) {
1198
- colName = val.name;
1199
- tableAlias = val.table;
1200
- }
1201
- else {
1202
- return true;
1203
- }
1204
- const columnRef = this.resolveColumnReference(`${tableAlias ?? this.primaryAlias}.${colName}`);
1205
- const rowValue = this.getRowValueForColumn(row, columnRef);
1515
+ const rowValue = this.resolveConditionOperandValue(row, val, this.primaryAlias);
1206
1516
  switch (op.toUpperCase()) {
1207
1517
  case 'IS NULL':
1208
1518
  return rowValue === null || rowValue === undefined;
@@ -1223,6 +1533,16 @@ class SelectQueryBuilder {
1223
1533
  }
1224
1534
  return true;
1225
1535
  }
1536
+ resolveConditionOperandValue(row, operand, fallbackAlias) {
1537
+ if (operand instanceof expressions_1.SelectionAliasExpression) {
1538
+ return row[operand.alias];
1539
+ }
1540
+ const columnRef = this.getConditionColumnReference(operand, fallbackAlias);
1541
+ if (columnRef) {
1542
+ return this.getRowValueForColumn(row, columnRef);
1543
+ }
1544
+ return operand;
1545
+ }
1226
1546
  getColumnKeyCandidates(column) {
1227
1547
  const candidates = [];
1228
1548
  const baseAlias = this.primaryAlias;