linkgress-orm 0.1.0 → 0.1.1
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/LICENSE +21 -21
- package/README.md +196 -196
- package/dist/migration/db-schema-manager.js +77 -77
- package/dist/migration/enum-migrator.js +6 -6
- package/dist/query/cte-builder.d.ts +3 -0
- package/dist/query/cte-builder.d.ts.map +1 -1
- package/dist/query/cte-builder.js +14 -0
- package/dist/query/cte-builder.js.map +1 -1
- package/dist/query/grouped-query.d.ts +11 -0
- package/dist/query/grouped-query.d.ts.map +1 -1
- package/dist/query/grouped-query.js +113 -67
- package/dist/query/grouped-query.js.map +1 -1
- package/dist/query/join-builder.d.ts.map +1 -1
- package/dist/query/join-builder.js +10 -14
- package/dist/query/join-builder.js.map +1 -1
- package/dist/query/query-builder.d.ts +29 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +215 -133
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/strategies/cte-collection-strategy.js +68 -68
- package/dist/query/strategies/lateral-collection-strategy.js +26 -26
- package/dist/query/strategies/temptable-collection-strategy.js +97 -97
- package/dist/schema/table-builder.d.ts +16 -0
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +23 -1
- package/dist/schema/table-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CollectionQueryBuilder = exports.ReferenceQueryBuilder = exports.SelectQueryBuilder = exports.QueryBuilder = void 0;
|
|
4
|
+
exports.getColumnNameMapForSchema = getColumnNameMapForSchema;
|
|
5
|
+
exports.getRelationEntriesForSchema = getRelationEntriesForSchema;
|
|
6
|
+
exports.getTargetSchemaForRelation = getTargetSchemaForRelation;
|
|
7
|
+
exports.createNestedFieldRefProxy = createNestedFieldRefProxy;
|
|
4
8
|
const conditions_1 = require("./conditions");
|
|
5
9
|
const query_utils_1 = require("./query-utils");
|
|
6
10
|
const subquery_1 = require("./subquery");
|
|
7
11
|
const grouped_query_1 = require("./grouped-query");
|
|
8
12
|
const cte_builder_1 = require("./cte-builder");
|
|
9
13
|
const collection_strategy_factory_1 = require("./collection-strategy.factory");
|
|
14
|
+
/**
|
|
15
|
+
* Performance utility: Get column name map from schema, using cached version if available
|
|
16
|
+
*/
|
|
17
|
+
function getColumnNameMapForSchema(schema) {
|
|
18
|
+
if (schema.columnNameMap) {
|
|
19
|
+
return schema.columnNameMap;
|
|
20
|
+
}
|
|
21
|
+
// Fallback: build the map (for schemas that weren't built with the new TableBuilder)
|
|
22
|
+
const map = new Map();
|
|
23
|
+
for (const [colName, colBuilder] of Object.entries(schema.columns)) {
|
|
24
|
+
map.set(colName, colBuilder.build().name);
|
|
25
|
+
}
|
|
26
|
+
return map;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Performance utility: Get relation entries array from schema, using cached version if available
|
|
30
|
+
*/
|
|
31
|
+
function getRelationEntriesForSchema(schema) {
|
|
32
|
+
if (schema.relationEntries) {
|
|
33
|
+
return schema.relationEntries;
|
|
34
|
+
}
|
|
35
|
+
// Fallback: build the array (for schemas that weren't built with the new TableBuilder)
|
|
36
|
+
return Object.entries(schema.relations);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Performance utility: Get target schema for a relation, using cached version if available
|
|
40
|
+
*/
|
|
41
|
+
function getTargetSchemaForRelation(schema, relName, relConfig) {
|
|
42
|
+
// Try cached version first
|
|
43
|
+
if (schema.relationSchemaCache) {
|
|
44
|
+
const cached = schema.relationSchemaCache.get(relName);
|
|
45
|
+
if (cached)
|
|
46
|
+
return cached;
|
|
47
|
+
}
|
|
48
|
+
// Fallback: build the schema
|
|
49
|
+
if (relConfig.targetTableBuilder) {
|
|
50
|
+
return relConfig.targetTableBuilder.build();
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
// Performance: Cache nested field ref proxies per table alias
|
|
55
|
+
const nestedFieldRefProxyCache = new Map();
|
|
56
|
+
/**
|
|
57
|
+
* Creates a nested proxy that supports accessing properties at any depth.
|
|
58
|
+
* This allows patterns like `p.product.priceMode` to work even without full schema information.
|
|
59
|
+
* Each property access returns an object that is both a FieldRef and can be further accessed.
|
|
60
|
+
*
|
|
61
|
+
* @param tableAlias The table alias to use for the FieldRef
|
|
62
|
+
* @returns A proxy that creates FieldRefs for any property access
|
|
63
|
+
*/
|
|
64
|
+
function createNestedFieldRefProxy(tableAlias) {
|
|
65
|
+
// Return cached proxy if available
|
|
66
|
+
const cached = nestedFieldRefProxyCache.get(tableAlias);
|
|
67
|
+
if (cached)
|
|
68
|
+
return cached;
|
|
69
|
+
const handler = {
|
|
70
|
+
get: (_target, prop) => {
|
|
71
|
+
// Handle Symbol.toPrimitive for string conversion (used in template literals)
|
|
72
|
+
if (prop === Symbol.toPrimitive || prop === 'toString' || prop === 'valueOf') {
|
|
73
|
+
return () => `[NestedFieldRefProxy:${tableAlias}]`;
|
|
74
|
+
}
|
|
75
|
+
if (typeof prop === 'symbol')
|
|
76
|
+
return undefined;
|
|
77
|
+
// Return an object that is both a FieldRef AND a proxy for further nesting
|
|
78
|
+
const fieldRef = {
|
|
79
|
+
__fieldName: prop,
|
|
80
|
+
__dbColumnName: prop,
|
|
81
|
+
__tableAlias: tableAlias,
|
|
82
|
+
};
|
|
83
|
+
// Return a proxy that acts as both the FieldRef and allows further property access
|
|
84
|
+
return new Proxy(fieldRef, {
|
|
85
|
+
get: (fieldTarget, nestedProp) => {
|
|
86
|
+
// Handle Symbol.toPrimitive for string conversion (used in template literals)
|
|
87
|
+
if (nestedProp === Symbol.toPrimitive || nestedProp === 'toString' || nestedProp === 'valueOf') {
|
|
88
|
+
return () => fieldTarget.__dbColumnName;
|
|
89
|
+
}
|
|
90
|
+
if (typeof nestedProp === 'symbol')
|
|
91
|
+
return undefined;
|
|
92
|
+
// If accessing FieldRef properties, return them
|
|
93
|
+
if (nestedProp === '__fieldName' || nestedProp === '__dbColumnName' || nestedProp === '__tableAlias') {
|
|
94
|
+
return fieldTarget[nestedProp];
|
|
95
|
+
}
|
|
96
|
+
// Otherwise, treat as nested navigation and create a new nested proxy
|
|
97
|
+
// The nested table alias is the property name (e.g., 'product' for p.product)
|
|
98
|
+
return createNestedFieldRefProxy(prop)[nestedProp];
|
|
99
|
+
},
|
|
100
|
+
has: (_fieldTarget, _nestedProp) => true,
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
has: (_target, prop) => {
|
|
104
|
+
// The outer proxy doesn't have FieldRef properties - only field names
|
|
105
|
+
if (prop === '__fieldName' || prop === '__dbColumnName' || prop === '__tableAlias') {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
const proxy = new Proxy({}, handler);
|
|
112
|
+
nestedFieldRefProxyCache.set(tableAlias, proxy);
|
|
113
|
+
return proxy;
|
|
114
|
+
}
|
|
10
115
|
/**
|
|
11
116
|
* Cached regex for numeric string detection
|
|
12
117
|
* Used to convert PostgreSQL NUMERIC/BIGINT strings to numbers
|
|
@@ -77,14 +182,10 @@ class QueryBuilder {
|
|
|
77
182
|
return this._cachedMockRow;
|
|
78
183
|
}
|
|
79
184
|
const mock = {};
|
|
80
|
-
// Performance:
|
|
81
|
-
const
|
|
82
|
-
const columnConfigs = new Map();
|
|
83
|
-
for (const [colName, colBuilder] of columnEntries) {
|
|
84
|
-
columnConfigs.set(colName, colBuilder.build().name);
|
|
85
|
-
}
|
|
185
|
+
// Performance: Use pre-computed column name map if available
|
|
186
|
+
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
86
187
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
87
|
-
for (const [colName, dbColumnName] of
|
|
188
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
88
189
|
Object.defineProperty(mock, colName, {
|
|
89
190
|
get: () => ({
|
|
90
191
|
__fieldName: colName,
|
|
@@ -95,17 +196,13 @@ class QueryBuilder {
|
|
|
95
196
|
configurable: true,
|
|
96
197
|
});
|
|
97
198
|
}
|
|
98
|
-
// Performance:
|
|
99
|
-
const
|
|
100
|
-
for (const [relName, relConfig] of Object.entries(this.schema.relations)) {
|
|
101
|
-
if (relConfig.targetTableBuilder) {
|
|
102
|
-
relationSchemas.set(relName, relConfig.targetTableBuilder.build());
|
|
103
|
-
}
|
|
104
|
-
}
|
|
199
|
+
// Performance: Use pre-computed relation entries and cached schemas
|
|
200
|
+
const relationEntries = getRelationEntriesForSchema(this.schema);
|
|
105
201
|
// Add relations (both collections and single references)
|
|
106
|
-
for (const [relName, relConfig] of
|
|
202
|
+
for (const [relName, relConfig] of relationEntries) {
|
|
203
|
+
// Performance: Use cached target schema
|
|
204
|
+
const targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
107
205
|
if (relConfig.type === 'many') {
|
|
108
|
-
const targetSchema = relationSchemas.get(relName);
|
|
109
206
|
Object.defineProperty(mock, relName, {
|
|
110
207
|
get: () => {
|
|
111
208
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', this.schema.name, targetSchema);
|
|
@@ -116,7 +213,6 @@ class QueryBuilder {
|
|
|
116
213
|
}
|
|
117
214
|
else {
|
|
118
215
|
// Single reference navigation (many-to-one, one-to-one)
|
|
119
|
-
const targetSchema = relationSchemas.get(relName);
|
|
120
216
|
Object.defineProperty(mock, relName, {
|
|
121
217
|
get: () => {
|
|
122
218
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
@@ -234,14 +330,10 @@ class QueryBuilder {
|
|
|
234
330
|
*/
|
|
235
331
|
createMockRowForTable(schema, alias) {
|
|
236
332
|
const mock = {};
|
|
237
|
-
// Performance:
|
|
238
|
-
const
|
|
239
|
-
const columnConfigs = new Map();
|
|
240
|
-
for (const [colName, colBuilder] of columnEntries) {
|
|
241
|
-
columnConfigs.set(colName, colBuilder.build().name);
|
|
242
|
-
}
|
|
333
|
+
// Performance: Use pre-computed column name map if available
|
|
334
|
+
const columnNameMap = getColumnNameMapForSchema(schema);
|
|
243
335
|
// Add columns as FieldRef objects with table alias
|
|
244
|
-
for (const [colName, dbColumnName] of
|
|
336
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
245
337
|
Object.defineProperty(mock, colName, {
|
|
246
338
|
get: () => ({
|
|
247
339
|
__fieldName: colName,
|
|
@@ -252,18 +344,14 @@ class QueryBuilder {
|
|
|
252
344
|
configurable: true,
|
|
253
345
|
});
|
|
254
346
|
}
|
|
255
|
-
// Performance:
|
|
256
|
-
const
|
|
257
|
-
for (const [relName, relConfig] of Object.entries(schema.relations)) {
|
|
258
|
-
if (relConfig.targetTableBuilder) {
|
|
259
|
-
relationSchemas.set(relName, relConfig.targetTableBuilder.build());
|
|
260
|
-
}
|
|
261
|
-
}
|
|
347
|
+
// Performance: Use pre-computed relation entries and cached schemas
|
|
348
|
+
const relationEntries = getRelationEntriesForSchema(schema);
|
|
262
349
|
// Add navigation properties (single references and collections)
|
|
263
|
-
for (const [relName, relConfig] of
|
|
350
|
+
for (const [relName, relConfig] of relationEntries) {
|
|
351
|
+
// Performance: Use cached target schema
|
|
352
|
+
const targetSchema = getTargetSchemaForRelation(schema, relName, relConfig);
|
|
264
353
|
if (relConfig.type === 'many') {
|
|
265
354
|
// Collection navigation
|
|
266
|
-
const targetSchema = relationSchemas.get(relName);
|
|
267
355
|
Object.defineProperty(mock, relName, {
|
|
268
356
|
get: () => {
|
|
269
357
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
|
|
@@ -274,7 +362,6 @@ class QueryBuilder {
|
|
|
274
362
|
}
|
|
275
363
|
else {
|
|
276
364
|
// Single reference navigation
|
|
277
|
-
const targetSchema = relationSchemas.get(relName);
|
|
278
365
|
Object.defineProperty(mock, relName, {
|
|
279
366
|
get: () => {
|
|
280
367
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
@@ -383,7 +470,12 @@ class SelectQueryBuilder {
|
|
|
383
470
|
* .toList();
|
|
384
471
|
*/
|
|
385
472
|
with(...ctes) {
|
|
386
|
-
|
|
473
|
+
// Add CTEs, avoiding duplicates by name
|
|
474
|
+
for (const cte of ctes) {
|
|
475
|
+
if (!this.ctes.some(existing => existing.name === cte.name)) {
|
|
476
|
+
this.ctes.push(cte);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
387
479
|
return this;
|
|
388
480
|
}
|
|
389
481
|
/**
|
|
@@ -610,14 +702,10 @@ class SelectQueryBuilder {
|
|
|
610
702
|
*/
|
|
611
703
|
createMockRowForTable(schema, alias) {
|
|
612
704
|
const mock = {};
|
|
613
|
-
// Performance:
|
|
614
|
-
const
|
|
615
|
-
const columnConfigs = new Map();
|
|
616
|
-
for (const [colName, colBuilder] of columnEntries) {
|
|
617
|
-
columnConfigs.set(colName, colBuilder.build().name);
|
|
618
|
-
}
|
|
705
|
+
// Performance: Use pre-computed column name map if available
|
|
706
|
+
const columnNameMap = getColumnNameMapForSchema(schema);
|
|
619
707
|
// Add columns as FieldRef objects with table alias
|
|
620
|
-
for (const [colName, dbColumnName] of
|
|
708
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
621
709
|
Object.defineProperty(mock, colName, {
|
|
622
710
|
get: () => ({
|
|
623
711
|
__fieldName: colName,
|
|
@@ -628,18 +716,14 @@ class SelectQueryBuilder {
|
|
|
628
716
|
configurable: true,
|
|
629
717
|
});
|
|
630
718
|
}
|
|
631
|
-
// Performance:
|
|
632
|
-
const
|
|
633
|
-
for (const [relName, relConfig] of Object.entries(schema.relations)) {
|
|
634
|
-
if (relConfig.targetTableBuilder) {
|
|
635
|
-
relationSchemas.set(relName, relConfig.targetTableBuilder.build());
|
|
636
|
-
}
|
|
637
|
-
}
|
|
719
|
+
// Performance: Use pre-computed relation entries and cached schemas
|
|
720
|
+
const relationEntries = getRelationEntriesForSchema(schema);
|
|
638
721
|
// Add navigation properties (single references and collections)
|
|
639
|
-
for (const [relName, relConfig] of
|
|
722
|
+
for (const [relName, relConfig] of relationEntries) {
|
|
723
|
+
// Performance: Use cached target schema
|
|
724
|
+
const targetSchema = getTargetSchemaForRelation(schema, relName, relConfig);
|
|
640
725
|
if (relConfig.type === 'many') {
|
|
641
726
|
// Collection navigation
|
|
642
|
-
const targetSchema = relationSchemas.get(relName);
|
|
643
727
|
Object.defineProperty(mock, relName, {
|
|
644
728
|
get: () => {
|
|
645
729
|
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', schema.name, targetSchema);
|
|
@@ -650,7 +734,6 @@ class SelectQueryBuilder {
|
|
|
650
734
|
}
|
|
651
735
|
else {
|
|
652
736
|
// Single reference navigation
|
|
653
|
-
const targetSchema = relationSchemas.get(relName);
|
|
654
737
|
Object.defineProperty(mock, relName, {
|
|
655
738
|
get: () => {
|
|
656
739
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema);
|
|
@@ -1238,9 +1321,10 @@ class SelectQueryBuilder {
|
|
|
1238
1321
|
*/
|
|
1239
1322
|
createMockRow() {
|
|
1240
1323
|
const mock = {};
|
|
1324
|
+
// Performance: Use pre-computed column name map if available
|
|
1325
|
+
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
1241
1326
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
1242
|
-
for (const [colName,
|
|
1243
|
-
const dbColumnName = colBuilder.build().name;
|
|
1327
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
1244
1328
|
Object.defineProperty(mock, colName, {
|
|
1245
1329
|
get: () => ({
|
|
1246
1330
|
__fieldName: colName,
|
|
@@ -1257,13 +1341,12 @@ class SelectQueryBuilder {
|
|
|
1257
1341
|
if (join.isSubquery || !join.schema) {
|
|
1258
1342
|
continue;
|
|
1259
1343
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
}
|
|
1344
|
+
// Performance: Use pre-computed column name map for joined schema
|
|
1345
|
+
const joinColumnNameMap = getColumnNameMapForSchema(join.schema);
|
|
1346
|
+
if (!mock[join.alias]) {
|
|
1347
|
+
mock[join.alias] = {};
|
|
1348
|
+
}
|
|
1349
|
+
for (const [colName, dbColumnName] of joinColumnNameMap) {
|
|
1267
1350
|
Object.defineProperty(mock[join.alias], colName, {
|
|
1268
1351
|
get: () => ({
|
|
1269
1352
|
__fieldName: colName,
|
|
@@ -1275,15 +1358,18 @@ class SelectQueryBuilder {
|
|
|
1275
1358
|
});
|
|
1276
1359
|
}
|
|
1277
1360
|
}
|
|
1361
|
+
// Performance: Use pre-computed relation entries
|
|
1362
|
+
const relationEntries = getRelationEntriesForSchema(this.schema);
|
|
1278
1363
|
// Add relations as CollectionQueryBuilder or ReferenceQueryBuilder
|
|
1279
|
-
for (const [relName, relConfig] of
|
|
1280
|
-
// Try to get target schema from registry (preferred, has full relations) or
|
|
1364
|
+
for (const [relName, relConfig] of relationEntries) {
|
|
1365
|
+
// Try to get target schema from registry (preferred, has full relations) or cached schema
|
|
1281
1366
|
let targetSchema;
|
|
1282
1367
|
if (this.schemaRegistry) {
|
|
1283
1368
|
targetSchema = this.schemaRegistry.get(relConfig.targetTable);
|
|
1284
1369
|
}
|
|
1285
|
-
if (!targetSchema
|
|
1286
|
-
|
|
1370
|
+
if (!targetSchema) {
|
|
1371
|
+
// Performance: Use cached target schema
|
|
1372
|
+
targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
1287
1373
|
}
|
|
1288
1374
|
if (relConfig.type === 'many') {
|
|
1289
1375
|
Object.defineProperty(mock, relName, {
|
|
@@ -1384,27 +1470,13 @@ class SelectQueryBuilder {
|
|
|
1384
1470
|
for (const [key, value] of Object.entries(selection)) {
|
|
1385
1471
|
if (value && typeof value === 'object' && '__tableAlias' in value && '__dbColumnName' in value) {
|
|
1386
1472
|
// This is a FieldRef with a table alias - check if it's from a related table
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
if (relation.targetTableBuilder) {
|
|
1395
|
-
const targetTableSchema = relation.targetTableBuilder.build();
|
|
1396
|
-
targetSchema = targetTableSchema.schema;
|
|
1397
|
-
}
|
|
1398
|
-
// Add a JOIN for this reference
|
|
1399
|
-
joins.push({
|
|
1400
|
-
alias: tableAlias,
|
|
1401
|
-
targetTable: relation.targetTable,
|
|
1402
|
-
targetSchema,
|
|
1403
|
-
foreignKeys: relation.foreignKeys || [relation.foreignKey || ''],
|
|
1404
|
-
matches: relation.matches || [],
|
|
1405
|
-
isMandatory: relation.isMandatory ?? false,
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1473
|
+
this.addJoinForFieldRef(value, joins);
|
|
1474
|
+
}
|
|
1475
|
+
else if (value instanceof conditions_1.SqlFragment) {
|
|
1476
|
+
// SqlFragment may contain navigation property references - extract them
|
|
1477
|
+
const fieldRefs = value.getFieldRefs();
|
|
1478
|
+
for (const fieldRef of fieldRefs) {
|
|
1479
|
+
this.addJoinForFieldRef(fieldRef, joins);
|
|
1408
1480
|
}
|
|
1409
1481
|
}
|
|
1410
1482
|
else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
@@ -1413,6 +1485,36 @@ class SelectQueryBuilder {
|
|
|
1413
1485
|
}
|
|
1414
1486
|
}
|
|
1415
1487
|
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Add a JOIN for a FieldRef if it references a related table
|
|
1490
|
+
*/
|
|
1491
|
+
addJoinForFieldRef(fieldRef, joins) {
|
|
1492
|
+
if (!fieldRef || typeof fieldRef !== 'object' || !('__tableAlias' in fieldRef) || !('__dbColumnName' in fieldRef)) {
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const tableAlias = fieldRef.__tableAlias;
|
|
1496
|
+
if (tableAlias && tableAlias !== this.schema.name && !joins.some(j => j.alias === tableAlias)) {
|
|
1497
|
+
// This references a related table - find the relation and add a JOIN
|
|
1498
|
+
const relation = this.schema.relations[tableAlias];
|
|
1499
|
+
if (relation && relation.type === 'one') {
|
|
1500
|
+
// Get target schema from targetTableBuilder if available
|
|
1501
|
+
let targetSchema;
|
|
1502
|
+
if (relation.targetTableBuilder) {
|
|
1503
|
+
const targetTableSchema = relation.targetTableBuilder.build();
|
|
1504
|
+
targetSchema = targetTableSchema.schema;
|
|
1505
|
+
}
|
|
1506
|
+
// Add a JOIN for this reference
|
|
1507
|
+
joins.push({
|
|
1508
|
+
alias: tableAlias,
|
|
1509
|
+
targetTable: relation.targetTable,
|
|
1510
|
+
targetSchema,
|
|
1511
|
+
foreignKeys: relation.foreignKeys || [relation.foreignKey || ''],
|
|
1512
|
+
matches: relation.matches || [],
|
|
1513
|
+
isMandatory: relation.isMandatory ?? false,
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1416
1518
|
/**
|
|
1417
1519
|
* Detect navigation property references in a WHERE condition and add necessary JOINs
|
|
1418
1520
|
*/
|
|
@@ -1599,9 +1701,10 @@ class SelectQueryBuilder {
|
|
|
1599
1701
|
// Select all columns from the target table and group them
|
|
1600
1702
|
// We'll need to use JSON object building in SQL
|
|
1601
1703
|
const fieldParts = [];
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1704
|
+
// Performance: Use cached column name map
|
|
1705
|
+
const targetColMap = getColumnNameMapForSchema(targetSchema);
|
|
1706
|
+
for (const [colKey, dbColName] of targetColMap) {
|
|
1707
|
+
fieldParts.push(`'${colKey}', "${alias}"."${dbColName}"`);
|
|
1605
1708
|
}
|
|
1606
1709
|
selectParts.push(`json_build_object(${fieldParts.join(', ')}) as "${key}"`);
|
|
1607
1710
|
}
|
|
@@ -1631,11 +1734,8 @@ class SelectQueryBuilder {
|
|
|
1631
1734
|
const relConfig = this.schema.relations[alias];
|
|
1632
1735
|
if (relConfig && relConfig.type === 'one') {
|
|
1633
1736
|
// This is a reference navigation - select all fields from the target table
|
|
1634
|
-
//
|
|
1635
|
-
|
|
1636
|
-
if (relConfig.targetTableBuilder) {
|
|
1637
|
-
targetSchema = relConfig.targetTableBuilder.build();
|
|
1638
|
-
}
|
|
1737
|
+
// Performance: Use cached target schema
|
|
1738
|
+
const targetSchema = getTargetSchemaForRelation(this.schema, alias, relConfig);
|
|
1639
1739
|
if (targetSchema) {
|
|
1640
1740
|
// Add JOIN if not already added
|
|
1641
1741
|
if (!joins.find(j => j.alias === alias)) {
|
|
@@ -1654,9 +1754,10 @@ class SelectQueryBuilder {
|
|
|
1654
1754
|
}
|
|
1655
1755
|
// Select all columns from the target table and group them into a JSON object
|
|
1656
1756
|
const fieldParts = [];
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1757
|
+
// Performance: Use cached column name map
|
|
1758
|
+
const targetColMap = getColumnNameMapForSchema(targetSchema);
|
|
1759
|
+
for (const [colKey, dbColName] of targetColMap) {
|
|
1760
|
+
fieldParts.push(`'${colKey}', "${alias}"."${dbColName}"`);
|
|
1660
1761
|
}
|
|
1661
1762
|
selectParts.push(`json_build_object(${fieldParts.join(', ')}) as "${key}"`);
|
|
1662
1763
|
continue;
|
|
@@ -2301,9 +2402,9 @@ class ReferenceQueryBuilder {
|
|
|
2301
2402
|
createMockTargetRow() {
|
|
2302
2403
|
if (this.targetTableSchema) {
|
|
2303
2404
|
const mock = {};
|
|
2304
|
-
// Add columns
|
|
2305
|
-
|
|
2306
|
-
|
|
2405
|
+
// Add columns - use pre-computed column name map if available
|
|
2406
|
+
const columnNameMap = getColumnNameMapForSchema(this.targetTableSchema);
|
|
2407
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
2307
2408
|
Object.defineProperty(mock, colName, {
|
|
2308
2409
|
get: () => ({
|
|
2309
2410
|
__fieldName: colName,
|
|
@@ -2356,15 +2457,8 @@ class ReferenceQueryBuilder {
|
|
|
2356
2457
|
return mock;
|
|
2357
2458
|
}
|
|
2358
2459
|
else {
|
|
2359
|
-
// Fallback:
|
|
2360
|
-
|
|
2361
|
-
get: (target, prop) => ({
|
|
2362
|
-
__fieldName: prop,
|
|
2363
|
-
__dbColumnName: prop,
|
|
2364
|
-
__tableAlias: this.relationName,
|
|
2365
|
-
}),
|
|
2366
|
-
};
|
|
2367
|
-
return new Proxy({}, handler);
|
|
2460
|
+
// Fallback: use the shared nested proxy that supports deep property access
|
|
2461
|
+
return createNestedFieldRefProxy(this.relationName);
|
|
2368
2462
|
}
|
|
2369
2463
|
}
|
|
2370
2464
|
}
|
|
@@ -2444,14 +2538,10 @@ class CollectionQueryBuilder {
|
|
|
2444
2538
|
if (this.targetTableSchema) {
|
|
2445
2539
|
// If we have schema information, create a properly typed mock
|
|
2446
2540
|
const mock = {};
|
|
2447
|
-
// Performance:
|
|
2448
|
-
const
|
|
2449
|
-
const columnConfigs = new Map();
|
|
2450
|
-
for (const [colName, colBuilder] of columnEntries) {
|
|
2451
|
-
columnConfigs.set(colName, colBuilder.build().name);
|
|
2452
|
-
}
|
|
2541
|
+
// Performance: Use pre-computed column name map if available
|
|
2542
|
+
const columnNameMap = getColumnNameMapForSchema(this.targetTableSchema);
|
|
2453
2543
|
// Add columns
|
|
2454
|
-
for (const [colName, dbColumnName] of
|
|
2544
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
2455
2545
|
Object.defineProperty(mock, colName, {
|
|
2456
2546
|
get: () => ({
|
|
2457
2547
|
__fieldName: colName,
|
|
@@ -2500,14 +2590,8 @@ class CollectionQueryBuilder {
|
|
|
2500
2590
|
return mock;
|
|
2501
2591
|
}
|
|
2502
2592
|
else {
|
|
2503
|
-
// Fallback:
|
|
2504
|
-
|
|
2505
|
-
get: (target, prop) => ({
|
|
2506
|
-
__fieldName: prop,
|
|
2507
|
-
__dbColumnName: prop,
|
|
2508
|
-
}),
|
|
2509
|
-
};
|
|
2510
|
-
return new Proxy({}, handler);
|
|
2593
|
+
// Fallback: use the shared nested proxy that supports deep property access
|
|
2594
|
+
return createNestedFieldRefProxy(this.targetTable);
|
|
2511
2595
|
}
|
|
2512
2596
|
}
|
|
2513
2597
|
/**
|
|
@@ -2724,9 +2808,9 @@ class CollectionQueryBuilder {
|
|
|
2724
2808
|
else {
|
|
2725
2809
|
// No selector - select all fields from the target table schema
|
|
2726
2810
|
if (this.targetTableSchema && this.targetTableSchema.columns) {
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2811
|
+
// Performance: Use cached column name map
|
|
2812
|
+
const colNameMap = getColumnNameMapForSchema(this.targetTableSchema);
|
|
2813
|
+
for (const [colName, dbColumnName] of colNameMap) {
|
|
2730
2814
|
selectedFieldConfigs.push({
|
|
2731
2815
|
alias: colName,
|
|
2732
2816
|
expression: `"${dbColumnName}"`,
|
|
@@ -2756,13 +2840,11 @@ class CollectionQueryBuilder {
|
|
|
2756
2840
|
// Step 3: Build ORDER BY clause SQL (without ORDER BY keyword)
|
|
2757
2841
|
let orderByClause;
|
|
2758
2842
|
if (this.orderByFields.length > 0) {
|
|
2843
|
+
// Performance: Pre-compute column name map for ORDER BY lookups
|
|
2844
|
+
const colNameMap = this.targetTableSchema ? getColumnNameMapForSchema(this.targetTableSchema) : null;
|
|
2759
2845
|
const orderParts = this.orderByFields.map(({ field, direction }) => {
|
|
2760
|
-
// Look up the database column name from the
|
|
2761
|
-
|
|
2762
|
-
if (this.targetTableSchema && this.targetTableSchema.columns[field]) {
|
|
2763
|
-
const colBuilder = this.targetTableSchema.columns[field];
|
|
2764
|
-
dbColumnName = colBuilder.build().name;
|
|
2765
|
-
}
|
|
2846
|
+
// Look up the database column name from the cached map if available
|
|
2847
|
+
const dbColumnName = colNameMap?.get(field) ?? field;
|
|
2766
2848
|
return `"${dbColumnName}" ${direction}`;
|
|
2767
2849
|
});
|
|
2768
2850
|
orderByClause = orderParts.join(', ');
|