@farming-labs/orm-sql 0.0.10 → 0.0.12
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.cjs +300 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +301 -30
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,11 +3,13 @@ import { randomUUID } from "crypto";
|
|
|
3
3
|
import {
|
|
4
4
|
createManifest,
|
|
5
5
|
mergeUniqueLookupCreateData,
|
|
6
|
+
isOperatorFilterObject,
|
|
6
7
|
requireUniqueLookup,
|
|
7
8
|
resolveRowIdentityLookup,
|
|
8
9
|
toUniqueLookupWhere,
|
|
9
10
|
validateUniqueLookupUpdateData
|
|
10
11
|
} from "@farming-labs/orm";
|
|
12
|
+
var nativeNodeIdentity = /* @__PURE__ */ Symbol("nativeNodeIdentity");
|
|
11
13
|
var manifestCache = /* @__PURE__ */ new WeakMap();
|
|
12
14
|
function getManifest(schema) {
|
|
13
15
|
const cached = manifestCache.get(schema);
|
|
@@ -56,6 +58,9 @@ function encodeValue(field, dialect, value) {
|
|
|
56
58
|
if (dialect === "postgres") return Boolean(value);
|
|
57
59
|
return value ? 1 : 0;
|
|
58
60
|
}
|
|
61
|
+
if (field.kind === "integer") {
|
|
62
|
+
return Number(value);
|
|
63
|
+
}
|
|
59
64
|
if (field.kind === "datetime") {
|
|
60
65
|
if (value instanceof Date) {
|
|
61
66
|
if (dialect === "mysql") {
|
|
@@ -65,6 +70,12 @@ function encodeValue(field, dialect, value) {
|
|
|
65
70
|
}
|
|
66
71
|
return value;
|
|
67
72
|
}
|
|
73
|
+
if (field.kind === "json") {
|
|
74
|
+
if (dialect === "postgres") {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
}
|
|
68
79
|
return value;
|
|
69
80
|
}
|
|
70
81
|
function normalizeNaiveSqlDate(value) {
|
|
@@ -108,6 +119,19 @@ function decodeValue(field, dialect, value) {
|
|
|
108
119
|
}
|
|
109
120
|
return new Date(String(value));
|
|
110
121
|
}
|
|
122
|
+
if (field.kind === "integer") {
|
|
123
|
+
return typeof value === "number" ? value : Number(value);
|
|
124
|
+
}
|
|
125
|
+
if (field.kind === "json") {
|
|
126
|
+
if (typeof value === "string") {
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(value);
|
|
129
|
+
} catch {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
111
135
|
return value;
|
|
112
136
|
}
|
|
113
137
|
function decodeRow(model, dialect, row) {
|
|
@@ -125,39 +149,36 @@ function mergeWhere(...clauses) {
|
|
|
125
149
|
AND: defined
|
|
126
150
|
};
|
|
127
151
|
}
|
|
128
|
-
function
|
|
129
|
-
return !!value && typeof value === "object" && !(value instanceof Date) && !Array.isArray(value);
|
|
130
|
-
}
|
|
131
|
-
function compileFieldFilter(model, fieldName, filter, dialect, state) {
|
|
152
|
+
function compileFieldFilter(model, fieldName, filter, dialect, state, tableAlias = model.table) {
|
|
132
153
|
const field = model.fields[fieldName];
|
|
133
154
|
if (!field) {
|
|
134
155
|
throw new Error(`Unknown field "${fieldName}" on model "${model.name}".`);
|
|
135
156
|
}
|
|
136
|
-
const column = `${quoteIdentifier(
|
|
137
|
-
|
|
157
|
+
const column = `${quoteIdentifier(tableAlias, dialect)}.${quoteIdentifier(field.column, dialect)}`;
|
|
158
|
+
const createValueExpression = (value) => {
|
|
159
|
+
const placeholder = createPlaceholder(dialect, state, encodeValue(field, dialect, value));
|
|
160
|
+
if (field.kind === "json" && dialect === "mysql") {
|
|
161
|
+
return `cast(${placeholder} as json)`;
|
|
162
|
+
}
|
|
163
|
+
return placeholder;
|
|
164
|
+
};
|
|
165
|
+
if (!isOperatorFilterObject(filter)) {
|
|
138
166
|
if (filter === null) return `${column} is null`;
|
|
139
|
-
|
|
140
|
-
return `${column} = ${placeholder}`;
|
|
167
|
+
return `${column} = ${createValueExpression(filter)}`;
|
|
141
168
|
}
|
|
142
169
|
const clauses = [];
|
|
143
170
|
if ("eq" in filter) {
|
|
144
171
|
if (filter.eq === null) {
|
|
145
172
|
clauses.push(`${column} is null`);
|
|
146
173
|
} else {
|
|
147
|
-
|
|
148
|
-
clauses.push(`${column} = ${placeholder}`);
|
|
174
|
+
clauses.push(`${column} = ${createValueExpression(filter.eq)}`);
|
|
149
175
|
}
|
|
150
176
|
}
|
|
151
177
|
if ("not" in filter) {
|
|
152
178
|
if (filter.not === null) {
|
|
153
179
|
clauses.push(`${column} is not null`);
|
|
154
180
|
} else {
|
|
155
|
-
|
|
156
|
-
dialect,
|
|
157
|
-
state,
|
|
158
|
-
encodeValue(field, dialect, filter.not)
|
|
159
|
-
);
|
|
160
|
-
clauses.push(`${column} <> ${placeholder}`);
|
|
181
|
+
clauses.push(`${column} <> ${createValueExpression(filter.not)}`);
|
|
161
182
|
}
|
|
162
183
|
}
|
|
163
184
|
if ("in" in filter) {
|
|
@@ -165,9 +186,7 @@ function compileFieldFilter(model, fieldName, filter, dialect, state) {
|
|
|
165
186
|
if (!values.length) {
|
|
166
187
|
clauses.push("1 = 0");
|
|
167
188
|
} else {
|
|
168
|
-
const placeholders = values.map(
|
|
169
|
-
(value) => createPlaceholder(dialect, state, encodeValue(field, dialect, value))
|
|
170
|
-
);
|
|
189
|
+
const placeholders = values.map((value) => createValueExpression(value));
|
|
171
190
|
clauses.push(`${column} in (${placeholders.join(", ")})`);
|
|
172
191
|
}
|
|
173
192
|
}
|
|
@@ -197,39 +216,39 @@ function compileFieldFilter(model, fieldName, filter, dialect, state) {
|
|
|
197
216
|
if (clauses.length === 1) return clauses[0];
|
|
198
217
|
return `(${clauses.join(" and ")})`;
|
|
199
218
|
}
|
|
200
|
-
function compileWhere(model, where, dialect, state) {
|
|
219
|
+
function compileWhere(model, where, dialect, state, tableAlias = model.table) {
|
|
201
220
|
if (!where) return void 0;
|
|
202
221
|
const clauses = [];
|
|
203
222
|
for (const [key, value] of Object.entries(where)) {
|
|
204
223
|
if (key === "AND") {
|
|
205
224
|
const items = Array.isArray(value) ? value : [];
|
|
206
225
|
if (!items.length) continue;
|
|
207
|
-
const nested = items.map((item) => compileWhere(model, item, dialect, state)).filter(Boolean).map((item) => `(${item})`);
|
|
226
|
+
const nested = items.map((item) => compileWhere(model, item, dialect, state, tableAlias)).filter(Boolean).map((item) => `(${item})`);
|
|
208
227
|
if (nested.length) clauses.push(nested.join(" and "));
|
|
209
228
|
continue;
|
|
210
229
|
}
|
|
211
230
|
if (key === "OR") {
|
|
212
231
|
const items = Array.isArray(value) ? value : [];
|
|
213
232
|
if (!items.length) continue;
|
|
214
|
-
const nested = items.map((item) => compileWhere(model, item, dialect, state)).filter(Boolean).map((item) => `(${item})`);
|
|
233
|
+
const nested = items.map((item) => compileWhere(model, item, dialect, state, tableAlias)).filter(Boolean).map((item) => `(${item})`);
|
|
215
234
|
if (nested.length) clauses.push(`(${nested.join(" or ")})`);
|
|
216
235
|
continue;
|
|
217
236
|
}
|
|
218
237
|
if (key === "NOT") {
|
|
219
|
-
const nested = compileWhere(model, value, dialect, state);
|
|
238
|
+
const nested = compileWhere(model, value, dialect, state, tableAlias);
|
|
220
239
|
if (nested) clauses.push(`not (${nested})`);
|
|
221
240
|
continue;
|
|
222
241
|
}
|
|
223
|
-
clauses.push(compileFieldFilter(model, key, value, dialect, state));
|
|
242
|
+
clauses.push(compileFieldFilter(model, key, value, dialect, state, tableAlias));
|
|
224
243
|
}
|
|
225
244
|
if (!clauses.length) return void 0;
|
|
226
245
|
return clauses.join(" and ");
|
|
227
246
|
}
|
|
228
|
-
function compileOrderBy(model, orderBy, dialect) {
|
|
247
|
+
function compileOrderBy(model, orderBy, dialect, tableAlias = model.table) {
|
|
229
248
|
if (!orderBy) return "";
|
|
230
249
|
const parts = Object.entries(orderBy).filter(([fieldName]) => fieldName in model.fields).map(([fieldName, direction]) => {
|
|
231
250
|
const field = model.fields[fieldName];
|
|
232
|
-
return `${quoteIdentifier(
|
|
251
|
+
return `${quoteIdentifier(tableAlias, dialect)}.${quoteIdentifier(field.column, dialect)} ${direction === "desc" ? "desc" : "asc"}`;
|
|
233
252
|
});
|
|
234
253
|
if (!parts.length) return "";
|
|
235
254
|
return ` order by ${parts.join(", ")}`;
|
|
@@ -252,13 +271,14 @@ function compilePagination(dialect, take, skip) {
|
|
|
252
271
|
}
|
|
253
272
|
function buildSelectStatement(model, dialect, args) {
|
|
254
273
|
const state = { params: [] };
|
|
274
|
+
const tableAlias = args.tableAlias ?? model.table;
|
|
255
275
|
const selectList = Object.values(model.fields).map(
|
|
256
|
-
(field) => `${quoteIdentifier(
|
|
276
|
+
(field) => `${quoteIdentifier(tableAlias, dialect)}.${quoteIdentifier(field.column, dialect)} as ${quoteIdentifier(field.name, dialect)}`
|
|
257
277
|
);
|
|
258
|
-
let sql = `select ${selectList.join(", ")} from ${quoteIdentifier(model.table, dialect)}`;
|
|
259
|
-
const where = compileWhere(model, args.where, dialect, state);
|
|
278
|
+
let sql = `select ${selectList.join(", ")} from ${quoteIdentifier(model.table, dialect)} as ${quoteIdentifier(tableAlias, dialect)}`;
|
|
279
|
+
const where = compileWhere(model, args.where, dialect, state, tableAlias);
|
|
260
280
|
if (where) sql += ` where ${where}`;
|
|
261
|
-
sql += compileOrderBy(model, args.orderBy, dialect);
|
|
281
|
+
sql += compileOrderBy(model, args.orderBy, dialect, tableAlias);
|
|
262
282
|
sql += compilePagination(dialect, args.take, args.skip);
|
|
263
283
|
return { sql, params: state.params };
|
|
264
284
|
}
|
|
@@ -560,6 +580,10 @@ function createMysqlPoolAdapter(pool) {
|
|
|
560
580
|
}
|
|
561
581
|
function createSqlDriver(adapter) {
|
|
562
582
|
async function loadRows(schema, modelName, args) {
|
|
583
|
+
const nativeRows = await loadRowsWithNativeJoins(schema, modelName, args);
|
|
584
|
+
if (nativeRows) {
|
|
585
|
+
return nativeRows;
|
|
586
|
+
}
|
|
563
587
|
const manifest = getManifest(schema);
|
|
564
588
|
const model = manifest.models[modelName];
|
|
565
589
|
const statement = buildSelectStatement(model, adapter.dialect, args);
|
|
@@ -585,6 +609,253 @@ function createSqlDriver(adapter) {
|
|
|
585
609
|
const row = result.rows[0];
|
|
586
610
|
return row ? decodeRow(model, adapter.dialect, row) : null;
|
|
587
611
|
}
|
|
612
|
+
function createNativePresenceAlias(model, alias, includeAllScalars, selectedScalarKeys) {
|
|
613
|
+
const occupiedAliases = new Set(
|
|
614
|
+
(includeAllScalars ? Object.keys(model.fields) : selectedScalarKeys).map(
|
|
615
|
+
(fieldName) => `${alias}__${fieldName}`
|
|
616
|
+
)
|
|
617
|
+
);
|
|
618
|
+
let candidate = `${alias}__orm_presence`;
|
|
619
|
+
let suffix = 0;
|
|
620
|
+
while (occupiedAliases.has(candidate)) {
|
|
621
|
+
suffix += 1;
|
|
622
|
+
candidate = `${alias}__orm_presence_${suffix}`;
|
|
623
|
+
}
|
|
624
|
+
return candidate;
|
|
625
|
+
}
|
|
626
|
+
function buildNativeJoinPlan(schema, modelName, select, aliasState) {
|
|
627
|
+
const manifest = getManifest(schema);
|
|
628
|
+
const model = manifest.models[modelName];
|
|
629
|
+
const alias = `t${aliasState.next++}`;
|
|
630
|
+
const entries = select ? Object.entries(select) : [];
|
|
631
|
+
const selectedScalarKeys = select ? entries.filter(([key, value]) => key in model.fields && value === true).map(([key]) => key) : Object.keys(model.fields);
|
|
632
|
+
const node = {
|
|
633
|
+
modelName,
|
|
634
|
+
model,
|
|
635
|
+
alias,
|
|
636
|
+
presenceAlias: createNativePresenceAlias(model, alias, !select, selectedScalarKeys),
|
|
637
|
+
includeAllScalars: !select,
|
|
638
|
+
selectedScalarKeys,
|
|
639
|
+
children: []
|
|
640
|
+
};
|
|
641
|
+
for (const [key, value] of entries) {
|
|
642
|
+
if (value === void 0 || !(key in schema.models[modelName].relations)) continue;
|
|
643
|
+
const relation = schema.models[modelName].relations[key];
|
|
644
|
+
const relationArgs = value === true ? {} : value;
|
|
645
|
+
if (relationArgs.where !== void 0 || relationArgs.orderBy !== void 0 || relationArgs.take !== void 0 || relationArgs.skip !== void 0) {
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
const child = buildNativeJoinPlan(
|
|
649
|
+
schema,
|
|
650
|
+
relation.target,
|
|
651
|
+
relationArgs.select,
|
|
652
|
+
aliasState
|
|
653
|
+
);
|
|
654
|
+
if (!child) return null;
|
|
655
|
+
child.relationName = key;
|
|
656
|
+
child.relationKind = relation.kind;
|
|
657
|
+
if (relation.kind === "belongsTo") {
|
|
658
|
+
const sourceField = model.fields[relation.foreignKey];
|
|
659
|
+
if (!sourceField) return null;
|
|
660
|
+
const targetReference = parseReference(sourceField.references);
|
|
661
|
+
const targetFieldName = targetReference?.field ?? identityField(manifest.models[relation.target]).name;
|
|
662
|
+
const targetField = child.model.fields[targetFieldName];
|
|
663
|
+
if (!targetField) return null;
|
|
664
|
+
child.sourceField = sourceField;
|
|
665
|
+
child.targetField = targetField;
|
|
666
|
+
} else if (relation.kind === "hasOne" || relation.kind === "hasMany") {
|
|
667
|
+
const targetForeignField = child.model.fields[relation.foreignKey];
|
|
668
|
+
if (!targetForeignField) return null;
|
|
669
|
+
const sourceReference = parseReference(targetForeignField.references);
|
|
670
|
+
const sourceFieldName = sourceReference?.field ?? identityField(manifest.models[modelName]).name;
|
|
671
|
+
const sourceField = model.fields[sourceFieldName];
|
|
672
|
+
if (!sourceField) return null;
|
|
673
|
+
child.sourceField = sourceField;
|
|
674
|
+
child.targetField = targetForeignField;
|
|
675
|
+
} else {
|
|
676
|
+
const throughModel = manifest.models[relation.through];
|
|
677
|
+
const throughFromField = throughModel.fields[relation.from];
|
|
678
|
+
const throughToField = throughModel.fields[relation.to];
|
|
679
|
+
if (!throughFromField || !throughToField) return null;
|
|
680
|
+
const throughFromReference = parseReference(throughFromField.references);
|
|
681
|
+
const throughToReference = parseReference(throughToField.references);
|
|
682
|
+
const sourceFieldName = throughFromReference?.field ?? identityField(manifest.models[modelName]).name;
|
|
683
|
+
const targetFieldName = throughToReference?.field ?? identityField(child.model).name;
|
|
684
|
+
const sourceField = model.fields[sourceFieldName];
|
|
685
|
+
const targetField = child.model.fields[targetFieldName];
|
|
686
|
+
if (!sourceField || !targetField) return null;
|
|
687
|
+
child.sourceField = sourceField;
|
|
688
|
+
child.targetField = targetField;
|
|
689
|
+
child.throughModel = throughModel;
|
|
690
|
+
child.throughAlias = `t${aliasState.next++}`;
|
|
691
|
+
child.throughFromField = throughFromField;
|
|
692
|
+
child.throughToField = throughToField;
|
|
693
|
+
}
|
|
694
|
+
node.children.push(child);
|
|
695
|
+
}
|
|
696
|
+
return node;
|
|
697
|
+
}
|
|
698
|
+
function hasNativeJoinableRelations(schema, modelName, select) {
|
|
699
|
+
if (!select) return false;
|
|
700
|
+
const plan = buildNativeJoinPlan(schema, modelName, select, { next: 0 });
|
|
701
|
+
return !!plan && plan.children.length > 0;
|
|
702
|
+
}
|
|
703
|
+
function collectNativeJoinSelects(node, selectList) {
|
|
704
|
+
const scalarKeys = node.includeAllScalars ? Object.keys(node.model.fields) : node.selectedScalarKeys;
|
|
705
|
+
for (const fieldName of scalarKeys) {
|
|
706
|
+
const field = node.model.fields[fieldName];
|
|
707
|
+
if (!field) continue;
|
|
708
|
+
selectList.push(
|
|
709
|
+
`${quoteIdentifier(node.alias, adapter.dialect)}.${quoteIdentifier(field.column, adapter.dialect)} as ${quoteIdentifier(`${node.alias}__${field.name}`, adapter.dialect)}`
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
const identity = identityField(node.model);
|
|
713
|
+
selectList.push(
|
|
714
|
+
`${quoteIdentifier(node.alias, adapter.dialect)}.${quoteIdentifier(identity.column, adapter.dialect)} as ${quoteIdentifier(node.presenceAlias, adapter.dialect)}`
|
|
715
|
+
);
|
|
716
|
+
for (const child of node.children) {
|
|
717
|
+
collectNativeJoinSelects(child, selectList);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function collectNativeJoinClauses(node, joins) {
|
|
721
|
+
for (const child of node.children) {
|
|
722
|
+
if (child.relationKind === "manyToMany") {
|
|
723
|
+
joins.push(
|
|
724
|
+
` left join ${quoteIdentifier(child.throughModel.table, adapter.dialect)} as ${quoteIdentifier(child.throughAlias, adapter.dialect)} on ${quoteIdentifier(node.alias, adapter.dialect)}.${quoteIdentifier(child.sourceField.column, adapter.dialect)} = ${quoteIdentifier(child.throughAlias, adapter.dialect)}.${quoteIdentifier(child.throughFromField.column, adapter.dialect)}`
|
|
725
|
+
);
|
|
726
|
+
joins.push(
|
|
727
|
+
` left join ${quoteIdentifier(child.model.table, adapter.dialect)} as ${quoteIdentifier(child.alias, adapter.dialect)} on ${quoteIdentifier(child.throughAlias, adapter.dialect)}.${quoteIdentifier(child.throughToField.column, adapter.dialect)} = ${quoteIdentifier(child.alias, adapter.dialect)}.${quoteIdentifier(child.targetField.column, adapter.dialect)}`
|
|
728
|
+
);
|
|
729
|
+
} else {
|
|
730
|
+
const leftColumn = child.relationKind === "belongsTo" ? `${quoteIdentifier(node.alias, adapter.dialect)}.${quoteIdentifier(child.sourceField.column, adapter.dialect)}` : `${quoteIdentifier(child.alias, adapter.dialect)}.${quoteIdentifier(child.targetField.column, adapter.dialect)}`;
|
|
731
|
+
const rightColumn = child.relationKind === "belongsTo" ? `${quoteIdentifier(child.alias, adapter.dialect)}.${quoteIdentifier(child.targetField.column, adapter.dialect)}` : `${quoteIdentifier(node.alias, adapter.dialect)}.${quoteIdentifier(child.sourceField.column, adapter.dialect)}`;
|
|
732
|
+
joins.push(
|
|
733
|
+
` left join ${quoteIdentifier(child.model.table, adapter.dialect)} as ${quoteIdentifier(child.alias, adapter.dialect)} on ${leftColumn} = ${rightColumn}`
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
collectNativeJoinClauses(child, joins);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function buildNativeJoinRootSource(root, args) {
|
|
740
|
+
const state = { params: [] };
|
|
741
|
+
const sourceAlias = `${root.alias}__src`;
|
|
742
|
+
const selectList = Object.values(root.model.fields).map(
|
|
743
|
+
(field) => `${quoteIdentifier(sourceAlias, adapter.dialect)}.${quoteIdentifier(field.column, adapter.dialect)} as ${quoteIdentifier(field.column, adapter.dialect)}`
|
|
744
|
+
);
|
|
745
|
+
let sql = `select ${selectList.join(", ")} from ${quoteIdentifier(root.model.table, adapter.dialect)} as ${quoteIdentifier(sourceAlias, adapter.dialect)}`;
|
|
746
|
+
const where = compileWhere(root.model, args.where, adapter.dialect, state, sourceAlias);
|
|
747
|
+
if (where) sql += ` where ${where}`;
|
|
748
|
+
sql += compileOrderBy(root.model, args.orderBy, adapter.dialect, sourceAlias);
|
|
749
|
+
sql += compilePagination(adapter.dialect, args.take, args.skip);
|
|
750
|
+
return {
|
|
751
|
+
sql: `(${sql})`,
|
|
752
|
+
params: state.params
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function buildNativeJoinStatement(root, args) {
|
|
756
|
+
const state = { params: [] };
|
|
757
|
+
const selectList = [];
|
|
758
|
+
const joins = [];
|
|
759
|
+
collectNativeJoinSelects(root, selectList);
|
|
760
|
+
collectNativeJoinClauses(root, joins);
|
|
761
|
+
const rootSource = buildNativeJoinRootSource(root, args);
|
|
762
|
+
state.params.push(...rootSource.params);
|
|
763
|
+
let sql = `select ${selectList.join(", ")} from ${rootSource.sql} as ${quoteIdentifier(root.alias, adapter.dialect)}`;
|
|
764
|
+
if (joins.length) sql += joins.join("");
|
|
765
|
+
sql += compileOrderBy(root.model, args.orderBy, adapter.dialect, root.alias);
|
|
766
|
+
return { sql, params: state.params };
|
|
767
|
+
}
|
|
768
|
+
function nodePresenceValue(node, rawRow) {
|
|
769
|
+
return rawRow[node.presenceAlias];
|
|
770
|
+
}
|
|
771
|
+
function projectNativeJoinNode(node, rawRow) {
|
|
772
|
+
if (nodePresenceValue(node, rawRow) == null) {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
const output = {};
|
|
776
|
+
Object.defineProperty(output, nativeNodeIdentity, {
|
|
777
|
+
value: rawRow[node.presenceAlias],
|
|
778
|
+
enumerable: false,
|
|
779
|
+
configurable: true
|
|
780
|
+
});
|
|
781
|
+
const scalarKeys = node.includeAllScalars ? Object.keys(node.model.fields) : node.selectedScalarKeys;
|
|
782
|
+
for (const fieldName of scalarKeys) {
|
|
783
|
+
const field = node.model.fields[fieldName];
|
|
784
|
+
if (!field) continue;
|
|
785
|
+
output[field.name] = decodeValue(
|
|
786
|
+
field,
|
|
787
|
+
adapter.dialect,
|
|
788
|
+
rawRow[`${node.alias}__${field.name}`]
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
for (const child of node.children) {
|
|
792
|
+
const childValue = projectNativeJoinNode(child, rawRow);
|
|
793
|
+
output[child.relationName] = child.relationKind === "hasMany" || child.relationKind === "manyToMany" ? childValue ? [childValue] : [] : childValue;
|
|
794
|
+
}
|
|
795
|
+
return output;
|
|
796
|
+
}
|
|
797
|
+
function mergeNativeJoinNode(node, target, next) {
|
|
798
|
+
for (const child of node.children) {
|
|
799
|
+
const relationName = child.relationName;
|
|
800
|
+
if (child.relationKind === "hasMany" || child.relationKind === "manyToMany") {
|
|
801
|
+
const targetRows = Array.isArray(target[relationName]) ? target[relationName] : [];
|
|
802
|
+
const nextRows = Array.isArray(next[relationName]) ? next[relationName] : [];
|
|
803
|
+
if (!Array.isArray(target[relationName])) {
|
|
804
|
+
target[relationName] = targetRows;
|
|
805
|
+
}
|
|
806
|
+
for (const nextRow of nextRows) {
|
|
807
|
+
const identity = nextRow[nativeNodeIdentity];
|
|
808
|
+
const existing2 = targetRows.find((entry) => entry[nativeNodeIdentity] === identity);
|
|
809
|
+
if (existing2) {
|
|
810
|
+
mergeNativeJoinNode(child, existing2, nextRow);
|
|
811
|
+
} else {
|
|
812
|
+
targetRows.push(nextRow);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const nextValue = next[relationName];
|
|
818
|
+
if (nextValue === void 0) continue;
|
|
819
|
+
if (nextValue === null) {
|
|
820
|
+
if (!(relationName in target)) {
|
|
821
|
+
target[relationName] = null;
|
|
822
|
+
}
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const existing = target[relationName];
|
|
826
|
+
if (!existing || typeof existing !== "object") {
|
|
827
|
+
target[relationName] = nextValue;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
mergeNativeJoinNode(child, existing, nextValue);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
async function loadRowsWithNativeJoins(schema, modelName, args) {
|
|
834
|
+
if (!hasNativeJoinableRelations(schema, modelName, args.select)) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
837
|
+
const plan = buildNativeJoinPlan(schema, modelName, args.select, { next: 0 });
|
|
838
|
+
if (!plan || !plan.children.length) {
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
const statement = buildNativeJoinStatement(plan, args);
|
|
842
|
+
const result = await adapter.query(statement.sql, statement.params);
|
|
843
|
+
const groupedRows = [];
|
|
844
|
+
const groupedByIdentity = /* @__PURE__ */ new Map();
|
|
845
|
+
for (const row of result.rows) {
|
|
846
|
+
const projected = projectNativeJoinNode(plan, row);
|
|
847
|
+
if (!projected) continue;
|
|
848
|
+
const identity = projected[nativeNodeIdentity];
|
|
849
|
+
const existing = groupedByIdentity.get(identity);
|
|
850
|
+
if (existing) {
|
|
851
|
+
mergeNativeJoinNode(plan, existing, projected);
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
groupedByIdentity.set(identity, projected);
|
|
855
|
+
groupedRows.push(projected);
|
|
856
|
+
}
|
|
857
|
+
return groupedRows;
|
|
858
|
+
}
|
|
588
859
|
async function projectRow(schema, modelName, row, select) {
|
|
589
860
|
const manifest = getManifest(schema);
|
|
590
861
|
const model = manifest.models[modelName];
|