@dbsp/core 1.0.4 → 1.1.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 +79 -12
- package/dist/index.js +326 -85
- 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,
|
|
@@ -6126,53 +6133,131 @@ function negotiateFeatures(model, capabilities, behavior = "warning", checkers =
|
|
|
6126
6133
|
}
|
|
6127
6134
|
|
|
6128
6135
|
// src/dx/nql.ts
|
|
6129
|
-
import {
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6136
|
+
import {
|
|
6137
|
+
NqlLexer,
|
|
6138
|
+
compile as nqlCompile
|
|
6139
|
+
} from "@dbsp/nql";
|
|
6140
|
+
import { NQL_INTERNAL_COMPILER_OPTIONS } from "@dbsp/types/internal";
|
|
6141
|
+
var NQL_RAW_FRAGMENT = /* @__PURE__ */ Symbol("NqlRawFragment");
|
|
6142
|
+
function nqlRaw(fragment) {
|
|
6143
|
+
if (typeof fragment !== "string") {
|
|
6144
|
+
throw new TypeError("nqlRaw() expects a string fragment");
|
|
6145
|
+
}
|
|
6146
|
+
const raw2 = { fragment };
|
|
6147
|
+
Object.defineProperty(raw2, NQL_RAW_FRAGMENT, {
|
|
6148
|
+
value: true,
|
|
6149
|
+
enumerable: false
|
|
6150
|
+
});
|
|
6151
|
+
return Object.freeze(raw2);
|
|
6152
|
+
}
|
|
6153
|
+
function isNqlRawFragment(value) {
|
|
6154
|
+
if (typeof value !== "object" || value === null) {
|
|
6155
|
+
return false;
|
|
6133
6156
|
}
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6157
|
+
if (!Object.hasOwn(value, NQL_RAW_FRAGMENT)) {
|
|
6158
|
+
return false;
|
|
6159
|
+
}
|
|
6160
|
+
return value[NQL_RAW_FRAGMENT] === true;
|
|
6161
|
+
}
|
|
6162
|
+
function isInsideGeneratedRange(start, end, generatedRanges) {
|
|
6163
|
+
return generatedRanges.some(
|
|
6164
|
+
(range) => start >= range.start && end <= range.end
|
|
6165
|
+
);
|
|
6166
|
+
}
|
|
6167
|
+
function findOverlappingGeneratedRange(start, end, generatedRanges) {
|
|
6168
|
+
return generatedRanges.find(
|
|
6169
|
+
(range) => start < range.end && end > range.start
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
function findExactGeneratedRange(name, start, end, generatedRanges) {
|
|
6173
|
+
return generatedRanges.find(
|
|
6174
|
+
(range) => range.name === name && range.start === start && range.end === end
|
|
6175
|
+
);
|
|
6176
|
+
}
|
|
6177
|
+
function generatedParamLexError(name) {
|
|
6178
|
+
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.`;
|
|
6179
|
+
}
|
|
6180
|
+
function findInternalParamSourceError(query, generatedRanges) {
|
|
6181
|
+
const lexResult = NqlLexer.tokenize(query);
|
|
6182
|
+
const generatedTokenCounts = new Map(
|
|
6183
|
+
generatedRanges.map((range) => [range.name, 0])
|
|
6184
|
+
);
|
|
6185
|
+
for (const token of lexResult.tokens) {
|
|
6186
|
+
if (token.tokenType.name !== "NamedParam") {
|
|
6187
|
+
continue;
|
|
6150
6188
|
}
|
|
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}'`;
|
|
6189
|
+
const name = token.image.slice(1);
|
|
6190
|
+
if (!name.startsWith("__p")) {
|
|
6191
|
+
continue;
|
|
6159
6192
|
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6193
|
+
const start = token.startOffset;
|
|
6194
|
+
const end = (token.endOffset ?? start + token.image.length - 1) + 1;
|
|
6195
|
+
const exactGeneratedRange = findExactGeneratedRange(
|
|
6196
|
+
name,
|
|
6197
|
+
start,
|
|
6198
|
+
end,
|
|
6199
|
+
generatedRanges
|
|
6200
|
+
);
|
|
6201
|
+
if (exactGeneratedRange) {
|
|
6202
|
+
generatedTokenCounts.set(
|
|
6203
|
+
exactGeneratedRange.name,
|
|
6204
|
+
(generatedTokenCounts.get(exactGeneratedRange.name) ?? 0) + 1
|
|
6164
6205
|
);
|
|
6206
|
+
continue;
|
|
6207
|
+
}
|
|
6208
|
+
const overlappingGeneratedRange = findOverlappingGeneratedRange(
|
|
6209
|
+
start,
|
|
6210
|
+
end,
|
|
6211
|
+
generatedRanges
|
|
6212
|
+
);
|
|
6213
|
+
if (overlappingGeneratedRange) {
|
|
6214
|
+
return generatedParamLexError(overlappingGeneratedRange.name);
|
|
6165
6215
|
}
|
|
6216
|
+
if (!isInsideGeneratedRange(start, end, generatedRanges)) {
|
|
6217
|
+
return `Reserved NQL parameter namespace "__p" cannot be referenced by user source (${token.image}).`;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
for (const range of generatedRanges) {
|
|
6221
|
+
if (generatedTokenCounts.get(range.name) !== 1) {
|
|
6222
|
+
return generatedParamLexError(range.name);
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
return void 0;
|
|
6226
|
+
}
|
|
6227
|
+
function assembleNqlTemplate(strings, values) {
|
|
6228
|
+
const params = /* @__PURE__ */ Object.create(null);
|
|
6229
|
+
const generatedRanges = [];
|
|
6230
|
+
let query = strings[0] ?? "";
|
|
6231
|
+
let boundIndex = 0;
|
|
6232
|
+
for (let i = 0; i < values.length; i++) {
|
|
6233
|
+
const value = values[i];
|
|
6234
|
+
if (isNqlRawFragment(value)) {
|
|
6235
|
+
query += value.fragment;
|
|
6236
|
+
} else {
|
|
6237
|
+
const name = `__p${boundIndex++}`;
|
|
6238
|
+
const placeholder = `:${name}`;
|
|
6239
|
+
const start = query.length;
|
|
6240
|
+
query += placeholder;
|
|
6241
|
+
generatedRanges.push({ name, start, end: start + placeholder.length });
|
|
6242
|
+
params[name] = value;
|
|
6243
|
+
}
|
|
6244
|
+
query += strings[i + 1] ?? "";
|
|
6166
6245
|
}
|
|
6246
|
+
return {
|
|
6247
|
+
query,
|
|
6248
|
+
params,
|
|
6249
|
+
hasBoundParams: boundIndex > 0,
|
|
6250
|
+
sourceError: findInternalParamSourceError(query, generatedRanges)
|
|
6251
|
+
};
|
|
6167
6252
|
}
|
|
6168
6253
|
function createNqlTag(schemaDefinition, model, adapter, schemaName) {
|
|
6169
6254
|
return function nql(strings, ...values) {
|
|
6170
|
-
|
|
6171
|
-
for (let i = 0; i < values.length; i++) {
|
|
6172
|
-
query += toNqlLiteral(values[i], i) + (strings[i + 1] ?? "");
|
|
6173
|
-
}
|
|
6255
|
+
const assembled = assembleNqlTemplate(strings, values);
|
|
6174
6256
|
return new NqlBuilderImpl(
|
|
6175
|
-
query,
|
|
6257
|
+
assembled.query,
|
|
6258
|
+
assembled.params,
|
|
6259
|
+
assembled.hasBoundParams,
|
|
6260
|
+
assembled.sourceError,
|
|
6176
6261
|
schemaDefinition,
|
|
6177
6262
|
model,
|
|
6178
6263
|
adapter,
|
|
@@ -6183,13 +6268,19 @@ function createNqlTag(schemaDefinition, model, adapter, schemaName) {
|
|
|
6183
6268
|
var NqlBuilderImpl = class {
|
|
6184
6269
|
_intent;
|
|
6185
6270
|
query;
|
|
6271
|
+
params;
|
|
6272
|
+
hasBoundParams;
|
|
6273
|
+
sourceError;
|
|
6186
6274
|
schemaDefinition;
|
|
6187
6275
|
model;
|
|
6188
6276
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: Reserved for future schema-scoping support
|
|
6189
6277
|
_schemaName;
|
|
6190
6278
|
adapter;
|
|
6191
|
-
constructor(query, schemaDefinition, model, adapter, schemaName) {
|
|
6279
|
+
constructor(query, params, hasBoundParams, sourceError, schemaDefinition, model, adapter, schemaName) {
|
|
6192
6280
|
this.query = query;
|
|
6281
|
+
this.params = params;
|
|
6282
|
+
this.hasBoundParams = hasBoundParams;
|
|
6283
|
+
this.sourceError = sourceError;
|
|
6193
6284
|
this.schemaDefinition = schemaDefinition;
|
|
6194
6285
|
this.model = model;
|
|
6195
6286
|
this.adapter = adapter;
|
|
@@ -6199,7 +6290,14 @@ var NqlBuilderImpl = class {
|
|
|
6199
6290
|
if (this._intent) {
|
|
6200
6291
|
return this._intent;
|
|
6201
6292
|
}
|
|
6202
|
-
|
|
6293
|
+
if (this.sourceError !== void 0) {
|
|
6294
|
+
throw new Error(`NQL compilation failed: ${this.sourceError}`);
|
|
6295
|
+
}
|
|
6296
|
+
const compilerOptions = {
|
|
6297
|
+
...extractPseudoColumnKeywords(this.model) ?? {},
|
|
6298
|
+
params: this.params,
|
|
6299
|
+
[NQL_INTERNAL_COMPILER_OPTIONS]: { allowInternalParams: true }
|
|
6300
|
+
};
|
|
6203
6301
|
const result = nqlCompile(
|
|
6204
6302
|
this.query,
|
|
6205
6303
|
this.schemaDefinition,
|
|
@@ -6208,7 +6306,8 @@ var NqlBuilderImpl = class {
|
|
|
6208
6306
|
);
|
|
6209
6307
|
if (!result.success) {
|
|
6210
6308
|
const errors = result.errors?.map((e) => e.message).join(", ") ?? "Unknown error";
|
|
6211
|
-
|
|
6309
|
+
const rawHint = this.hasBoundParams ? " If an interpolation was intended as NQL structure, wrap a trusted fragment with nqlRaw()." : "";
|
|
6310
|
+
throw new Error(`NQL compilation failed: ${errors}${rawHint}`);
|
|
6212
6311
|
}
|
|
6213
6312
|
if (result.ast?.mutation && !result.ast?.query) {
|
|
6214
6313
|
throw new Error(
|
|
@@ -6224,12 +6323,15 @@ var NqlBuilderImpl = class {
|
|
|
6224
6323
|
toIntentIR() {
|
|
6225
6324
|
return this.compile();
|
|
6226
6325
|
}
|
|
6227
|
-
|
|
6326
|
+
planInternal() {
|
|
6228
6327
|
const intent = this.compile();
|
|
6229
6328
|
return plan(intent, this.model);
|
|
6230
6329
|
}
|
|
6330
|
+
plan() {
|
|
6331
|
+
return this.planInternal();
|
|
6332
|
+
}
|
|
6231
6333
|
dump(meta) {
|
|
6232
|
-
const planReport = this.
|
|
6334
|
+
const planReport = this.planInternal();
|
|
6233
6335
|
if (!this.adapter) {
|
|
6234
6336
|
return {
|
|
6235
6337
|
plan: planReport,
|
|
@@ -6272,7 +6374,7 @@ var NqlBuilderImpl = class {
|
|
|
6272
6374
|
"Cannot execute query: no adapter configured. Pass an adapter to createOrm() or use .toIntentIR() / .plan() for debugging."
|
|
6273
6375
|
);
|
|
6274
6376
|
}
|
|
6275
|
-
const planReport = this.
|
|
6377
|
+
const planReport = this.planInternal();
|
|
6276
6378
|
const compiled = this.adapter.compile(planReport);
|
|
6277
6379
|
return this.adapter.execute(compiled);
|
|
6278
6380
|
}
|
|
@@ -6661,8 +6763,152 @@ function hydrateJsonAggIncludes(results, planReport) {
|
|
|
6661
6763
|
}
|
|
6662
6764
|
}
|
|
6663
6765
|
|
|
6766
|
+
// src/dx/relation-paths.ts
|
|
6767
|
+
function nonEmptyString(value) {
|
|
6768
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
6769
|
+
}
|
|
6770
|
+
function includeNode(value) {
|
|
6771
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
6772
|
+
}
|
|
6773
|
+
function parseIntentPathIndexes(intentPath) {
|
|
6774
|
+
const indexes = [];
|
|
6775
|
+
const indexPattern = /include\[(\d+)\]/g;
|
|
6776
|
+
let execResult = indexPattern.exec(intentPath);
|
|
6777
|
+
while (execResult !== null) {
|
|
6778
|
+
const rawIndex = execResult[1];
|
|
6779
|
+
if (rawIndex === void 0) break;
|
|
6780
|
+
indexes.push(parseInt(rawIndex, 10));
|
|
6781
|
+
execResult = indexPattern.exec(intentPath);
|
|
6782
|
+
}
|
|
6783
|
+
return indexes;
|
|
6784
|
+
}
|
|
6785
|
+
function deriveRelationPathFromIntentPath(includes, intentPath, fallbackRelation) {
|
|
6786
|
+
if (includes && intentPath) {
|
|
6787
|
+
let current = includes;
|
|
6788
|
+
const path = [];
|
|
6789
|
+
for (const index of parseIntentPathIndexes(intentPath)) {
|
|
6790
|
+
const item = includeNode(current[index]);
|
|
6791
|
+
if (!item) break;
|
|
6792
|
+
const segment = nonEmptyString(item.via) ?? nonEmptyString(item.relation);
|
|
6793
|
+
if (!segment) break;
|
|
6794
|
+
path.push(segment);
|
|
6795
|
+
current = Array.isArray(item.include) ? item.include : [];
|
|
6796
|
+
}
|
|
6797
|
+
if (path.length > 0) return path.join(".");
|
|
6798
|
+
}
|
|
6799
|
+
return nonEmptyString(fallbackRelation);
|
|
6800
|
+
}
|
|
6801
|
+
function countDistinctRelationPathsByName(usages) {
|
|
6802
|
+
const pathsByRelationName = /* @__PURE__ */ new Map();
|
|
6803
|
+
for (const usage of usages) {
|
|
6804
|
+
const relationName = nonEmptyString(usage.relationName);
|
|
6805
|
+
if (!relationName) continue;
|
|
6806
|
+
const relationPath = nonEmptyString(usage.relationPath) ?? relationName;
|
|
6807
|
+
const paths = pathsByRelationName.get(relationName) ?? /* @__PURE__ */ new Set();
|
|
6808
|
+
paths.add(relationPath);
|
|
6809
|
+
pathsByRelationName.set(relationName, paths);
|
|
6810
|
+
}
|
|
6811
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6812
|
+
for (const [relationName, paths] of pathsByRelationName) {
|
|
6813
|
+
counts.set(relationName, paths.size);
|
|
6814
|
+
}
|
|
6815
|
+
return counts;
|
|
6816
|
+
}
|
|
6817
|
+
|
|
6664
6818
|
// src/dx/result-hydrator.ts
|
|
6665
6819
|
var COMPOSITE_KEY_SEP = "\0";
|
|
6820
|
+
function stringProp(source, key) {
|
|
6821
|
+
const value = source?.[key];
|
|
6822
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
6823
|
+
}
|
|
6824
|
+
function resolveJoinIncludePath(planReport, decision) {
|
|
6825
|
+
const context = decision.context;
|
|
6826
|
+
const decisionRecord = decision;
|
|
6827
|
+
const contextRecord = context;
|
|
6828
|
+
const explicitRelationPath = stringProp(decisionRecord, "relationPath") ?? stringProp(contextRecord, "relationPath");
|
|
6829
|
+
if (explicitRelationPath) return explicitRelationPath;
|
|
6830
|
+
const fallbackRelation = stringProp(contextRecord, "relation") ?? stringProp(contextRecord, "includeAlias") ?? stringProp(decisionRecord, "relationName");
|
|
6831
|
+
const intentPath = stringProp(contextRecord, "intentPath");
|
|
6832
|
+
return deriveRelationPathFromIntentPath(
|
|
6833
|
+
Array.isArray(planReport.intent?.include) ? planReport.intent.include : void 0,
|
|
6834
|
+
intentPath,
|
|
6835
|
+
fallbackRelation
|
|
6836
|
+
);
|
|
6837
|
+
}
|
|
6838
|
+
function lastRelationSegment(path) {
|
|
6839
|
+
const segments = path.split(".");
|
|
6840
|
+
return segments[segments.length - 1] ?? path;
|
|
6841
|
+
}
|
|
6842
|
+
function resolveJoinRelationName(decision, relationPath) {
|
|
6843
|
+
const context = decision.context;
|
|
6844
|
+
const decisionRecord = decision;
|
|
6845
|
+
return stringProp(context, "relation") ?? stringProp(context, "includeAlias") ?? stringProp(decisionRecord, "relationName") ?? lastRelationSegment(relationPath);
|
|
6846
|
+
}
|
|
6847
|
+
function collectJoinHydrationInfos(planReport) {
|
|
6848
|
+
const candidates = [];
|
|
6849
|
+
for (const decision of planReport.decisions) {
|
|
6850
|
+
if (decision.type !== "include-strategy" || decision.choice !== "join") {
|
|
6851
|
+
continue;
|
|
6852
|
+
}
|
|
6853
|
+
const relationPath = resolveJoinIncludePath(planReport, decision);
|
|
6854
|
+
if (!relationPath) continue;
|
|
6855
|
+
const relationName = resolveJoinRelationName(decision, relationPath);
|
|
6856
|
+
const decisionRecord = decision;
|
|
6857
|
+
const contextRecord = decision.context;
|
|
6858
|
+
const hydrationPrefix = stringProp(decisionRecord, "hydrationPrefix") ?? stringProp(contextRecord, "hydrationPrefix");
|
|
6859
|
+
candidates.push({
|
|
6860
|
+
relationName,
|
|
6861
|
+
relationPath,
|
|
6862
|
+
...hydrationPrefix && { hydrationPrefix }
|
|
6863
|
+
});
|
|
6864
|
+
}
|
|
6865
|
+
const pathCountsByRelationName = countDistinctRelationPathsByName(candidates);
|
|
6866
|
+
const infosByPath = /* @__PURE__ */ new Map();
|
|
6867
|
+
for (const candidate of candidates) {
|
|
6868
|
+
if (infosByPath.has(candidate.relationPath)) continue;
|
|
6869
|
+
const usesFullPath = (pathCountsByRelationName.get(candidate.relationName) ?? 0) > 1;
|
|
6870
|
+
const keyPrefix = candidate.hydrationPrefix ?? (usesFullPath ? candidate.relationPath : candidate.relationName);
|
|
6871
|
+
if (!keyPrefix) continue;
|
|
6872
|
+
infosByPath.set(candidate.relationPath, {
|
|
6873
|
+
relationPath: candidate.relationPath,
|
|
6874
|
+
segments: candidate.relationPath.split("."),
|
|
6875
|
+
keyPrefix,
|
|
6876
|
+
keyPrefixWithDot: `${keyPrefix}.`
|
|
6877
|
+
});
|
|
6878
|
+
}
|
|
6879
|
+
return [...infosByPath.values()];
|
|
6880
|
+
}
|
|
6881
|
+
function findLongestJoinInfo(key, infos) {
|
|
6882
|
+
let best;
|
|
6883
|
+
for (const info of infos) {
|
|
6884
|
+
if (!key.startsWith(info.keyPrefixWithDot)) continue;
|
|
6885
|
+
if (!best || info.keyPrefixWithDot.length > best.keyPrefixWithDot.length) {
|
|
6886
|
+
best = info;
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
return best;
|
|
6890
|
+
}
|
|
6891
|
+
function assignNestedValue(target, path, value) {
|
|
6892
|
+
let cursor = target;
|
|
6893
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
6894
|
+
const segment = path[i];
|
|
6895
|
+
if (!segment) return;
|
|
6896
|
+
const existing = cursor[segment];
|
|
6897
|
+
if (existing === null) {
|
|
6898
|
+
return;
|
|
6899
|
+
} else if (typeof existing !== "object" || Array.isArray(existing)) {
|
|
6900
|
+
cursor[segment] = {};
|
|
6901
|
+
}
|
|
6902
|
+
cursor = cursor[segment];
|
|
6903
|
+
}
|
|
6904
|
+
const leaf = path[path.length - 1];
|
|
6905
|
+
if (leaf) cursor[leaf] = value;
|
|
6906
|
+
}
|
|
6907
|
+
function assignColumnValue(target, columnPath, value) {
|
|
6908
|
+
const segments = columnPath.split(".").filter(Boolean);
|
|
6909
|
+
if (segments.length === 0) return;
|
|
6910
|
+
assignNestedValue(target, segments, value);
|
|
6911
|
+
}
|
|
6666
6912
|
var ResultHydrator = class {
|
|
6667
6913
|
model;
|
|
6668
6914
|
from;
|
|
@@ -6739,51 +6985,42 @@ var ResultHydrator = class {
|
|
|
6739
6985
|
* E2E-004: JOIN strategy for to-one relations returns columns like "author.id", "author.name".
|
|
6740
6986
|
*/
|
|
6741
6987
|
hydrateJoinIncludes(results, planReport) {
|
|
6742
|
-
const
|
|
6743
|
-
|
|
6988
|
+
const joinInfos = collectJoinHydrationInfos(planReport);
|
|
6989
|
+
if (joinInfos.length === 0) return;
|
|
6990
|
+
const infosByDepth = [...joinInfos].sort(
|
|
6991
|
+
(a, b) => a.segments.length - b.segments.length
|
|
6744
6992
|
);
|
|
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
6993
|
for (const row of results) {
|
|
6769
6994
|
if (typeof row !== "object" || row === null) {
|
|
6770
6995
|
continue;
|
|
6771
6996
|
}
|
|
6772
6997
|
const record2 = row;
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
delete record2[key];
|
|
6783
|
-
if (val !== null) allNull = false;
|
|
6784
|
-
}
|
|
6998
|
+
const valuesByPath = /* @__PURE__ */ new Map();
|
|
6999
|
+
for (const key of Object.keys(record2)) {
|
|
7000
|
+
const info = findLongestJoinInfo(key, joinInfos);
|
|
7001
|
+
if (!info) continue;
|
|
7002
|
+
const value = record2[key];
|
|
7003
|
+
let entry = valuesByPath.get(info.relationPath);
|
|
7004
|
+
if (!entry) {
|
|
7005
|
+
entry = { values: {}, allNull: true };
|
|
7006
|
+
valuesByPath.set(info.relationPath, entry);
|
|
6785
7007
|
}
|
|
6786
|
-
|
|
7008
|
+
assignColumnValue(
|
|
7009
|
+
entry.values,
|
|
7010
|
+
key.slice(info.keyPrefixWithDot.length),
|
|
7011
|
+
value
|
|
7012
|
+
);
|
|
7013
|
+
if (value !== null && value !== void 0) entry.allNull = false;
|
|
7014
|
+
delete record2[key];
|
|
7015
|
+
}
|
|
7016
|
+
for (const info of infosByDepth) {
|
|
7017
|
+
const entry = valuesByPath.get(info.relationPath);
|
|
7018
|
+
if (!entry) continue;
|
|
7019
|
+
assignNestedValue(
|
|
7020
|
+
record2,
|
|
7021
|
+
info.segments,
|
|
7022
|
+
entry.allNull ? null : entry.values
|
|
7023
|
+
);
|
|
6787
7024
|
}
|
|
6788
7025
|
}
|
|
6789
7026
|
}
|
|
@@ -11474,6 +11711,7 @@ export {
|
|
|
11474
11711
|
composeBeforeMutationHooks,
|
|
11475
11712
|
composeBeforeQueryHooks,
|
|
11476
11713
|
composeOnErrorHooks,
|
|
11714
|
+
countDistinctRelationPathsByName,
|
|
11477
11715
|
createDialectCapabilities,
|
|
11478
11716
|
createHookManager,
|
|
11479
11717
|
createNqlTag,
|
|
@@ -11486,6 +11724,7 @@ export {
|
|
|
11486
11724
|
defineModel,
|
|
11487
11725
|
defineSchema,
|
|
11488
11726
|
denseRank,
|
|
11727
|
+
deriveRelationPathFromIntentPath,
|
|
11489
11728
|
detectForeignKeys,
|
|
11490
11729
|
detectManyToMany,
|
|
11491
11730
|
distinct,
|
|
@@ -11588,6 +11827,7 @@ export {
|
|
|
11588
11827
|
normalizeSchema,
|
|
11589
11828
|
not,
|
|
11590
11829
|
notExists,
|
|
11830
|
+
nqlRaw,
|
|
11591
11831
|
objectToWhereIntent,
|
|
11592
11832
|
op,
|
|
11593
11833
|
or,
|
|
@@ -11642,6 +11882,7 @@ export {
|
|
|
11642
11882
|
validateAssertionBlocks,
|
|
11643
11883
|
validateRecursiveInclude,
|
|
11644
11884
|
validateRecursiveShape,
|
|
11885
|
+
validateTypeName,
|
|
11645
11886
|
wAvg,
|
|
11646
11887
|
wCount,
|
|
11647
11888
|
wMax,
|