@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
@@ -1,10 +1,12 @@
1
1
  var _a;
2
2
  import { entityKind, SQL } from 'drizzle-orm';
3
- import { PodColumnBase } from '../schema';
3
+ import { PodTable, PodColumnBase } from '../schema';
4
4
  import { inArray } from '../query-conditions';
5
5
  import { isAggregateExpression } from '../aggregates';
6
- import { createLiteralCondition, buildConditionTreeFromObject } from './helpers';
6
+ import { createLiteralCondition, buildConditionTreeFromObject, inferSPARQLQueryType } from './helpers';
7
7
  import { UriResolverImpl } from '../uri';
8
+ import { isOrderByExpression } from '../order-by';
9
+ import { SelectionAliasExpression } from '../expressions';
8
10
  export class SelectQueryBuilder {
9
11
  constructor(session, fields) {
10
12
  this.session = session;
@@ -129,17 +131,35 @@ export class SelectQueryBuilder {
129
131
  fullJoin(table, condition) {
130
132
  return this.addJoin('fullJoin', table, condition);
131
133
  }
134
+ crossJoin(table) {
135
+ return this.addJoin('crossJoin', table);
136
+ }
132
137
  groupBy(...fields) {
133
138
  const refs = fields.map((field) => this.resolveColumnReference(field));
134
139
  this.groupByColumns.push(...refs);
135
140
  return this;
136
141
  }
142
+ having(condition) {
143
+ this.havingCondition = typeof condition === 'function'
144
+ ? condition(this.createSelectedFieldAliases())
145
+ : condition;
146
+ return this;
147
+ }
148
+ createSelectedFieldAliases() {
149
+ const aliases = {};
150
+ for (const alias of Object.keys(this.selectedFields ?? {})) {
151
+ aliases[alias] = new SelectionAliasExpression(alias);
152
+ }
153
+ return aliases;
154
+ }
137
155
  addJoin(type, table, condition) {
138
156
  if (type === 'rightJoin' || type === 'fullJoin') {
139
157
  throw new Error(`${type} is not yet supported in Solid dialect`);
140
158
  }
141
159
  const alias = this.ensureAliasForTable(table);
142
- const resolvedConditions = this.resolveJoinConditions(condition, alias);
160
+ const resolvedConditions = type === 'crossJoin'
161
+ ? undefined
162
+ : this.resolveJoinConditions(condition, alias);
143
163
  this.joins.push({
144
164
  type,
145
165
  table,
@@ -164,8 +184,11 @@ export class SelectQueryBuilder {
164
184
  return alias;
165
185
  }
166
186
  resolveJoinConditions(condition, joinAlias) {
187
+ if (this.isQueryCondition(condition)) {
188
+ return this.resolveJoinConditionExpression(condition, joinAlias);
189
+ }
167
190
  if (!condition || typeof condition !== 'object') {
168
- throw new Error('JOIN condition must be an object mapping columns');
191
+ throw new Error('JOIN condition must be an equality expression or column mapping');
169
192
  }
170
193
  const entries = Object.entries(condition);
171
194
  if (entries.length === 0) {
@@ -180,6 +203,44 @@ export class SelectQueryBuilder {
180
203
  return { left: leftRef, right: rightRef };
181
204
  });
182
205
  }
206
+ resolveJoinConditionExpression(condition, joinAlias) {
207
+ if (condition.type === 'logical_expr') {
208
+ const operator = (condition.operator ?? '').toUpperCase();
209
+ const expressions = (condition.expressions ?? []);
210
+ if (operator !== 'AND' || expressions.length === 0) {
211
+ throw new Error('JOIN condition only supports equality expressions combined with AND');
212
+ }
213
+ return expressions.flatMap((expression) => this.resolveJoinConditionExpression(expression, joinAlias));
214
+ }
215
+ if (condition.type !== 'binary_expr' || condition.operator !== '=') {
216
+ throw new Error('JOIN condition only supports equality expressions');
217
+ }
218
+ const leftRef = this.getConditionColumnReference(condition.left);
219
+ const rightRef = this.getConditionColumnReference(condition.right);
220
+ if (!leftRef || !rightRef) {
221
+ throw new Error('JOIN equality must compare two table columns');
222
+ }
223
+ if (leftRef.alias !== joinAlias && rightRef.alias !== joinAlias) {
224
+ throw new Error('JOIN condition must reference the joined table in at least one side');
225
+ }
226
+ return [{ left: leftRef, right: rightRef }];
227
+ }
228
+ getConditionColumnReference(field, fallbackAlias) {
229
+ if (field instanceof PodColumnBase) {
230
+ return this.resolveColumnReference(field, fallbackAlias);
231
+ }
232
+ if (typeof field === 'string') {
233
+ if (field.includes('.')) {
234
+ return this.resolveColumnReference(field, fallbackAlias);
235
+ }
236
+ const alias = fallbackAlias ?? this.primaryAlias;
237
+ const table = alias ? this.aliasToTable.get(alias) : undefined;
238
+ if (table && field in (table.columns ?? {})) {
239
+ return this.resolveColumnReference(field, fallbackAlias);
240
+ }
241
+ }
242
+ return undefined;
243
+ }
183
244
  resolveColumnReference(field, fallbackAlias) {
184
245
  if (field instanceof PodColumnBase) {
185
246
  const table = field.table;
@@ -274,6 +335,7 @@ export class SelectQueryBuilder {
274
335
  joins: planJoins.length > 0 ? planJoins : undefined,
275
336
  joinFilters: this.joinFilters.length > 0 ? [...this.joinFilters] : undefined,
276
337
  groupBy: this.groupByColumns.length > 0 ? [...this.groupByColumns] : undefined,
338
+ having: this.havingCondition,
277
339
  orderBy: this.orderByClauses.length > 0
278
340
  ? this.orderByClauses.map((clause) => ({
279
341
  rawColumn: clause.column,
@@ -301,49 +363,115 @@ export class SelectQueryBuilder {
301
363
  this.offsetCount = count;
302
364
  return this;
303
365
  }
304
- orderBy(column, direction = 'asc') {
366
+ addOrderByClause(column, direction = 'asc') {
305
367
  const columnName = typeof column === 'string' ? column : column.name;
306
368
  if (!columnName) {
307
369
  throw new Error('ORDER BY requires a valid column name');
308
370
  }
309
371
  this.orderByClauses.push({ column: columnName, direction });
372
+ }
373
+ orderBy(...args) {
374
+ if (args.length === 0) {
375
+ throw new Error('ORDER BY requires at least one column or expression');
376
+ }
377
+ if (args.length === 2
378
+ && (args[0] instanceof PodColumnBase || typeof args[0] === 'string')
379
+ && (args[1] === 'asc' || args[1] === 'desc')) {
380
+ this.addOrderByClause(args[0], args[1]);
381
+ return this;
382
+ }
383
+ for (const arg of args) {
384
+ if (isOrderByExpression(arg)) {
385
+ this.addOrderByClause(arg.column, arg.direction);
386
+ continue;
387
+ }
388
+ if (arg instanceof PodColumnBase || typeof arg === 'string') {
389
+ this.addOrderByClause(arg, 'asc');
390
+ continue;
391
+ }
392
+ throw new Error('ORDER BY received an unsupported argument');
393
+ }
310
394
  return this;
311
395
  }
312
396
  distinct(enable = true) {
313
397
  this.isDistinct = enable;
314
398
  return this;
315
399
  }
400
+ buildSPARQLQuery(methodName = 'toSPARQL()') {
401
+ if (this.sql) {
402
+ const query = this.sql.queryChunks.join('');
403
+ const type = inferSPARQLQueryType(query);
404
+ if (!type) {
405
+ throw new Error(`${methodName} could not infer SPARQL query type from raw AST input`);
406
+ }
407
+ return { type, query, prefixes: {} };
408
+ }
409
+ if (!this.selectedTable) {
410
+ throw new Error('No table specified for SELECT query');
411
+ }
412
+ if (this.joins.length > 0) {
413
+ throw new Error(`${methodName} is not yet supported for JOIN queries in Solid dialect`);
414
+ }
415
+ if (this.shouldUseProjectionFallback()) {
416
+ throw new Error(`${methodName} does not support structured selections in Solid dialect`);
417
+ }
418
+ const converter = this.session.getDialect().getSPARQLConverter?.();
419
+ if (!converter) {
420
+ throw new Error(`${methodName} requires dialect SPARQL converter support`);
421
+ }
422
+ return converter.convertSelectPlan(this.toIR());
423
+ }
424
+ toSPARQL() {
425
+ return this.buildSPARQLQuery('toSPARQL()');
426
+ }
427
+ toSparql() {
428
+ return this.toSPARQL();
429
+ }
316
430
  async execute() {
317
431
  if (!this.selectedTable) {
318
432
  throw new Error('No table specified for SELECT query');
319
433
  }
434
+ if (this.limitCount === 0) {
435
+ return [];
436
+ }
320
437
  if (this.sql) {
321
438
  return await this.session.executeSql(this.sql, this.selectedTable);
322
439
  }
323
440
  else {
324
441
  const plan = this.toIR();
325
442
  const wherePayload = plan.conditionTree;
443
+ const hasJoins = this.joins.length > 0;
444
+ const useAggregateFallback = this.shouldUseAggregateFallback();
445
+ const useProjectionFallback = this.shouldUseProjectionFallback();
446
+ const shouldDeferQueryModifiers = hasJoins || useAggregateFallback;
447
+ if (hasJoins || useAggregateFallback || useProjectionFallback) {
448
+ plan.select = undefined;
449
+ plan.selectAll = true;
450
+ }
451
+ const executionPlan = shouldDeferQueryModifiers
452
+ ? {
453
+ ...plan,
454
+ limit: undefined,
455
+ offset: undefined,
456
+ orderBy: undefined,
457
+ ...(useAggregateFallback ? { groupBy: undefined, having: undefined } : {}),
458
+ }
459
+ : plan;
326
460
  const operation = {
327
461
  type: 'select',
328
462
  table: this.selectedTable,
329
463
  where: wherePayload,
330
- limit: this.limitCount,
331
- offset: this.offsetCount,
332
- orderBy: this.orderByClauses.length > 0 ? this.orderByClauses : undefined,
464
+ limit: shouldDeferQueryModifiers ? undefined : this.limitCount,
465
+ offset: shouldDeferQueryModifiers ? undefined : this.offsetCount,
466
+ orderBy: shouldDeferQueryModifiers || this.orderByClauses.length === 0 ? undefined : this.orderByClauses,
333
467
  distinct: this.isDistinct || undefined
334
468
  };
335
- const hasJoins = this.joins.length > 0;
336
- const useAggregateFallback = this.shouldUseAggregateFallback();
337
- if (hasJoins || useAggregateFallback) {
338
- plan.select = undefined;
339
- plan.selectAll = true;
340
- }
341
- operation.plan = plan;
469
+ operation.plan = executionPlan;
342
470
  if (this.groupByColumns.length === 0 && this.hasMixedAggregateSelection()) {
343
471
  throw new Error('Mixed aggregate and non-aggregate selections require groupBy columns');
344
472
  }
345
473
  let intermediateRows;
346
- if (!hasJoins && !useAggregateFallback) {
474
+ if (!hasJoins && !useAggregateFallback && !useProjectionFallback) {
347
475
  if (this.selectedFields) {
348
476
  operation.select = this.selectedFields;
349
477
  }
@@ -354,7 +482,7 @@ export class SelectQueryBuilder {
354
482
  if (hasJoins) {
355
483
  operation.select = undefined;
356
484
  }
357
- else if (!useAggregateFallback) {
485
+ else if (!useAggregateFallback && !useProjectionFallback) {
358
486
  operation.select = this.selectedFields;
359
487
  }
360
488
  else {
@@ -371,51 +499,141 @@ export class SelectQueryBuilder {
371
499
  }
372
500
  }
373
501
  if (useAggregateFallback) {
374
- return this.handleAggregateFallback(intermediateRows);
502
+ let aggregateRows = this.handleAggregateFallback(intermediateRows);
503
+ aggregateRows = this.applyHavingFilter(aggregateRows);
504
+ if (shouldDeferQueryModifiers) {
505
+ aggregateRows = this.applyDeferredOrderBy(aggregateRows);
506
+ aggregateRows = this.applyDeferredOffsetAndLimit(aggregateRows);
507
+ }
508
+ return aggregateRows;
375
509
  }
376
510
  let finalRows = intermediateRows;
377
511
  if (!hasJoins) {
378
512
  finalRows = this.mergeRowsBySubject(finalRows);
379
513
  }
380
514
  if (this.selectedTable) {
381
- finalRows = await this.hydrateInlineColumns(finalRows, this.selectedTable);
515
+ finalRows = await this.hydrateInlineColumns(finalRows, this.selectedTable, !hasJoins);
382
516
  // 处理引用字段:将 URI 转换回 ID
383
517
  finalRows = this.resolveReferenceIds(finalRows, this.selectedTable);
384
518
  }
519
+ if (hasJoins) {
520
+ finalRows = this.applyDeferredOrderBy(finalRows);
521
+ }
385
522
  if (this.selectedFields) {
386
523
  finalRows = finalRows.map((row) => this.projectSelectedRow(row));
387
524
  }
388
- finalRows = this.mergeRowsBySubject(finalRows);
525
+ finalRows = this.applyDistinctRows(finalRows);
526
+ if (!hasJoins) {
527
+ finalRows = this.mergeRowsBySubject(finalRows);
528
+ }
529
+ if (hasJoins) {
530
+ finalRows = this.applyDeferredOffsetAndLimit(finalRows);
531
+ }
389
532
  return finalRows;
390
533
  }
391
534
  }
392
535
  projectSelectedRow(row) {
393
- const projected = {};
394
536
  if (!this.selectedFields) {
395
537
  return row;
396
538
  }
397
- for (const key of Object.keys(this.selectedFields)) {
398
- const field = this.selectedFields[key];
399
- if (isAggregateExpression(field)) {
400
- projected[key] = row[key];
401
- continue;
402
- }
403
- let assigned = false;
404
- for (const candidate of this.resolveFieldBindingCandidates(key, field)) {
405
- if (row[candidate] !== undefined) {
406
- projected[key] = row[candidate];
407
- assigned = true;
408
- break;
539
+ return this.projectFieldMap(row, this.selectedFields);
540
+ }
541
+ projectFieldMap(row, fields, allowAliasFallback = true) {
542
+ const projected = {};
543
+ for (const [key, field] of Object.entries(fields)) {
544
+ projected[key] = this.projectFieldValue(row, key, field, allowAliasFallback);
545
+ }
546
+ return projected;
547
+ }
548
+ projectFieldValue(row, alias, field, allowAliasFallback = true) {
549
+ if (isAggregateExpression(field)) {
550
+ return allowAliasFallback ? row[alias] : undefined;
551
+ }
552
+ if (field instanceof PodTable) {
553
+ return this.projectTableValue(row, field);
554
+ }
555
+ if (this.isSelectFieldMap(field)) {
556
+ const aliases = this.collectSelectionAliases(field);
557
+ const isJoinedOnlySelection = aliases.size === 1 && !aliases.has(this.primaryAlias ?? '');
558
+ const projected = this.projectFieldMap(row, field, !isJoinedOnlySelection);
559
+ if (aliases.size === 1) {
560
+ const [targetAlias] = Array.from(aliases);
561
+ if (targetAlias && targetAlias !== this.primaryAlias && this.isProjectedValueEmpty(projected)) {
562
+ return null;
409
563
  }
410
564
  }
411
- if (!assigned) {
412
- projected[key] = row[key];
565
+ return projected;
566
+ }
567
+ for (const candidate of this.resolveFieldBindingCandidates(alias, field)) {
568
+ if (row[candidate] !== undefined) {
569
+ return row[candidate];
413
570
  }
414
571
  }
572
+ return allowAliasFallback ? row[alias] : undefined;
573
+ }
574
+ projectTableValue(row, table) {
575
+ const alias = this.tableAliases.get(table) ?? table.config.name;
576
+ const projected = {};
577
+ let hasValue = false;
578
+ for (const columnName of Object.keys(table.columns)) {
579
+ const candidates = alias === this.primaryAlias
580
+ ? [columnName, `${alias}.${columnName}`]
581
+ : [`${alias}.${columnName}`];
582
+ const match = candidates.find((candidate) => row[candidate] !== undefined);
583
+ projected[columnName] = match ? row[match] : undefined;
584
+ if (projected[columnName] !== undefined) {
585
+ hasValue = true;
586
+ }
587
+ }
588
+ if (alias !== this.primaryAlias && !hasValue) {
589
+ return null;
590
+ }
415
591
  return projected;
416
592
  }
593
+ isSelectFieldMap(field) {
594
+ return !!field
595
+ && typeof field === 'object'
596
+ && !(field instanceof PodColumnBase)
597
+ && !(field instanceof PodTable)
598
+ && !isAggregateExpression(field);
599
+ }
600
+ collectSelectionAliases(field) {
601
+ if (typeof field === 'string') {
602
+ const { alias } = this.parseColumnReferenceString(field);
603
+ return new Set(alias ? [alias] : this.primaryAlias ? [this.primaryAlias] : []);
604
+ }
605
+ if (field instanceof PodColumnBase) {
606
+ return new Set([this.resolveColumnReference(field).alias]);
607
+ }
608
+ if (field instanceof PodTable) {
609
+ return new Set([this.tableAliases.get(field) ?? field.config.name]);
610
+ }
611
+ if (!this.isSelectFieldMap(field)) {
612
+ return new Set();
613
+ }
614
+ const aliases = new Set();
615
+ for (const child of Object.values(field)) {
616
+ for (const alias of this.collectSelectionAliases(child)) {
617
+ aliases.add(alias);
618
+ }
619
+ }
620
+ return aliases;
621
+ }
622
+ isProjectedValueEmpty(value) {
623
+ if (value === undefined || value === null) {
624
+ return true;
625
+ }
626
+ if (Array.isArray(value)) {
627
+ return value.length === 0;
628
+ }
629
+ if (typeof value === 'object') {
630
+ const entries = Object.values(value);
631
+ return entries.length > 0 && entries.every((entry) => this.isProjectedValueEmpty(entry));
632
+ }
633
+ return false;
634
+ }
417
635
  resolveFieldBindingCandidates(alias, field) {
418
- const candidates = new Set([alias]);
636
+ const candidates = new Set();
419
637
  if (typeof field === 'string') {
420
638
  const { alias: refAlias, column } = this.parseColumnReferenceString(field);
421
639
  candidates.add(field);
@@ -426,8 +644,10 @@ export class SelectQueryBuilder {
426
644
  }
427
645
  else if (field instanceof PodColumnBase) {
428
646
  const columnRef = this.resolveColumnReference(field);
429
- candidates.add(field.name);
430
647
  candidates.add(`${columnRef.alias}.${columnRef.column}`);
648
+ if (!columnRef.alias || columnRef.alias === this.primaryAlias) {
649
+ candidates.add(field.name);
650
+ }
431
651
  }
432
652
  else if (field && typeof field === 'object') {
433
653
  const candidateName = field.name;
@@ -437,13 +657,31 @@ export class SelectQueryBuilder {
437
657
  }
438
658
  return Array.from(candidates);
439
659
  }
660
+ shouldUseProjectionFallback() {
661
+ if (!this.selectedFields) {
662
+ return false;
663
+ }
664
+ const containsStructuredField = (field) => {
665
+ if (field instanceof PodTable) {
666
+ return true;
667
+ }
668
+ if (this.isSelectFieldMap(field)) {
669
+ return true;
670
+ }
671
+ return false;
672
+ };
673
+ return Object.values(this.selectedFields).some((field) => containsStructuredField(field));
674
+ }
440
675
  shouldUseAggregateFallback() {
441
- // GROUP BY is handled at SPARQL level (both LDP/Comunica and SPARQL endpoint support it)
676
+ if (this.havingCondition) {
677
+ return true;
678
+ }
679
+ if (this.joins.length > 0) {
680
+ return this.groupByColumns.length > 0 || this.hasAggregateSelection();
681
+ }
442
682
  if (this.groupByColumns.length > 0) {
443
683
  return false;
444
684
  }
445
- // For pure aggregate queries (no GROUP BY), Comunica seems to have issues with multiple aggregates
446
- // So we use JS fallback for these cases
447
685
  if (!this.selectedFields) {
448
686
  return false;
449
687
  }
@@ -654,6 +892,12 @@ export class SelectQueryBuilder {
654
892
  }
655
893
  return undefined;
656
894
  }
895
+ hasAggregateSelection() {
896
+ if (!this.selectedFields) {
897
+ return false;
898
+ }
899
+ return Object.values(this.selectedFields).some((field) => isAggregateExpression(field));
900
+ }
657
901
  hasMixedAggregateSelection() {
658
902
  if (!this.selectedFields) {
659
903
  return false;
@@ -718,15 +962,17 @@ export class SelectQueryBuilder {
718
962
  return result;
719
963
  });
720
964
  }
721
- async hydrateInlineColumns(rows, table) {
965
+ async hydrateInlineColumns(rows, table, mergeRows = true) {
722
966
  if (!rows.length) {
723
967
  return rows;
724
968
  }
725
- rows = this.mergeRowsBySubject(rows);
726
969
  const inlineColumns = Object.values(table.columns ?? {}).filter((col) => this.isInlineObjectColumn(col));
727
970
  if (inlineColumns.length === 0) {
728
971
  return rows;
729
972
  }
973
+ if (mergeRows) {
974
+ rows = this.mergeRowsBySubject(rows);
975
+ }
730
976
  const predicateToColumn = new Map();
731
977
  inlineColumns.forEach((col) => {
732
978
  const predicate = col.getPredicate(table.config.namespace);
@@ -779,7 +1025,7 @@ export class SelectQueryBuilder {
779
1025
  if (!childIri || !pred)
780
1026
  return;
781
1027
  const child = childMap.get(childIri) ?? { '@id': childIri, id: this.extractIdFromSubject(childIri, table) };
782
- const key = inlineNamespace && pred.startsWith(inlineNamespace) ? pred.slice(inlineNamespace.length) : pred;
1028
+ const key = this.normalizeInlinePredicateKey(pred, inlineNamespace);
783
1029
  if (obj === undefined) {
784
1030
  childMap.set(childIri, child);
785
1031
  return;
@@ -865,6 +1111,20 @@ export class SelectQueryBuilder {
865
1111
  return result;
866
1112
  });
867
1113
  }
1114
+ normalizeInlinePredicateKey(predicate, inlineNamespace) {
1115
+ if (inlineNamespace && predicate.startsWith(inlineNamespace)) {
1116
+ return predicate.slice(inlineNamespace.length);
1117
+ }
1118
+ const hashIndex = predicate.lastIndexOf('#');
1119
+ if (hashIndex !== -1 && hashIndex < predicate.length - 1) {
1120
+ return predicate.slice(hashIndex + 1);
1121
+ }
1122
+ const slashIndex = predicate.lastIndexOf('/');
1123
+ if (slashIndex !== -1 && slashIndex < predicate.length - 1) {
1124
+ return predicate.slice(slashIndex + 1);
1125
+ }
1126
+ return predicate;
1127
+ }
868
1128
  normalizeInlineObjectValue(value) {
869
1129
  if (value === null || value === undefined)
870
1130
  return undefined;
@@ -887,6 +1147,73 @@ export class SelectQueryBuilder {
887
1147
  return value.value;
888
1148
  return undefined;
889
1149
  }
1150
+ applyDeferredOrderBy(rows) {
1151
+ if (this.orderByClauses.length === 0 || rows.length < 2) {
1152
+ return rows;
1153
+ }
1154
+ const sorted = [...rows];
1155
+ sorted.sort((leftRow, rightRow) => {
1156
+ for (const clause of this.orderByClauses) {
1157
+ const leftValue = this.getOrderByValue(leftRow, clause.column);
1158
+ const rightValue = this.getOrderByValue(rightRow, clause.column);
1159
+ const comparison = this.compareOrderByValues(leftValue, rightValue);
1160
+ if (comparison !== 0) {
1161
+ return clause.direction === 'desc' ? -comparison : comparison;
1162
+ }
1163
+ }
1164
+ return 0;
1165
+ });
1166
+ return sorted;
1167
+ }
1168
+ applyDistinctRows(rows) {
1169
+ if (!this.isDistinct || rows.length < 2) {
1170
+ return rows;
1171
+ }
1172
+ const seen = new Set();
1173
+ const deduped = [];
1174
+ for (const row of rows) {
1175
+ const key = this.serializeValueForKey(row);
1176
+ if (seen.has(key)) {
1177
+ continue;
1178
+ }
1179
+ seen.add(key);
1180
+ deduped.push(row);
1181
+ }
1182
+ return deduped;
1183
+ }
1184
+ applyDeferredOffsetAndLimit(rows) {
1185
+ const offset = this.offsetCount ?? 0;
1186
+ const offsetRows = offset > 0 ? rows.slice(offset) : rows;
1187
+ if (this.limitCount === undefined) {
1188
+ return offsetRows;
1189
+ }
1190
+ return offsetRows.slice(0, this.limitCount);
1191
+ }
1192
+ getOrderByValue(row, columnName) {
1193
+ if (columnName in row) {
1194
+ return row[columnName];
1195
+ }
1196
+ const aliasKey = Object.keys(row).find((key) => key.endsWith(`.${columnName}`));
1197
+ if (aliasKey) {
1198
+ return row[aliasKey];
1199
+ }
1200
+ return undefined;
1201
+ }
1202
+ compareOrderByValues(left, right) {
1203
+ if (left === right) {
1204
+ return 0;
1205
+ }
1206
+ if (left === undefined || left === null) {
1207
+ return 1;
1208
+ }
1209
+ if (right === undefined || right === null) {
1210
+ return -1;
1211
+ }
1212
+ if (typeof left === 'number' && typeof right === 'number') {
1213
+ return left - right;
1214
+ }
1215
+ return String(left).localeCompare(String(right));
1216
+ }
890
1217
  mergeRowsBySubject(rows) {
891
1218
  const merged = new Map();
892
1219
  const order = [];
@@ -984,11 +1311,11 @@ export class SelectQueryBuilder {
984
1311
  isInlineObjectColumn(column) {
985
1312
  if (!column)
986
1313
  return false;
987
- if (column.dataType === 'object')
1314
+ if (column.dataType === 'object' || column.dataType === 'json')
988
1315
  return true;
989
1316
  if (column.dataType === 'array') {
990
1317
  const elementType = column.elementType ?? column.options?.baseType;
991
- return elementType === 'object';
1318
+ return elementType === 'object' || elementType === 'json';
992
1319
  }
993
1320
  return false;
994
1321
  }
@@ -1001,6 +1328,10 @@ export class SelectQueryBuilder {
1001
1328
  return combinedRows;
1002
1329
  }
1003
1330
  async fetchJoinRows(join, baseRows) {
1331
+ if (join.type === 'crossJoin') {
1332
+ const joinRows = await this.session.select().from(join.table);
1333
+ return this.normalizeJoinRows(join, joinRows);
1334
+ }
1004
1335
  const conditions = join.resolvedConditions ?? [];
1005
1336
  if (conditions.length === 0) {
1006
1337
  return [];
@@ -1054,6 +1385,18 @@ export class SelectQueryBuilder {
1054
1385
  });
1055
1386
  }
1056
1387
  mergeRowsWithJoin(baseRows, join, joinRows) {
1388
+ if (join.type === 'crossJoin') {
1389
+ if (baseRows.length === 0 || joinRows.length === 0) {
1390
+ return [];
1391
+ }
1392
+ const merged = [];
1393
+ for (const baseRow of baseRows) {
1394
+ for (const joinRow of joinRows) {
1395
+ merged.push({ ...baseRow, ...joinRow });
1396
+ }
1397
+ }
1398
+ return merged;
1399
+ }
1057
1400
  const conditions = join.resolvedConditions ?? [];
1058
1401
  if (conditions.length === 0) {
1059
1402
  return baseRows;
@@ -1106,6 +1449,12 @@ export class SelectQueryBuilder {
1106
1449
  }
1107
1450
  return rows.filter((row) => this.joinFilters.every((condition) => this.evaluateCondition(row, condition)));
1108
1451
  }
1452
+ applyHavingFilter(rows) {
1453
+ if (!this.havingCondition) {
1454
+ return rows;
1455
+ }
1456
+ return rows.filter((row) => this.evaluateCondition(row, this.havingCondition));
1457
+ }
1109
1458
  evaluateCondition(row, condition) {
1110
1459
  switch (condition.type) {
1111
1460
  case 'binary_expr':
@@ -1125,27 +1474,8 @@ export class SelectQueryBuilder {
1125
1474
  if (!left) {
1126
1475
  return true;
1127
1476
  }
1128
- // Extract column name and table alias from left
1129
- let colName;
1130
- let tableAlias;
1131
- if (typeof left === 'string') {
1132
- if (left.includes('.')) {
1133
- [tableAlias, colName] = left.split('.');
1134
- }
1135
- else {
1136
- colName = left;
1137
- }
1138
- }
1139
- else if (left && typeof left === 'object') {
1140
- colName = left.name;
1141
- tableAlias = left.table;
1142
- }
1143
- else {
1144
- return true;
1145
- }
1146
- const columnRef = this.resolveColumnReference(`${tableAlias ?? this.primaryAlias}.${colName}`);
1147
- const value = this.getRowValueForColumn(row, columnRef);
1148
- const target = right;
1477
+ const value = this.resolveConditionOperandValue(row, left, this.primaryAlias);
1478
+ const target = this.resolveConditionOperandValue(row, right);
1149
1479
  switch (op.toUpperCase()) {
1150
1480
  case '=':
1151
1481
  return value === target;
@@ -1173,33 +1503,13 @@ export class SelectQueryBuilder {
1173
1503
  evaluateUnaryCondition(row, condition) {
1174
1504
  const op = condition.operator;
1175
1505
  const val = condition.value;
1176
- // For NOT operator, value is another condition
1177
1506
  if (op.toUpperCase() === 'NOT') {
1178
1507
  return !this.evaluateCondition(row, val);
1179
1508
  }
1180
- // For IS NULL / IS NOT NULL, value is the column reference
1181
1509
  if (!val) {
1182
1510
  return true;
1183
1511
  }
1184
- let colName;
1185
- let tableAlias;
1186
- if (typeof val === 'string') {
1187
- if (val.includes('.')) {
1188
- [tableAlias, colName] = val.split('.');
1189
- }
1190
- else {
1191
- colName = val;
1192
- }
1193
- }
1194
- else if (val && typeof val === 'object' && 'name' in val) {
1195
- colName = val.name;
1196
- tableAlias = val.table;
1197
- }
1198
- else {
1199
- return true;
1200
- }
1201
- const columnRef = this.resolveColumnReference(`${tableAlias ?? this.primaryAlias}.${colName}`);
1202
- const rowValue = this.getRowValueForColumn(row, columnRef);
1512
+ const rowValue = this.resolveConditionOperandValue(row, val, this.primaryAlias);
1203
1513
  switch (op.toUpperCase()) {
1204
1514
  case 'IS NULL':
1205
1515
  return rowValue === null || rowValue === undefined;
@@ -1220,6 +1530,16 @@ export class SelectQueryBuilder {
1220
1530
  }
1221
1531
  return true;
1222
1532
  }
1533
+ resolveConditionOperandValue(row, operand, fallbackAlias) {
1534
+ if (operand instanceof SelectionAliasExpression) {
1535
+ return row[operand.alias];
1536
+ }
1537
+ const columnRef = this.getConditionColumnReference(operand, fallbackAlias);
1538
+ if (columnRef) {
1539
+ return this.getRowValueForColumn(row, columnRef);
1540
+ }
1541
+ return operand;
1542
+ }
1223
1543
  getColumnKeyCandidates(column) {
1224
1544
  const candidates = [];
1225
1545
  const baseAlias = this.primaryAlias;