@dbsp/core 1.0.5 → 1.2.0
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.
- package/dist/index.d.ts +61 -29
- package/dist/index.js +623 -232
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2601,7 +2601,8 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
|
|
|
2601
2601
|
return;
|
|
2602
2602
|
}
|
|
2603
2603
|
const includePath = `${sourceTable}.${relation.name}`;
|
|
2604
|
-
|
|
2604
|
+
const isSelfReferentialRelation = relation.source === relation.target;
|
|
2605
|
+
if (!isSelfReferentialRelation && state.visitedIncludes.has(includePath)) {
|
|
2605
2606
|
state.warnings.push({
|
|
2606
2607
|
code: "CIRCULAR_INCLUDE",
|
|
2607
2608
|
message: `Circular include detected: ${includePath}`,
|
|
@@ -2609,12 +2610,12 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
|
|
|
2609
2610
|
});
|
|
2610
2611
|
return;
|
|
2611
2612
|
}
|
|
2612
|
-
state.visitedIncludes.add(includePath);
|
|
2613
|
+
if (!isSelfReferentialRelation) state.visitedIncludes.add(includePath);
|
|
2613
2614
|
const relationPath = `${sourceTable}.${relation.name}`;
|
|
2614
2615
|
const paths = state.relationAccessCounts.get(relationPath) ?? [];
|
|
2615
2616
|
paths.push(intentPath);
|
|
2616
2617
|
state.relationAccessCounts.set(relationPath, paths);
|
|
2617
|
-
const isRecursiveInclude = (!!include.recursive || !!relation.recursive) &&
|
|
2618
|
+
const isRecursiveInclude = (!!include.recursive || !!relation.recursive) && isSelfReferentialRelation;
|
|
2618
2619
|
let includeStrategy;
|
|
2619
2620
|
if (isRecursiveInclude) {
|
|
2620
2621
|
if (opts.dialectCapabilities?.supportsRecursiveCTE === false) {
|
|
@@ -2749,7 +2750,7 @@ function processInclude(include, sourceTable, model, state, opts, intentPath, de
|
|
|
2749
2750
|
}
|
|
2750
2751
|
}
|
|
2751
2752
|
}
|
|
2752
|
-
state.visitedIncludes.delete(includePath);
|
|
2753
|
+
if (!isSelfReferentialRelation) state.visitedIncludes.delete(includePath);
|
|
2753
2754
|
}
|
|
2754
2755
|
function disambiguateRelation(relationName, sourceTable, model, state, opts, _intentPath, viaHint) {
|
|
2755
2756
|
const directRelation = model.getRelation(`${sourceTable}.${relationName}`);
|
|
@@ -4105,6 +4106,12 @@ function coalesce(fields, as) {
|
|
|
4105
4106
|
};
|
|
4106
4107
|
}
|
|
4107
4108
|
function raw(sqlFragment, as) {
|
|
4109
|
+
if (typeof as !== "string" || as.trim().length === 0) {
|
|
4110
|
+
throw new InvalidOperationError(
|
|
4111
|
+
"raw",
|
|
4112
|
+
"raw() requires an alias as second argument, e.g. raw('COUNT(*)', 'count')"
|
|
4113
|
+
);
|
|
4114
|
+
}
|
|
4108
4115
|
validateIdentifier(as, "column");
|
|
4109
4116
|
return {
|
|
4110
4117
|
__expr: true,
|
|
@@ -5446,6 +5453,122 @@ var LightweightModelIR = class {
|
|
|
5446
5453
|
};
|
|
5447
5454
|
|
|
5448
5455
|
// src/dx/mutation-builders.ts
|
|
5456
|
+
function compileMutationIntent(adapter, intent, options) {
|
|
5457
|
+
switch (intent.type) {
|
|
5458
|
+
case "insert":
|
|
5459
|
+
return adapter.compileInsert(intent, options);
|
|
5460
|
+
case "insert_from":
|
|
5461
|
+
return adapter.compileInsertFrom(intent, options);
|
|
5462
|
+
case "update":
|
|
5463
|
+
return adapter.compileUpdate(intent, options);
|
|
5464
|
+
case "batchUpdate":
|
|
5465
|
+
return adapter.compileBatchUpdate(intent, options);
|
|
5466
|
+
case "delete":
|
|
5467
|
+
return adapter.compileDelete(intent, options);
|
|
5468
|
+
case "upsert":
|
|
5469
|
+
return adapter.compileUpsert(intent, options);
|
|
5470
|
+
case "upsert_from":
|
|
5471
|
+
return adapter.compileUpsertFrom(intent, options);
|
|
5472
|
+
}
|
|
5473
|
+
const _exhaustive = intent;
|
|
5474
|
+
throw new Error(
|
|
5475
|
+
`Unsupported mutation type: ${_exhaustive.type}`
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5478
|
+
async function runMutationWithHooks(opts) {
|
|
5479
|
+
if (!opts.hookStore || !hasHooks(opts.hookStore)) {
|
|
5480
|
+
const prepared = opts.prepare(opts.intent);
|
|
5481
|
+
return prepared.execute();
|
|
5482
|
+
}
|
|
5483
|
+
return withReentrancyGuard(
|
|
5484
|
+
opts.hookStore,
|
|
5485
|
+
(store) => runMutationWithHooksInner(opts, store)
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
async function runMutationWithHooksInner(opts, store) {
|
|
5489
|
+
const { intent } = opts;
|
|
5490
|
+
const operation = intent.type;
|
|
5491
|
+
const startTime = Date.now();
|
|
5492
|
+
const { cardinality, data } = extractMutationIntentData(intent);
|
|
5493
|
+
let ctx = Object.freeze({
|
|
5494
|
+
table: opts.table,
|
|
5495
|
+
operation,
|
|
5496
|
+
intent,
|
|
5497
|
+
cardinality,
|
|
5498
|
+
data,
|
|
5499
|
+
...opts.schemaName !== void 0 ? { schemaName: opts.schemaName } : {},
|
|
5500
|
+
...opts.inTransaction ? { inTransaction: true } : {}
|
|
5501
|
+
});
|
|
5502
|
+
try {
|
|
5503
|
+
if (store.beforeMutation.length > 0) {
|
|
5504
|
+
ctx = await runBeforeMutationHooks(
|
|
5505
|
+
store.beforeMutation,
|
|
5506
|
+
ctx,
|
|
5507
|
+
opts.onHookError
|
|
5508
|
+
);
|
|
5509
|
+
}
|
|
5510
|
+
const prepared = opts.prepare(intent);
|
|
5511
|
+
const duration = Date.now() - startTime;
|
|
5512
|
+
const result = await prepared.execute();
|
|
5513
|
+
if (store.afterMutation.length > 0) {
|
|
5514
|
+
const afterCtx = Object.freeze({
|
|
5515
|
+
...ctx,
|
|
5516
|
+
sql: prepared.sql,
|
|
5517
|
+
parameters: prepared.parameters,
|
|
5518
|
+
duration
|
|
5519
|
+
});
|
|
5520
|
+
const transformed = await runAfterMutationHooks(
|
|
5521
|
+
store.afterMutation,
|
|
5522
|
+
afterCtx,
|
|
5523
|
+
[...prepared.getAfterMutationResult?.(result) ?? []],
|
|
5524
|
+
opts.onHookError
|
|
5525
|
+
);
|
|
5526
|
+
if (prepared.returnAfterMutationResult) {
|
|
5527
|
+
return transformed;
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
return result;
|
|
5531
|
+
} catch (error) {
|
|
5532
|
+
if (store.onError.length > 0) {
|
|
5533
|
+
const errorCtx = {
|
|
5534
|
+
table: opts.table,
|
|
5535
|
+
operation,
|
|
5536
|
+
error,
|
|
5537
|
+
intent,
|
|
5538
|
+
phase: "beforeMutation",
|
|
5539
|
+
...opts.schemaName !== void 0 ? { schemaName: opts.schemaName } : {}
|
|
5540
|
+
};
|
|
5541
|
+
const transformed = await runOnErrorHooks(store.onError, errorCtx);
|
|
5542
|
+
throw transformed;
|
|
5543
|
+
}
|
|
5544
|
+
throw error;
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
function extractMutationIntentData(intent) {
|
|
5548
|
+
if (intent.type === "insert" || intent.type === "upsert") {
|
|
5549
|
+
const values = intent.values;
|
|
5550
|
+
return {
|
|
5551
|
+
cardinality: values.length > 1 ? "bulk" : "single",
|
|
5552
|
+
data: values.length > 1 ? values : values[0]
|
|
5553
|
+
};
|
|
5554
|
+
}
|
|
5555
|
+
if (intent.type === "update") {
|
|
5556
|
+
return {
|
|
5557
|
+
cardinality: "single",
|
|
5558
|
+
data: intent.set
|
|
5559
|
+
};
|
|
5560
|
+
}
|
|
5561
|
+
if (intent.type === "batchUpdate") {
|
|
5562
|
+
return {
|
|
5563
|
+
cardinality: "bulk",
|
|
5564
|
+
data: intent.updates
|
|
5565
|
+
};
|
|
5566
|
+
}
|
|
5567
|
+
if (intent.type === "insert_from" || intent.type === "upsert_from") {
|
|
5568
|
+
return { cardinality: "bulk", data: void 0 };
|
|
5569
|
+
}
|
|
5570
|
+
return { cardinality: "single", data: void 0 };
|
|
5571
|
+
}
|
|
5449
5572
|
var MutationBuilderBase = class {
|
|
5450
5573
|
table;
|
|
5451
5574
|
model;
|
|
@@ -5500,11 +5623,15 @@ var MutationBuilderBase = class {
|
|
|
5500
5623
|
Object.keys(compileOptions).length > 0 ? compileOptions : void 0
|
|
5501
5624
|
);
|
|
5502
5625
|
const meta = {
|
|
5503
|
-
compiledAt: /* @__PURE__ */ new Date()
|
|
5626
|
+
compiledAt: /* @__PURE__ */ new Date(),
|
|
5627
|
+
...this.schemaName !== void 0 && { schema: this.schemaName },
|
|
5628
|
+
...extraOptions?.queryName !== void 0 && {
|
|
5629
|
+
queryName: extraOptions.queryName
|
|
5630
|
+
},
|
|
5631
|
+
...extraOptions?.correlationId !== void 0 && {
|
|
5632
|
+
correlationId: extraOptions.correlationId
|
|
5633
|
+
}
|
|
5504
5634
|
};
|
|
5505
|
-
if (this.schemaName !== void 0) {
|
|
5506
|
-
meta.schema = this.schemaName;
|
|
5507
|
-
}
|
|
5508
5635
|
return {
|
|
5509
5636
|
sql: compiled.sql,
|
|
5510
5637
|
parameters: compiled.parameters,
|
|
@@ -5514,129 +5641,38 @@ var MutationBuilderBase = class {
|
|
|
5514
5641
|
}
|
|
5515
5642
|
async execute() {
|
|
5516
5643
|
const adapter = this.requireAdapter(this.operationName);
|
|
5517
|
-
if (!this.hookStore || !hasHooks(this.hookStore)) {
|
|
5518
|
-
return this.executeWithoutHooks(adapter);
|
|
5519
|
-
}
|
|
5520
|
-
return this.executeWithHooks(adapter);
|
|
5521
|
-
}
|
|
5522
|
-
async executeWithoutHooks(adapter) {
|
|
5523
5644
|
const intent = this.buildIntent();
|
|
5524
|
-
|
|
5525
|
-
const compiled = this.compileIntent(adapter, intent, compileOptions);
|
|
5526
|
-
if (this.returningColumns && this.returningColumns.length > 0) {
|
|
5527
|
-
const result = await adapter.execute(compiled);
|
|
5528
|
-
return result;
|
|
5529
|
-
}
|
|
5530
|
-
await adapter.execute(compiled);
|
|
5531
|
-
return void 0;
|
|
5532
|
-
}
|
|
5533
|
-
async executeWithHooks(adapter) {
|
|
5534
|
-
const store = this.hookStore;
|
|
5535
|
-
if (!store) throw new Error("executeWithHooks called without hookStore");
|
|
5536
|
-
return withReentrancyGuard(
|
|
5537
|
-
store,
|
|
5538
|
-
(s) => this.executeWithHooksInner(adapter, s)
|
|
5539
|
-
);
|
|
5540
|
-
}
|
|
5541
|
-
async executeWithHooksInner(adapter, store) {
|
|
5542
|
-
const intent = this.buildIntent();
|
|
5543
|
-
const operation = intent.type;
|
|
5544
|
-
const startTime = Date.now();
|
|
5545
|
-
const { cardinality, data } = this.extractIntentData(intent);
|
|
5546
|
-
let ctx = Object.freeze({
|
|
5645
|
+
return runMutationWithHooks({
|
|
5547
5646
|
table: this.table,
|
|
5548
|
-
operation,
|
|
5549
5647
|
intent,
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5648
|
+
hookStore: this.hookStore,
|
|
5649
|
+
onHookError: this.onHookError,
|
|
5650
|
+
schemaName: this.schemaName,
|
|
5651
|
+
inTransaction: this.inTransaction,
|
|
5652
|
+
prepare: (preparedIntent) => this.prepareMutationExecution(adapter, preparedIntent)
|
|
5554
5653
|
});
|
|
5555
|
-
try {
|
|
5556
|
-
if (store.beforeMutation.length > 0) {
|
|
5557
|
-
ctx = await runBeforeMutationHooks(
|
|
5558
|
-
store.beforeMutation,
|
|
5559
|
-
ctx,
|
|
5560
|
-
this.onHookError
|
|
5561
|
-
);
|
|
5562
|
-
}
|
|
5563
|
-
const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
|
|
5564
|
-
const compiled = this.compileIntent(adapter, intent, compileOptions);
|
|
5565
|
-
const duration = Date.now() - startTime;
|
|
5566
|
-
if (this.returningColumns && this.returningColumns.length > 0) {
|
|
5567
|
-
const result = await adapter.execute(compiled);
|
|
5568
|
-
const afterCtx = Object.freeze({
|
|
5569
|
-
...ctx,
|
|
5570
|
-
sql: compiled.sql,
|
|
5571
|
-
parameters: compiled.parameters,
|
|
5572
|
-
duration
|
|
5573
|
-
});
|
|
5574
|
-
if (store.afterMutation.length > 0) {
|
|
5575
|
-
const transformed = await runAfterMutationHooks(
|
|
5576
|
-
store.afterMutation,
|
|
5577
|
-
afterCtx,
|
|
5578
|
-
result,
|
|
5579
|
-
this.onHookError
|
|
5580
|
-
);
|
|
5581
|
-
return transformed;
|
|
5582
|
-
}
|
|
5583
|
-
return result;
|
|
5584
|
-
}
|
|
5585
|
-
await adapter.execute(compiled);
|
|
5586
|
-
if (store.afterMutation.length > 0) {
|
|
5587
|
-
const afterCtx = Object.freeze({
|
|
5588
|
-
...ctx,
|
|
5589
|
-
sql: compiled.sql,
|
|
5590
|
-
parameters: compiled.parameters,
|
|
5591
|
-
duration
|
|
5592
|
-
});
|
|
5593
|
-
await runAfterMutationHooks(
|
|
5594
|
-
store.afterMutation,
|
|
5595
|
-
afterCtx,
|
|
5596
|
-
[],
|
|
5597
|
-
this.onHookError
|
|
5598
|
-
);
|
|
5599
|
-
}
|
|
5600
|
-
return void 0;
|
|
5601
|
-
} catch (error) {
|
|
5602
|
-
if (store.onError.length > 0) {
|
|
5603
|
-
const errorCtx = {
|
|
5604
|
-
table: this.table,
|
|
5605
|
-
operation,
|
|
5606
|
-
error,
|
|
5607
|
-
intent,
|
|
5608
|
-
phase: "beforeMutation",
|
|
5609
|
-
...this.schemaName !== void 0 ? { schemaName: this.schemaName } : {}
|
|
5610
|
-
};
|
|
5611
|
-
const transformed = await runOnErrorHooks(store.onError, errorCtx);
|
|
5612
|
-
throw transformed;
|
|
5613
|
-
}
|
|
5614
|
-
throw error;
|
|
5615
|
-
}
|
|
5616
5654
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
return {
|
|
5622
|
-
cardinality: values.length > 1 ? "bulk" : "single",
|
|
5623
|
-
data: values.length > 1 ? values : values[0]
|
|
5624
|
-
};
|
|
5625
|
-
}
|
|
5626
|
-
if (intent.type === "update") {
|
|
5627
|
-
return {
|
|
5628
|
-
cardinality: "single",
|
|
5629
|
-
data: intent.set
|
|
5630
|
-
};
|
|
5631
|
-
}
|
|
5632
|
-
if (intent.type === "batchUpdate") {
|
|
5633
|
-
const updates = intent.updates;
|
|
5655
|
+
prepareMutationExecution(adapter, intent) {
|
|
5656
|
+
const compileOptions = this.schemaName ? { schemaName: this.schemaName } : void 0;
|
|
5657
|
+
const compiled = this.compileIntent(adapter, intent, compileOptions);
|
|
5658
|
+
if (this.returningColumns && this.returningColumns.length > 0) {
|
|
5634
5659
|
return {
|
|
5635
|
-
|
|
5636
|
-
|
|
5660
|
+
sql: compiled.sql,
|
|
5661
|
+
parameters: compiled.parameters,
|
|
5662
|
+
execute: () => adapter.execute(compiled),
|
|
5663
|
+
getAfterMutationResult: (result) => result,
|
|
5664
|
+
returnAfterMutationResult: true
|
|
5637
5665
|
};
|
|
5638
5666
|
}
|
|
5639
|
-
return {
|
|
5667
|
+
return {
|
|
5668
|
+
sql: compiled.sql,
|
|
5669
|
+
parameters: compiled.parameters,
|
|
5670
|
+
execute: async () => {
|
|
5671
|
+
await adapter.execute(compiled);
|
|
5672
|
+
return void 0;
|
|
5673
|
+
},
|
|
5674
|
+
getAfterMutationResult: () => []
|
|
5675
|
+
};
|
|
5640
5676
|
}
|
|
5641
5677
|
};
|
|
5642
5678
|
var InsertBuilder = class _InsertBuilder extends MutationBuilderBase {
|
|
@@ -5696,7 +5732,7 @@ var InsertBuilder = class _InsertBuilder extends MutationBuilderBase {
|
|
|
5696
5732
|
return intent;
|
|
5697
5733
|
}
|
|
5698
5734
|
compileIntent(adapter, intent, options) {
|
|
5699
|
-
return adapter
|
|
5735
|
+
return compileMutationIntent(adapter, intent, options);
|
|
5700
5736
|
}
|
|
5701
5737
|
};
|
|
5702
5738
|
var UpdateBuilder = class _UpdateBuilder extends MutationBuilderBase {
|
|
@@ -5851,10 +5887,7 @@ var UpdateBuilder = class _UpdateBuilder extends MutationBuilderBase {
|
|
|
5851
5887
|
return intent;
|
|
5852
5888
|
}
|
|
5853
5889
|
compileIntent(adapter, intent, options) {
|
|
5854
|
-
|
|
5855
|
-
return adapter.compileBatchUpdate(intent, options);
|
|
5856
|
-
}
|
|
5857
|
-
return adapter.compileUpdate(intent, options);
|
|
5890
|
+
return compileMutationIntent(adapter, intent, options);
|
|
5858
5891
|
}
|
|
5859
5892
|
};
|
|
5860
5893
|
var DeleteBuilder = class _DeleteBuilder extends MutationBuilderBase {
|
|
@@ -5941,7 +5974,7 @@ var DeleteBuilder = class _DeleteBuilder extends MutationBuilderBase {
|
|
|
5941
5974
|
return intent;
|
|
5942
5975
|
}
|
|
5943
5976
|
compileIntent(adapter, intent, options) {
|
|
5944
|
-
return adapter
|
|
5977
|
+
return compileMutationIntent(adapter, intent, options);
|
|
5945
5978
|
}
|
|
5946
5979
|
};
|
|
5947
5980
|
var UpsertBuilder = class _UpsertBuilder extends MutationBuilderBase {
|
|
@@ -6073,7 +6106,7 @@ var UpsertBuilder = class _UpsertBuilder extends MutationBuilderBase {
|
|
|
6073
6106
|
return intent;
|
|
6074
6107
|
}
|
|
6075
6108
|
compileIntent(adapter, intent, options) {
|
|
6076
|
-
return adapter
|
|
6109
|
+
return compileMutationIntent(adapter, intent, options);
|
|
6077
6110
|
}
|
|
6078
6111
|
};
|
|
6079
6112
|
|
|
@@ -6126,80 +6159,192 @@ function negotiateFeatures(model, capabilities, behavior = "warning", checkers =
|
|
|
6126
6159
|
}
|
|
6127
6160
|
|
|
6128
6161
|
// src/dx/nql.ts
|
|
6129
|
-
import {
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6162
|
+
import {
|
|
6163
|
+
NqlLexer,
|
|
6164
|
+
compile as nqlCompile
|
|
6165
|
+
} from "@dbsp/nql";
|
|
6166
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS } from "@dbsp/types/internal";
|
|
6167
|
+
var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
|
|
6168
|
+
function hasNqlBindings(bundle) {
|
|
6169
|
+
return (bundle.bindings?.size ?? 0) > 0;
|
|
6170
|
+
}
|
|
6171
|
+
function assertNoMutationBindingBodies(bundle) {
|
|
6172
|
+
const mutationBindings = bundle.mutationBindings;
|
|
6173
|
+
if (!mutationBindings || mutationBindings.size === 0) {
|
|
6174
|
+
return;
|
|
6133
6175
|
}
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6176
|
+
const bindNames = [...mutationBindings.keys()].map((name) => `'${name}'`).join(", ");
|
|
6177
|
+
throw new Error(
|
|
6178
|
+
`NQL tag programs cannot use mutation bodies in | bind clauses (${bindNames}); writable binding CTEs are disabled for tag execution (#173). Use a read-only bind before a single final mutation, or execute the mutation explicitly.`
|
|
6179
|
+
);
|
|
6180
|
+
}
|
|
6181
|
+
function nqlRaw(fragment) {
|
|
6182
|
+
if (typeof fragment !== "string") {
|
|
6183
|
+
throw new TypeError("nqlRaw() expects a string fragment");
|
|
6184
|
+
}
|
|
6185
|
+
const raw2 = { fragment };
|
|
6186
|
+
Object.defineProperty(raw2, NQL_RAW_FRAGMENT, {
|
|
6187
|
+
value: true,
|
|
6188
|
+
enumerable: false
|
|
6189
|
+
});
|
|
6190
|
+
return Object.freeze(raw2);
|
|
6191
|
+
}
|
|
6192
|
+
function isNqlRawFragment(value) {
|
|
6193
|
+
if (typeof value !== "object" || value === null) {
|
|
6194
|
+
return false;
|
|
6195
|
+
}
|
|
6196
|
+
if (!Object.hasOwn(value, NQL_RAW_FRAGMENT)) {
|
|
6197
|
+
return false;
|
|
6198
|
+
}
|
|
6199
|
+
return value[NQL_RAW_FRAGMENT] === true;
|
|
6200
|
+
}
|
|
6201
|
+
function isInsideGeneratedRange(start, end, generatedRanges) {
|
|
6202
|
+
return generatedRanges.some(
|
|
6203
|
+
(range) => start >= range.start && end <= range.end
|
|
6204
|
+
);
|
|
6205
|
+
}
|
|
6206
|
+
function findOverlappingGeneratedRange(start, end, generatedRanges) {
|
|
6207
|
+
return generatedRanges.find(
|
|
6208
|
+
(range) => start < range.end && end > range.start
|
|
6209
|
+
);
|
|
6210
|
+
}
|
|
6211
|
+
function findExactGeneratedRange(name, start, end, generatedRanges) {
|
|
6212
|
+
return generatedRanges.find(
|
|
6213
|
+
(range) => range.name === name && range.start === start && range.end === end
|
|
6214
|
+
);
|
|
6215
|
+
}
|
|
6216
|
+
function generatedParamLexError(name) {
|
|
6217
|
+
return `Generated NQL parameter :${name} was not recognized as exactly one NamedParam token after raw-fragment assembly; check adjacent nqlRaw() fragments for quotes, comments, or identifier text that can swallow the placeholder.`;
|
|
6218
|
+
}
|
|
6219
|
+
function findInternalParamSourceError(query, generatedRanges) {
|
|
6220
|
+
const lexResult = NqlLexer.tokenize(query);
|
|
6221
|
+
const generatedTokenCounts = new Map(
|
|
6222
|
+
generatedRanges.map((range) => [range.name, 0])
|
|
6223
|
+
);
|
|
6224
|
+
for (const token of lexResult.tokens) {
|
|
6225
|
+
if (token.tokenType.name !== "NamedParam") {
|
|
6226
|
+
continue;
|
|
6150
6227
|
}
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
`nql\`...\`: cannot interpolate a string containing a newline at position ${index}. NQL string literals do not support raw newline characters. For dynamic NQL structure use the builder API (orm.select()). See issue #134.`
|
|
6155
|
-
);
|
|
6156
|
-
}
|
|
6157
|
-
const escaped = value.replaceAll("'", "''");
|
|
6158
|
-
return `'${escaped}'`;
|
|
6228
|
+
const name = token.image.slice(1);
|
|
6229
|
+
if (!name.startsWith("__p")) {
|
|
6230
|
+
continue;
|
|
6159
6231
|
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6232
|
+
const start = token.startOffset;
|
|
6233
|
+
const end = (token.endOffset ?? start + token.image.length - 1) + 1;
|
|
6234
|
+
const exactGeneratedRange = findExactGeneratedRange(
|
|
6235
|
+
name,
|
|
6236
|
+
start,
|
|
6237
|
+
end,
|
|
6238
|
+
generatedRanges
|
|
6239
|
+
);
|
|
6240
|
+
if (exactGeneratedRange) {
|
|
6241
|
+
generatedTokenCounts.set(
|
|
6242
|
+
exactGeneratedRange.name,
|
|
6243
|
+
(generatedTokenCounts.get(exactGeneratedRange.name) ?? 0) + 1
|
|
6164
6244
|
);
|
|
6245
|
+
continue;
|
|
6246
|
+
}
|
|
6247
|
+
const overlappingGeneratedRange = findOverlappingGeneratedRange(
|
|
6248
|
+
start,
|
|
6249
|
+
end,
|
|
6250
|
+
generatedRanges
|
|
6251
|
+
);
|
|
6252
|
+
if (overlappingGeneratedRange) {
|
|
6253
|
+
return generatedParamLexError(overlappingGeneratedRange.name);
|
|
6254
|
+
}
|
|
6255
|
+
if (!isInsideGeneratedRange(start, end, generatedRanges)) {
|
|
6256
|
+
return `Reserved NQL parameter namespace "__p" cannot be referenced by user source (${token.image}).`;
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
for (const range of generatedRanges) {
|
|
6260
|
+
if (generatedTokenCounts.get(range.name) !== 1) {
|
|
6261
|
+
return generatedParamLexError(range.name);
|
|
6165
6262
|
}
|
|
6166
6263
|
}
|
|
6264
|
+
return void 0;
|
|
6167
6265
|
}
|
|
6168
|
-
function
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6266
|
+
function assembleNqlTemplate(strings, values) {
|
|
6267
|
+
const params = /* @__PURE__ */ Object.create(null);
|
|
6268
|
+
const generatedRanges = [];
|
|
6269
|
+
let query = strings[0] ?? "";
|
|
6270
|
+
let boundIndex = 0;
|
|
6271
|
+
for (let i = 0; i < values.length; i++) {
|
|
6272
|
+
const value = values[i];
|
|
6273
|
+
if (isNqlRawFragment(value)) {
|
|
6274
|
+
query += value.fragment;
|
|
6275
|
+
} else {
|
|
6276
|
+
const name = `__p${boundIndex++}`;
|
|
6277
|
+
const placeholder = `:${name}`;
|
|
6278
|
+
const start = query.length;
|
|
6279
|
+
query += placeholder;
|
|
6280
|
+
generatedRanges.push({ name, start, end: start + placeholder.length });
|
|
6281
|
+
params[name] = value;
|
|
6173
6282
|
}
|
|
6283
|
+
query += strings[i + 1] ?? "";
|
|
6284
|
+
}
|
|
6285
|
+
return {
|
|
6286
|
+
query,
|
|
6287
|
+
params,
|
|
6288
|
+
hasBoundParams: boundIndex > 0,
|
|
6289
|
+
sourceError: findInternalParamSourceError(query, generatedRanges)
|
|
6290
|
+
};
|
|
6291
|
+
}
|
|
6292
|
+
function createNqlTag(schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
|
|
6293
|
+
return function nql(strings, ...values) {
|
|
6294
|
+
const assembled = assembleNqlTemplate(strings, values);
|
|
6174
6295
|
return new NqlBuilderImpl(
|
|
6175
|
-
query,
|
|
6296
|
+
assembled.query,
|
|
6297
|
+
assembled.params,
|
|
6298
|
+
assembled.hasBoundParams,
|
|
6299
|
+
assembled.sourceError,
|
|
6176
6300
|
schemaDefinition,
|
|
6177
6301
|
model,
|
|
6178
6302
|
adapter,
|
|
6179
|
-
schemaName
|
|
6303
|
+
schemaName,
|
|
6304
|
+
hookStore,
|
|
6305
|
+
onHookError,
|
|
6306
|
+
inTransaction
|
|
6180
6307
|
);
|
|
6181
6308
|
};
|
|
6182
6309
|
}
|
|
6183
6310
|
var NqlBuilderImpl = class {
|
|
6184
|
-
|
|
6311
|
+
_compiled;
|
|
6185
6312
|
query;
|
|
6313
|
+
params;
|
|
6314
|
+
hasBoundParams;
|
|
6315
|
+
sourceError;
|
|
6186
6316
|
schemaDefinition;
|
|
6187
6317
|
model;
|
|
6188
|
-
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Reserved for future schema-scoping support
|
|
6189
6318
|
_schemaName;
|
|
6190
6319
|
adapter;
|
|
6191
|
-
|
|
6320
|
+
hookStore;
|
|
6321
|
+
onHookError;
|
|
6322
|
+
inTransaction;
|
|
6323
|
+
constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName, hookStore, onHookError, inTransaction) {
|
|
6192
6324
|
this.query = query;
|
|
6325
|
+
this.params = params;
|
|
6326
|
+
this.hasBoundParams = hasBoundParams;
|
|
6327
|
+
this.sourceError = sourceError;
|
|
6193
6328
|
this.schemaDefinition = schemaDefinition;
|
|
6194
6329
|
this.model = model;
|
|
6195
6330
|
this.adapter = adapter;
|
|
6196
6331
|
this._schemaName = schemaName;
|
|
6332
|
+
this.hookStore = hookStore;
|
|
6333
|
+
this.onHookError = onHookError;
|
|
6334
|
+
this.inTransaction = inTransaction;
|
|
6197
6335
|
}
|
|
6198
6336
|
compile() {
|
|
6199
|
-
if (this.
|
|
6200
|
-
return this.
|
|
6337
|
+
if (this._compiled) {
|
|
6338
|
+
return this._compiled;
|
|
6339
|
+
}
|
|
6340
|
+
if (this.sourceError !== void 0) {
|
|
6341
|
+
throw new Error(`NQL compilation failed: ${this.sourceError}`);
|
|
6201
6342
|
}
|
|
6202
|
-
const compilerOptions =
|
|
6343
|
+
const compilerOptions = {
|
|
6344
|
+
...extractPseudoColumnKeywords(this.model) ?? {},
|
|
6345
|
+
params: this.params,
|
|
6346
|
+
[NQL_INTERNAL_COMPILER_OPTIONS]: { allowInternalParams: true }
|
|
6347
|
+
};
|
|
6203
6348
|
const result = nqlCompile(
|
|
6204
6349
|
this.query,
|
|
6205
6350
|
this.schemaDefinition,
|
|
@@ -6208,28 +6353,57 @@ var NqlBuilderImpl = class {
|
|
|
6208
6353
|
);
|
|
6209
6354
|
if (!result.success) {
|
|
6210
6355
|
const errors = result.errors?.map((e) => e.message).join(", ") ?? "Unknown error";
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
if (result.ast?.mutation && !result.ast?.query) {
|
|
6214
|
-
throw new Error(
|
|
6215
|
-
"INSERT/UPDATE/DELETE/UPSERT not yet supported via the nql`...` tagged template. Use orm.insert(table, data) / orm.update(table, set) / orm.delete(table) / orm.upsert(table, data) instead. Tracking: https://github.com/oorabona/db-semantic-planner/issues/113"
|
|
6216
|
-
);
|
|
6356
|
+
const rawHint = this.hasBoundParams ? " If an interpolation was intended as NQL structure, wrap a trusted fragment with nqlRaw()." : "";
|
|
6357
|
+
throw new Error(`NQL compilation failed: ${errors}${rawHint}`);
|
|
6217
6358
|
}
|
|
6218
|
-
|
|
6359
|
+
const bundle = result.ast;
|
|
6360
|
+
if (!bundle) {
|
|
6219
6361
|
throw new Error("NQL compilation failed: no query AST produced");
|
|
6220
6362
|
}
|
|
6221
|
-
|
|
6222
|
-
|
|
6363
|
+
assertNoMutationBindingBodies(bundle);
|
|
6364
|
+
if (bundle.query) {
|
|
6365
|
+
this._compiled = {
|
|
6366
|
+
kind: "query",
|
|
6367
|
+
bundle,
|
|
6368
|
+
intent: bundle.query
|
|
6369
|
+
};
|
|
6370
|
+
return this._compiled;
|
|
6371
|
+
}
|
|
6372
|
+
if (bundle.mutation) {
|
|
6373
|
+
this._compiled = {
|
|
6374
|
+
kind: "mutation",
|
|
6375
|
+
bundle,
|
|
6376
|
+
intent: bundle.mutation
|
|
6377
|
+
};
|
|
6378
|
+
return this._compiled;
|
|
6379
|
+
}
|
|
6380
|
+
throw new Error("NQL compilation failed: no query AST produced");
|
|
6223
6381
|
}
|
|
6224
6382
|
toIntentIR() {
|
|
6225
|
-
return this.compile();
|
|
6383
|
+
return this.compile().intent;
|
|
6384
|
+
}
|
|
6385
|
+
planInternal() {
|
|
6386
|
+
const compiled = this.compile();
|
|
6387
|
+
if (compiled.kind === "mutation") {
|
|
6388
|
+
throw new Error(
|
|
6389
|
+
"NQL mutations do not have execution plans; use dump() for SQL and parameters."
|
|
6390
|
+
);
|
|
6391
|
+
}
|
|
6392
|
+
return plan(compiled.intent, this.model);
|
|
6226
6393
|
}
|
|
6227
6394
|
plan() {
|
|
6228
|
-
|
|
6229
|
-
return plan(intent, this.model);
|
|
6395
|
+
return this.planInternal();
|
|
6230
6396
|
}
|
|
6231
6397
|
dump(meta) {
|
|
6232
|
-
const
|
|
6398
|
+
const compiledIntent = this.compile();
|
|
6399
|
+
if (compiledIntent.kind === "mutation") {
|
|
6400
|
+
return this.dumpMutation(
|
|
6401
|
+
compiledIntent.bundle,
|
|
6402
|
+
compiledIntent.intent,
|
|
6403
|
+
meta
|
|
6404
|
+
);
|
|
6405
|
+
}
|
|
6406
|
+
const planReport = this.planInternal();
|
|
6233
6407
|
if (!this.adapter) {
|
|
6234
6408
|
return {
|
|
6235
6409
|
plan: planReport,
|
|
@@ -6238,7 +6412,10 @@ var NqlBuilderImpl = class {
|
|
|
6238
6412
|
...meta !== void 0 && { meta }
|
|
6239
6413
|
};
|
|
6240
6414
|
}
|
|
6241
|
-
const compiled = this.adapter.compile(
|
|
6415
|
+
const compiled = hasNqlBindings(compiledIntent.bundle) ? this.adapter.compile(
|
|
6416
|
+
compiledIntent.bundle,
|
|
6417
|
+
this.nqlBundleCompileOptions()
|
|
6418
|
+
) : this.adapter.compile(planReport);
|
|
6242
6419
|
try {
|
|
6243
6420
|
return this.adapter.createDump(planReport, compiled, meta);
|
|
6244
6421
|
} catch (err) {
|
|
@@ -6266,15 +6443,88 @@ var NqlBuilderImpl = class {
|
|
|
6266
6443
|
throw err;
|
|
6267
6444
|
}
|
|
6268
6445
|
}
|
|
6269
|
-
|
|
6446
|
+
requireAdapter(operation) {
|
|
6270
6447
|
if (!this.adapter) {
|
|
6448
|
+
throw new ExecutionError({
|
|
6449
|
+
operation,
|
|
6450
|
+
reason: "Adapter not configured",
|
|
6451
|
+
fix: "Pass adapter option when creating ORM: createOrm({ model, adapter })"
|
|
6452
|
+
});
|
|
6453
|
+
}
|
|
6454
|
+
return this.adapter;
|
|
6455
|
+
}
|
|
6456
|
+
mutationCompileOptions(extraOptions) {
|
|
6457
|
+
return {
|
|
6458
|
+
model: this.model,
|
|
6459
|
+
...this._schemaName !== void 0 && { schemaName: this._schemaName },
|
|
6460
|
+
...extraOptions
|
|
6461
|
+
};
|
|
6462
|
+
}
|
|
6463
|
+
nqlBundleCompileOptions() {
|
|
6464
|
+
return {
|
|
6465
|
+
model: this.model,
|
|
6466
|
+
...this._schemaName !== void 0 && { schemaName: this._schemaName }
|
|
6467
|
+
};
|
|
6468
|
+
}
|
|
6469
|
+
dumpMutation(bundle, intent, meta) {
|
|
6470
|
+
const adapter = this.requireAdapter("dump");
|
|
6471
|
+
const compiled = adapter.compile(bundle, this.mutationCompileOptions(meta));
|
|
6472
|
+
const dumpMeta = {
|
|
6473
|
+
compiledAt: /* @__PURE__ */ new Date(),
|
|
6474
|
+
...this._schemaName !== void 0 && { schema: this._schemaName },
|
|
6475
|
+
...meta?.queryName !== void 0 && { queryName: meta.queryName },
|
|
6476
|
+
...meta?.correlationId !== void 0 && {
|
|
6477
|
+
correlationId: meta.correlationId
|
|
6478
|
+
}
|
|
6479
|
+
};
|
|
6480
|
+
return {
|
|
6481
|
+
sql: compiled.sql,
|
|
6482
|
+
parameters: compiled.parameters,
|
|
6483
|
+
intent,
|
|
6484
|
+
meta: dumpMeta
|
|
6485
|
+
};
|
|
6486
|
+
}
|
|
6487
|
+
async all() {
|
|
6488
|
+
const adapter = this.adapter;
|
|
6489
|
+
if (!adapter) {
|
|
6271
6490
|
throw new Error(
|
|
6272
6491
|
"Cannot execute query: no adapter configured. Pass an adapter to createOrm() or use .toIntentIR() / .plan() for debugging."
|
|
6273
6492
|
);
|
|
6274
6493
|
}
|
|
6275
|
-
const
|
|
6276
|
-
|
|
6277
|
-
|
|
6494
|
+
const compiledIntent = this.compile();
|
|
6495
|
+
if (compiledIntent.kind === "mutation") {
|
|
6496
|
+
const hasReturning = (compiledIntent.intent.returning?.length ?? 0) > 0;
|
|
6497
|
+
return runMutationWithHooks({
|
|
6498
|
+
table: compiledIntent.intent.table,
|
|
6499
|
+
intent: compiledIntent.intent,
|
|
6500
|
+
hookStore: this.hookStore,
|
|
6501
|
+
onHookError: this.onHookError,
|
|
6502
|
+
schemaName: this._schemaName,
|
|
6503
|
+
inTransaction: this.inTransaction,
|
|
6504
|
+
prepare: () => {
|
|
6505
|
+
const compiled2 = adapter.compile(
|
|
6506
|
+
compiledIntent.bundle,
|
|
6507
|
+
this.mutationCompileOptions()
|
|
6508
|
+
);
|
|
6509
|
+
return {
|
|
6510
|
+
sql: compiled2.sql,
|
|
6511
|
+
parameters: compiled2.parameters,
|
|
6512
|
+
execute: () => adapter.execute(compiled2),
|
|
6513
|
+
getAfterMutationResult: (result) => hasReturning ? result : [],
|
|
6514
|
+
returnAfterMutationResult: hasReturning
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
});
|
|
6518
|
+
}
|
|
6519
|
+
const planReport = this.planInternal();
|
|
6520
|
+
const compiled = hasNqlBindings(compiledIntent.bundle) ? adapter.compile(
|
|
6521
|
+
compiledIntent.bundle,
|
|
6522
|
+
this.nqlBundleCompileOptions()
|
|
6523
|
+
) : adapter.compile(planReport);
|
|
6524
|
+
return adapter.execute(compiled);
|
|
6525
|
+
}
|
|
6526
|
+
async run() {
|
|
6527
|
+
await this.all();
|
|
6278
6528
|
}
|
|
6279
6529
|
async first() {
|
|
6280
6530
|
const rows = await this.all();
|
|
@@ -6661,8 +6911,152 @@ function hydrateJsonAggIncludes(results, planReport) {
|
|
|
6661
6911
|
}
|
|
6662
6912
|
}
|
|
6663
6913
|
|
|
6914
|
+
// src/dx/relation-paths.ts
|
|
6915
|
+
function nonEmptyString(value) {
|
|
6916
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
6917
|
+
}
|
|
6918
|
+
function includeNode(value) {
|
|
6919
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
6920
|
+
}
|
|
6921
|
+
function parseIntentPathIndexes(intentPath) {
|
|
6922
|
+
const indexes = [];
|
|
6923
|
+
const indexPattern = /include\[(\d+)\]/g;
|
|
6924
|
+
let execResult = indexPattern.exec(intentPath);
|
|
6925
|
+
while (execResult !== null) {
|
|
6926
|
+
const rawIndex = execResult[1];
|
|
6927
|
+
if (rawIndex === void 0) break;
|
|
6928
|
+
indexes.push(parseInt(rawIndex, 10));
|
|
6929
|
+
execResult = indexPattern.exec(intentPath);
|
|
6930
|
+
}
|
|
6931
|
+
return indexes;
|
|
6932
|
+
}
|
|
6933
|
+
function deriveRelationPathFromIntentPath(includes, intentPath, fallbackRelation) {
|
|
6934
|
+
if (includes && intentPath) {
|
|
6935
|
+
let current = includes;
|
|
6936
|
+
const path = [];
|
|
6937
|
+
for (const index of parseIntentPathIndexes(intentPath)) {
|
|
6938
|
+
const item = includeNode(current[index]);
|
|
6939
|
+
if (!item) break;
|
|
6940
|
+
const segment = nonEmptyString(item.via) ?? nonEmptyString(item.relation);
|
|
6941
|
+
if (!segment) break;
|
|
6942
|
+
path.push(segment);
|
|
6943
|
+
current = Array.isArray(item.include) ? item.include : [];
|
|
6944
|
+
}
|
|
6945
|
+
if (path.length > 0) return path.join(".");
|
|
6946
|
+
}
|
|
6947
|
+
return nonEmptyString(fallbackRelation);
|
|
6948
|
+
}
|
|
6949
|
+
function countDistinctRelationPathsByName(usages) {
|
|
6950
|
+
const pathsByRelationName = /* @__PURE__ */ new Map();
|
|
6951
|
+
for (const usage of usages) {
|
|
6952
|
+
const relationName = nonEmptyString(usage.relationName);
|
|
6953
|
+
if (!relationName) continue;
|
|
6954
|
+
const relationPath = nonEmptyString(usage.relationPath) ?? relationName;
|
|
6955
|
+
const paths = pathsByRelationName.get(relationName) ?? /* @__PURE__ */ new Set();
|
|
6956
|
+
paths.add(relationPath);
|
|
6957
|
+
pathsByRelationName.set(relationName, paths);
|
|
6958
|
+
}
|
|
6959
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6960
|
+
for (const [relationName, paths] of pathsByRelationName) {
|
|
6961
|
+
counts.set(relationName, paths.size);
|
|
6962
|
+
}
|
|
6963
|
+
return counts;
|
|
6964
|
+
}
|
|
6965
|
+
|
|
6664
6966
|
// src/dx/result-hydrator.ts
|
|
6665
6967
|
var COMPOSITE_KEY_SEP = "\0";
|
|
6968
|
+
function stringProp(source, key) {
|
|
6969
|
+
const value = source?.[key];
|
|
6970
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
6971
|
+
}
|
|
6972
|
+
function resolveJoinIncludePath(planReport, decision) {
|
|
6973
|
+
const context = decision.context;
|
|
6974
|
+
const decisionRecord = decision;
|
|
6975
|
+
const contextRecord = context;
|
|
6976
|
+
const explicitRelationPath = stringProp(decisionRecord, "relationPath") ?? stringProp(contextRecord, "relationPath");
|
|
6977
|
+
if (explicitRelationPath) return explicitRelationPath;
|
|
6978
|
+
const fallbackRelation = stringProp(contextRecord, "relation") ?? stringProp(contextRecord, "includeAlias") ?? stringProp(decisionRecord, "relationName");
|
|
6979
|
+
const intentPath = stringProp(contextRecord, "intentPath");
|
|
6980
|
+
return deriveRelationPathFromIntentPath(
|
|
6981
|
+
Array.isArray(planReport.intent?.include) ? planReport.intent.include : void 0,
|
|
6982
|
+
intentPath,
|
|
6983
|
+
fallbackRelation
|
|
6984
|
+
);
|
|
6985
|
+
}
|
|
6986
|
+
function lastRelationSegment(path) {
|
|
6987
|
+
const segments = path.split(".");
|
|
6988
|
+
return segments[segments.length - 1] ?? path;
|
|
6989
|
+
}
|
|
6990
|
+
function resolveJoinRelationName(decision, relationPath) {
|
|
6991
|
+
const context = decision.context;
|
|
6992
|
+
const decisionRecord = decision;
|
|
6993
|
+
return stringProp(context, "relation") ?? stringProp(context, "includeAlias") ?? stringProp(decisionRecord, "relationName") ?? lastRelationSegment(relationPath);
|
|
6994
|
+
}
|
|
6995
|
+
function collectJoinHydrationInfos(planReport) {
|
|
6996
|
+
const candidates = [];
|
|
6997
|
+
for (const decision of planReport.decisions) {
|
|
6998
|
+
if (decision.type !== "include-strategy" || decision.choice !== "join") {
|
|
6999
|
+
continue;
|
|
7000
|
+
}
|
|
7001
|
+
const relationPath = resolveJoinIncludePath(planReport, decision);
|
|
7002
|
+
if (!relationPath) continue;
|
|
7003
|
+
const relationName = resolveJoinRelationName(decision, relationPath);
|
|
7004
|
+
const decisionRecord = decision;
|
|
7005
|
+
const contextRecord = decision.context;
|
|
7006
|
+
const hydrationPrefix = stringProp(decisionRecord, "hydrationPrefix") ?? stringProp(contextRecord, "hydrationPrefix");
|
|
7007
|
+
candidates.push({
|
|
7008
|
+
relationName,
|
|
7009
|
+
relationPath,
|
|
7010
|
+
...hydrationPrefix && { hydrationPrefix }
|
|
7011
|
+
});
|
|
7012
|
+
}
|
|
7013
|
+
const pathCountsByRelationName = countDistinctRelationPathsByName(candidates);
|
|
7014
|
+
const infosByPath = /* @__PURE__ */ new Map();
|
|
7015
|
+
for (const candidate of candidates) {
|
|
7016
|
+
if (infosByPath.has(candidate.relationPath)) continue;
|
|
7017
|
+
const usesFullPath = (pathCountsByRelationName.get(candidate.relationName) ?? 0) > 1;
|
|
7018
|
+
const keyPrefix = candidate.hydrationPrefix ?? (usesFullPath ? candidate.relationPath : candidate.relationName);
|
|
7019
|
+
if (!keyPrefix) continue;
|
|
7020
|
+
infosByPath.set(candidate.relationPath, {
|
|
7021
|
+
relationPath: candidate.relationPath,
|
|
7022
|
+
segments: candidate.relationPath.split("."),
|
|
7023
|
+
keyPrefix,
|
|
7024
|
+
keyPrefixWithDot: `${keyPrefix}.`
|
|
7025
|
+
});
|
|
7026
|
+
}
|
|
7027
|
+
return [...infosByPath.values()];
|
|
7028
|
+
}
|
|
7029
|
+
function findLongestJoinInfo(key, infos) {
|
|
7030
|
+
let best;
|
|
7031
|
+
for (const info of infos) {
|
|
7032
|
+
if (!key.startsWith(info.keyPrefixWithDot)) continue;
|
|
7033
|
+
if (!best || info.keyPrefixWithDot.length > best.keyPrefixWithDot.length) {
|
|
7034
|
+
best = info;
|
|
7035
|
+
}
|
|
7036
|
+
}
|
|
7037
|
+
return best;
|
|
7038
|
+
}
|
|
7039
|
+
function assignNestedValue(target, path, value) {
|
|
7040
|
+
let cursor = target;
|
|
7041
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
7042
|
+
const segment = path[i];
|
|
7043
|
+
if (!segment) return;
|
|
7044
|
+
const existing = cursor[segment];
|
|
7045
|
+
if (existing === null) {
|
|
7046
|
+
return;
|
|
7047
|
+
} else if (typeof existing !== "object" || Array.isArray(existing)) {
|
|
7048
|
+
cursor[segment] = {};
|
|
7049
|
+
}
|
|
7050
|
+
cursor = cursor[segment];
|
|
7051
|
+
}
|
|
7052
|
+
const leaf = path[path.length - 1];
|
|
7053
|
+
if (leaf) cursor[leaf] = value;
|
|
7054
|
+
}
|
|
7055
|
+
function assignColumnValue(target, columnPath, value) {
|
|
7056
|
+
const segments = columnPath.split(".").filter(Boolean);
|
|
7057
|
+
if (segments.length === 0) return;
|
|
7058
|
+
assignNestedValue(target, segments, value);
|
|
7059
|
+
}
|
|
6666
7060
|
var ResultHydrator = class {
|
|
6667
7061
|
model;
|
|
6668
7062
|
from;
|
|
@@ -6739,51 +7133,42 @@ var ResultHydrator = class {
|
|
|
6739
7133
|
* E2E-004: JOIN strategy for to-one relations returns columns like "author.id", "author.name".
|
|
6740
7134
|
*/
|
|
6741
7135
|
hydrateJoinIncludes(results, planReport) {
|
|
6742
|
-
const
|
|
6743
|
-
|
|
7136
|
+
const joinInfos = collectJoinHydrationInfos(planReport);
|
|
7137
|
+
if (joinInfos.length === 0) return;
|
|
7138
|
+
const infosByDepth = [...joinInfos].sort(
|
|
7139
|
+
(a, b) => a.segments.length - b.segments.length
|
|
6744
7140
|
);
|
|
6745
|
-
if (joinDecisions.length === 0) {
|
|
6746
|
-
return;
|
|
6747
|
-
}
|
|
6748
|
-
const joinInfos = joinDecisions.map((d) => d.context?.relation).filter((r) => typeof r === "string").map((relation) => ({ relation, prefix: `${relation}.` }));
|
|
6749
|
-
if (joinInfos.length === 0) {
|
|
6750
|
-
return;
|
|
6751
|
-
}
|
|
6752
|
-
const joinInfosWithCache = joinInfos.map((info) => ({
|
|
6753
|
-
...info,
|
|
6754
|
-
cachedKeys: null
|
|
6755
|
-
}));
|
|
6756
|
-
if (results.length > 0) {
|
|
6757
|
-
const firstRow = results.find(
|
|
6758
|
-
(r) => typeof r === "object" && r !== null
|
|
6759
|
-
);
|
|
6760
|
-
if (firstRow) {
|
|
6761
|
-
const firstRecord = firstRow;
|
|
6762
|
-
const allKeys = Object.keys(firstRecord);
|
|
6763
|
-
for (const info of joinInfosWithCache) {
|
|
6764
|
-
info.cachedKeys = allKeys.filter((k) => k.startsWith(info.prefix));
|
|
6765
|
-
}
|
|
6766
|
-
}
|
|
6767
|
-
}
|
|
6768
7141
|
for (const row of results) {
|
|
6769
7142
|
if (typeof row !== "object" || row === null) {
|
|
6770
7143
|
continue;
|
|
6771
7144
|
}
|
|
6772
7145
|
const record2 = row;
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
delete record2[key];
|
|
6783
|
-
if (val !== null) allNull = false;
|
|
6784
|
-
}
|
|
7146
|
+
const valuesByPath = /* @__PURE__ */ new Map();
|
|
7147
|
+
for (const key of Object.keys(record2)) {
|
|
7148
|
+
const info = findLongestJoinInfo(key, joinInfos);
|
|
7149
|
+
if (!info) continue;
|
|
7150
|
+
const value = record2[key];
|
|
7151
|
+
let entry = valuesByPath.get(info.relationPath);
|
|
7152
|
+
if (!entry) {
|
|
7153
|
+
entry = { values: {}, allNull: true };
|
|
7154
|
+
valuesByPath.set(info.relationPath, entry);
|
|
6785
7155
|
}
|
|
6786
|
-
|
|
7156
|
+
assignColumnValue(
|
|
7157
|
+
entry.values,
|
|
7158
|
+
key.slice(info.keyPrefixWithDot.length),
|
|
7159
|
+
value
|
|
7160
|
+
);
|
|
7161
|
+
if (value !== null && value !== void 0) entry.allNull = false;
|
|
7162
|
+
delete record2[key];
|
|
7163
|
+
}
|
|
7164
|
+
for (const info of infosByDepth) {
|
|
7165
|
+
const entry = valuesByPath.get(info.relationPath);
|
|
7166
|
+
if (!entry) continue;
|
|
7167
|
+
assignNestedValue(
|
|
7168
|
+
record2,
|
|
7169
|
+
info.segments,
|
|
7170
|
+
entry.allNull ? null : entry.values
|
|
7171
|
+
);
|
|
6787
7172
|
}
|
|
6788
7173
|
}
|
|
6789
7174
|
}
|
|
@@ -8914,7 +9299,10 @@ function createOrmInstance(model, strictMode, relationHints, adapter, schemaName
|
|
|
8914
9299
|
schemaDefinition,
|
|
8915
9300
|
model,
|
|
8916
9301
|
adapter,
|
|
8917
|
-
schemaName
|
|
9302
|
+
schemaName,
|
|
9303
|
+
hookStore,
|
|
9304
|
+
onHookError,
|
|
9305
|
+
inTransaction
|
|
8918
9306
|
);
|
|
8919
9307
|
const mutationOpts = {
|
|
8920
9308
|
model,
|
|
@@ -11474,6 +11862,7 @@ export {
|
|
|
11474
11862
|
composeBeforeMutationHooks,
|
|
11475
11863
|
composeBeforeQueryHooks,
|
|
11476
11864
|
composeOnErrorHooks,
|
|
11865
|
+
countDistinctRelationPathsByName,
|
|
11477
11866
|
createDialectCapabilities,
|
|
11478
11867
|
createHookManager,
|
|
11479
11868
|
createNqlTag,
|
|
@@ -11486,6 +11875,7 @@ export {
|
|
|
11486
11875
|
defineModel,
|
|
11487
11876
|
defineSchema,
|
|
11488
11877
|
denseRank,
|
|
11878
|
+
deriveRelationPathFromIntentPath,
|
|
11489
11879
|
detectForeignKeys,
|
|
11490
11880
|
detectManyToMany,
|
|
11491
11881
|
distinct,
|
|
@@ -11588,6 +11978,7 @@ export {
|
|
|
11588
11978
|
normalizeSchema,
|
|
11589
11979
|
not,
|
|
11590
11980
|
notExists,
|
|
11981
|
+
nqlRaw,
|
|
11591
11982
|
objectToWhereIntent,
|
|
11592
11983
|
op,
|
|
11593
11984
|
or,
|