linkgress-orm 0.4.68 → 0.4.70
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/database/database-client.interface.d.ts +12 -0
- package/dist/database/database-client.interface.d.ts.map +1 -1
- package/dist/database/database-client.interface.js.map +1 -1
- package/dist/database/postgres-client.d.ts.map +1 -1
- package/dist/database/postgres-client.js +21 -9
- package/dist/database/postgres-client.js.map +1 -1
- package/dist/entity/db-context.d.ts +81 -5
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +94 -25
- package/dist/entity/db-context.js.map +1 -1
- package/dist/query/cte-builder.d.ts.map +1 -1
- package/dist/query/cte-builder.js +3 -2
- package/dist/query/cte-builder.js.map +1 -1
- package/dist/query/mock-row-cache.d.ts +9 -8
- package/dist/query/mock-row-cache.d.ts.map +1 -1
- package/dist/query/mock-row-cache.js +9 -8
- package/dist/query/mock-row-cache.js.map +1 -1
- package/dist/query/mutation-batch.d.ts.map +1 -1
- package/dist/query/mutation-batch.js +3 -1
- package/dist/query/mutation-batch.js.map +1 -1
- package/dist/query/query-builder.d.ts +36 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +346 -207
- package/dist/query/query-builder.js.map +1 -1
- package/dist/query/union-builder.d.ts.map +1 -1
- package/dist/query/union-builder.js +2 -1
- package/dist/query/union-builder.js.map +1 -1
- package/package.json +80 -80
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CollectionQueryBuilder = exports.ReferenceQueryBuilder = exports.SelectQueryBuilder = exports.QueryBuilder = void 0;
|
|
3
|
+
exports.CollectionQueryBuilder = exports.ReferenceQueryBuilder = exports.SelectQueryBuilder = exports.QueryBuilder = exports.materializeMockSelection = void 0;
|
|
4
4
|
exports.getColumnNameMapForSchema = getColumnNameMapForSchema;
|
|
5
5
|
exports.getRelationEntriesForSchema = getRelationEntriesForSchema;
|
|
6
|
+
exports.getSchemaColumnMeta = getSchemaColumnMeta;
|
|
6
7
|
exports.getTargetSchemaForRelation = getTargetSchemaForRelation;
|
|
7
8
|
exports.createNestedFieldRefProxy = createNestedFieldRefProxy;
|
|
8
9
|
const conditions_1 = require("./conditions");
|
|
@@ -59,6 +60,26 @@ function getRelationEntriesForSchema(schema) {
|
|
|
59
60
|
// Fallback: build the array (for schemas that weren't built with the new TableBuilder)
|
|
60
61
|
return Object.entries(schema.relations);
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Performance utility: per-schema column metadata (mapper + SQL type), computed once per
|
|
65
|
+
* schema object. `_createMockRow` and the collection mock-item builder both used to call
|
|
66
|
+
* `colBuilder.build()` for EVERY column on EVERY query build — pure waste for an immutable
|
|
67
|
+
* schema. WeakMap keyed by schema identity so schemas can be garbage-collected with their
|
|
68
|
+
* DbContext.
|
|
69
|
+
*/
|
|
70
|
+
const schemaColumnMetaCache = new WeakMap();
|
|
71
|
+
function getSchemaColumnMeta(schema) {
|
|
72
|
+
let meta = schemaColumnMetaCache.get(schema);
|
|
73
|
+
if (meta == null) {
|
|
74
|
+
meta = new Map();
|
|
75
|
+
for (const [colName, colBuilder] of Object.entries(schema.columns)) {
|
|
76
|
+
const config = colBuilder.build();
|
|
77
|
+
meta.set(colName, { mapper: config.mapper, type: config.type, name: config.name, primaryKey: config.primaryKey });
|
|
78
|
+
}
|
|
79
|
+
schemaColumnMetaCache.set(schema, meta);
|
|
80
|
+
}
|
|
81
|
+
return meta;
|
|
82
|
+
}
|
|
62
83
|
/**
|
|
63
84
|
* Mock-row descriptor cache for {@link ReferenceQueryBuilder.createMockTargetRow}.
|
|
64
85
|
*
|
|
@@ -77,14 +98,18 @@ function getRelationEntriesForSchema(schema) {
|
|
|
77
98
|
*/
|
|
78
99
|
const MOCK_ROW_FIELD_REFS = Symbol('linkgressMockFieldRefs');
|
|
79
100
|
const MOCK_ROW_NAV_CACHE = Symbol('linkgressMockNavCache');
|
|
101
|
+
const MOCK_ROW_CHAIN_ID = Symbol('linkgressMockChainId');
|
|
80
102
|
const navigationPathSignature = (path) => path
|
|
81
103
|
.map(step => `${step.alias}:${step.targetTable}:${(step.foreignKeys ?? []).join('+')}:${(step.matches ?? []).join('+')}:${step.isMandatory ? 1 : 0}:${step.sourceAlias ?? ''}`)
|
|
82
104
|
.join('>');
|
|
83
105
|
/**
|
|
84
|
-
* Whether `value` is a
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
106
|
+
* Whether `value` is a mock row minted by the query builders (`createMockTargetRow`,
|
|
107
|
+
* `SelectQueryBuilder._createMockRow`, `CollectionQueryBuilder.createMockItem`). Every
|
|
108
|
+
* such row INHERITS its column/relation getters from a shared prototype (see MockRowCache),
|
|
109
|
+
* so `Object.getPrototypeOf(row) !== Object.prototype` and own-property APIs (`Object.keys`,
|
|
110
|
+
* `{...row}`) see no columns — the row's own state slots are the reliable marker. Callers
|
|
111
|
+
* must treat a mock row like the plain-object mock it replaced (walk it for revivals,
|
|
112
|
+
* read properties directly) but never enumerate it with own-property APIs.
|
|
88
113
|
*/
|
|
89
114
|
const isReferenceMockRow = (value) => value != null && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, MOCK_ROW_FIELD_REFS);
|
|
90
115
|
/** `Object.getOwnPropertyDescriptor` that walks the prototype chain (stops before Object.prototype). */
|
|
@@ -99,10 +124,32 @@ const findPropertyDescriptor = (value, key) => {
|
|
|
99
124
|
}
|
|
100
125
|
return undefined;
|
|
101
126
|
};
|
|
127
|
+
/**
|
|
128
|
+
* Materializes a selector RESULT that is itself a mock row (identity selectors like
|
|
129
|
+
* `.select(p => p)` or `(l, r) => l`): such rows inherit their columns from the shared
|
|
130
|
+
* prototype, so own-property walkers (`Object.entries` / `keys` / `values`) would see
|
|
131
|
+
* NOTHING and silently project zero columns. Reading every enumerable column getter
|
|
132
|
+
* once into a plain object restores exactly what the pre-prototype mocks exposed (their
|
|
133
|
+
* own enumerable getters) — non-enumerable relation getters stay out, as before. No-op
|
|
134
|
+
* for every other result shape (FieldRef, SqlFragment, builder, plain object, array).
|
|
135
|
+
*/
|
|
136
|
+
const materializeMockSelection = (result) => {
|
|
137
|
+
if (!isReferenceMockRow(result)) {
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const key in result) {
|
|
142
|
+
if (findPropertyDescriptor(result, key)?.get != null) {
|
|
143
|
+
out[key] = result[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
};
|
|
148
|
+
exports.materializeMockSelection = materializeMockSelection;
|
|
102
149
|
/**
|
|
103
150
|
* First enumerable key (own or inherited) backed by a getter — the "is this a mock row" probe
|
|
104
|
-
* shared by the selection resolvers.
|
|
105
|
-
*
|
|
151
|
+
* shared by the selection resolvers. All mock rows (root, collection-item, reference)
|
|
152
|
+
* inherit their getters from a shared prototype; column getters are enumerable, so the first
|
|
106
153
|
* enumerable getter is always a column and `row[key]` yields its FieldRef.
|
|
107
154
|
*/
|
|
108
155
|
const findFirstGetterKey = (value) => {
|
|
@@ -299,37 +346,67 @@ class QueryBuilder {
|
|
|
299
346
|
if (this._cachedMockRow) {
|
|
300
347
|
return this._cachedMockRow;
|
|
301
348
|
}
|
|
302
|
-
|
|
349
|
+
// Prototype-level cache — same pattern as ReferenceQueryBuilder.createMockTargetRow
|
|
350
|
+
// (see MockRowCache): every getter of a root mock row is fully determined by the
|
|
351
|
+
// schema object, so every query over the same table can share ONE prebuilt prototype
|
|
352
|
+
// carrying all column/relation getters; a new row is `Object.create` plus its own
|
|
353
|
+
// state slots — O(1) instead of one property definition per column+relation per
|
|
354
|
+
// query (measurable CPU under load: the root-mock walk was the ORM's largest
|
|
355
|
+
// per-query-build item after 0.4.69). Per-query state (`chainId`, the FieldRef
|
|
356
|
+
// cache) lives in symbol-keyed slots read through `this`; relation getters still
|
|
357
|
+
// construct FRESH builders per access (memoizing would fuse repeated
|
|
358
|
+
// `.where()` chains). Opt-in via the same static switch; OFF = fresh prototype
|
|
359
|
+
// per row (the pre-0.4.70 behaviour). Consumers must not probe mock rows with
|
|
360
|
+
// OWN-property APIs (`Object.keys`, `{...row}`) — see `isReferenceMockRow`.
|
|
361
|
+
const prototype = mock_row_cache_1.MockRowCache.getOrBuild(`select|${this.schema.name}`, () => Object.defineProperties({}, this.buildRootMockDescriptors()));
|
|
362
|
+
const mock = Object.create(prototype);
|
|
363
|
+
mock[MOCK_ROW_FIELD_REFS] = {};
|
|
364
|
+
mock[MOCK_ROW_CHAIN_ID] = this.chainId;
|
|
365
|
+
// Cache the mock for reuse
|
|
366
|
+
this._cachedMockRow = mock;
|
|
367
|
+
return mock;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Builds the shared property-descriptor map for {@link _createMockRow}'s cached path.
|
|
371
|
+
* The getters read per-row state through `this`-bound symbol slots, so one descriptor
|
|
372
|
+
* map serves every root mock row of the same schema. Values captured at build time
|
|
373
|
+
* (column mappers/types, target schemas, the registry) are schema-constants identical
|
|
374
|
+
* for every row of the signature.
|
|
375
|
+
*/
|
|
376
|
+
buildRootMockDescriptors() {
|
|
303
377
|
const tableAlias = this.schema.name;
|
|
304
|
-
const chainId = this.chainId;
|
|
305
378
|
// Performance: Use pre-computed column name map if available
|
|
306
379
|
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
307
|
-
// Performance: Lazy-cache FieldRef objects - only create when first accessed
|
|
308
|
-
const fieldRefCache = {};
|
|
309
380
|
// Build a mapper lookup for columns (only when needed)
|
|
310
381
|
const columnMappers = {};
|
|
311
382
|
const columnSqlTypes = {};
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
383
|
+
// Per-schema mapper/type lookup, computed once — `colBuilder.build()` per column
|
|
384
|
+
// per row was measurable CPU under load (every query build walks every column
|
|
385
|
+
// twice: once here, once in the descriptors). Keyed by schema identity.
|
|
386
|
+
const schemaColumnMeta = getSchemaColumnMeta(this.schema);
|
|
387
|
+
for (const [colName, meta] of schemaColumnMeta) {
|
|
388
|
+
if (meta.mapper) {
|
|
389
|
+
columnMappers[colName] = meta.mapper;
|
|
316
390
|
}
|
|
317
|
-
if (
|
|
318
|
-
columnSqlTypes[colName] =
|
|
391
|
+
if (meta.type) {
|
|
392
|
+
columnSqlTypes[colName] = meta.type;
|
|
319
393
|
}
|
|
320
394
|
}
|
|
395
|
+
const descriptors = {};
|
|
321
396
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
322
397
|
for (const [colName, dbColumnName] of columnNameMap) {
|
|
323
398
|
const mapper = columnMappers[colName];
|
|
324
|
-
|
|
399
|
+
descriptors[colName] = {
|
|
325
400
|
get() {
|
|
401
|
+
const slots = this;
|
|
402
|
+
const fieldRefCache = slots[MOCK_ROW_FIELD_REFS] ?? (slots[MOCK_ROW_FIELD_REFS] = {});
|
|
326
403
|
let cached = fieldRefCache[colName];
|
|
327
404
|
if (!cached) {
|
|
328
405
|
cached = fieldRefCache[colName] = {
|
|
329
406
|
__fieldName: colName,
|
|
330
407
|
__dbColumnName: dbColumnName,
|
|
331
408
|
__tableAlias: tableAlias,
|
|
332
|
-
__chainId:
|
|
409
|
+
__chainId: slots[MOCK_ROW_CHAIN_ID],
|
|
333
410
|
// Include mapper for toDriver transformation in conditions
|
|
334
411
|
__mapper: mapper,
|
|
335
412
|
// Column SQL type — lets flag* emit width-exact mask casts
|
|
@@ -340,49 +417,51 @@ class QueryBuilder {
|
|
|
340
417
|
},
|
|
341
418
|
enumerable: true,
|
|
342
419
|
configurable: true,
|
|
343
|
-
}
|
|
420
|
+
};
|
|
344
421
|
}
|
|
345
422
|
// Performance: Use pre-computed relation entries and cached schemas
|
|
346
423
|
const relationEntries = getRelationEntriesForSchema(this.schema);
|
|
424
|
+
// Values captured at descriptor-build time — identical for every row of this
|
|
425
|
+
// signature (the registry is the process-wide schema registry).
|
|
426
|
+
const schemaRegistry = this.schemaRegistry;
|
|
427
|
+
const sourceTableName = this.schema.name;
|
|
347
428
|
// Add relations (both collections and single references)
|
|
348
429
|
for (const [relName, relConfig] of relationEntries) {
|
|
349
430
|
// Performance: Use cached target schema, but prefer registry lookup for full relations
|
|
350
|
-
let targetSchema =
|
|
431
|
+
let targetSchema = schemaRegistry?.get(relConfig.targetTable);
|
|
351
432
|
if (!targetSchema) {
|
|
352
433
|
targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
353
434
|
}
|
|
354
435
|
if (relConfig.type === 'many') {
|
|
355
436
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
356
|
-
|
|
437
|
+
descriptors[relName] = {
|
|
357
438
|
get: () => {
|
|
358
|
-
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '',
|
|
439
|
+
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', sourceTableName, targetSchema, schemaRegistry, // Pass schema registry for nested navigation resolution
|
|
359
440
|
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
360
441
|
relConfig.matches);
|
|
361
442
|
},
|
|
362
443
|
enumerable: false,
|
|
363
444
|
configurable: true,
|
|
364
|
-
}
|
|
445
|
+
};
|
|
365
446
|
}
|
|
366
447
|
else {
|
|
367
448
|
// Single reference navigation (many-to-one, one-to-one)
|
|
368
449
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow
|
|
369
450
|
// with circular relations like User->Posts->User)
|
|
370
|
-
|
|
451
|
+
descriptors[relName] = {
|
|
371
452
|
get: () => {
|
|
372
|
-
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema,
|
|
453
|
+
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema, schemaRegistry, // Pass schema registry for nested navigation resolution
|
|
373
454
|
[], // Empty navigation path for first level navigation
|
|
374
|
-
|
|
455
|
+
sourceTableName // Pass source table name for lateral join correlation
|
|
375
456
|
);
|
|
376
457
|
return refBuilder.createMockTargetRow();
|
|
377
458
|
},
|
|
378
459
|
enumerable: false,
|
|
379
460
|
configurable: true,
|
|
380
|
-
}
|
|
461
|
+
};
|
|
381
462
|
}
|
|
382
463
|
}
|
|
383
|
-
|
|
384
|
-
this._cachedMockRow = mock;
|
|
385
|
-
return mock;
|
|
464
|
+
return descriptors;
|
|
386
465
|
}
|
|
387
466
|
/**
|
|
388
467
|
* Add a LEFT JOIN to the query with a selector (supports both tables and subqueries)
|
|
@@ -426,7 +505,7 @@ class QueryBuilder {
|
|
|
426
505
|
// Create fresh mocks for the selector invocation
|
|
427
506
|
const freshMockLeft = createLeftMock();
|
|
428
507
|
const freshMockRight = createRightMock();
|
|
429
|
-
return selector(freshMockLeft, freshMockRight);
|
|
508
|
+
return (0, exports.materializeMockSelection)(selector(freshMockLeft, freshMockRight));
|
|
430
509
|
};
|
|
431
510
|
return new SelectQueryBuilder(this.schema, this.client, wrappedSelector, this.whereCond, this.limitValue, this.offsetValue, this.orderByFields, this.executor, updatedJoins, newJoinCounter, false, // isDistinct defaults to false
|
|
432
511
|
undefined, // schemaRegistry
|
|
@@ -475,7 +554,7 @@ class QueryBuilder {
|
|
|
475
554
|
// Create fresh mocks for the selector invocation
|
|
476
555
|
const freshMockLeft = createLeftMock();
|
|
477
556
|
const freshMockRight = createRightMock();
|
|
478
|
-
return selector(freshMockLeft, freshMockRight);
|
|
557
|
+
return (0, exports.materializeMockSelection)(selector(freshMockLeft, freshMockRight));
|
|
479
558
|
};
|
|
480
559
|
return new SelectQueryBuilder(this.schema, this.client, wrappedSelector, this.whereCond, this.limitValue, this.offsetValue, this.orderByFields, this.executor, updatedJoins, newJoinCounter, false, // isDistinct defaults to false
|
|
481
560
|
undefined, // schemaRegistry
|
|
@@ -561,7 +640,7 @@ class QueryBuilder {
|
|
|
561
640
|
}
|
|
562
641
|
orderBy(selector) {
|
|
563
642
|
const mockRow = this._createMockRow();
|
|
564
|
-
const result = selector(mockRow);
|
|
643
|
+
const result = (0, exports.materializeMockSelection)(selector(mockRow));
|
|
565
644
|
(0, query_utils_1.parseOrderBy)(result, this.orderByFields);
|
|
566
645
|
return this;
|
|
567
646
|
}
|
|
@@ -631,7 +710,7 @@ class SelectQueryBuilder {
|
|
|
631
710
|
select(selector) {
|
|
632
711
|
// Create a composed selector that applies both transformations
|
|
633
712
|
const composedSelector = (row) => {
|
|
634
|
-
const firstResult = this.selector(row);
|
|
713
|
+
const firstResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
635
714
|
return selector(firstResult);
|
|
636
715
|
};
|
|
637
716
|
return new SelectQueryBuilder(this.schema, this.client, composedSelector, this.whereCond, this.limitValue, this.offsetValue, this.orderByFields, this.executor, this.manualJoins, this.joinCounter, this.isDistinct, this.schemaRegistry, this.ctes, this.collectionStrategy, this.chainId);
|
|
@@ -679,7 +758,7 @@ class SelectQueryBuilder {
|
|
|
679
758
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
680
759
|
this.joinCounter = this.joinCounter + 1;
|
|
681
760
|
const mockRow = this._createMockRow();
|
|
682
|
-
const selectedMock = this.selector(mockRow);
|
|
761
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
683
762
|
const leftMock = this.createFieldRefProxy(selectedMock, true);
|
|
684
763
|
const rightMock = this.createMockRowForTable(rightSchema, rightAlias);
|
|
685
764
|
const onCondition = on(leftMock, rightMock);
|
|
@@ -708,7 +787,7 @@ class SelectQueryBuilder {
|
|
|
708
787
|
*/
|
|
709
788
|
addCteFilterJoin(type, cte, on, filter) {
|
|
710
789
|
const mockRow = this._createMockRow();
|
|
711
|
-
const selectedMock = this.selector(mockRow);
|
|
790
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
712
791
|
const leftMock = this.createFieldRefProxy(selectedMock, true);
|
|
713
792
|
const rightMock = this.createMockRowForCte(cte);
|
|
714
793
|
const onCondition = on(leftMock, rightMock);
|
|
@@ -735,7 +814,7 @@ class SelectQueryBuilder {
|
|
|
735
814
|
where(condition) {
|
|
736
815
|
const mockRow = this._createMockRow();
|
|
737
816
|
// Apply the selector to get the selected shape that the user sees in the WHERE condition
|
|
738
|
-
const selectedMock = this.selector(mockRow);
|
|
817
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
739
818
|
// Wrap in proxy - for WHERE, we preserve original column names
|
|
740
819
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock, true);
|
|
741
820
|
const newCondition = condition(fieldRefProxy);
|
|
@@ -825,7 +904,7 @@ class SelectQueryBuilder {
|
|
|
825
904
|
}
|
|
826
905
|
orderBy(selector) {
|
|
827
906
|
const mockRow = this._createMockRow();
|
|
828
|
-
const selectedMock = this.selector(mockRow);
|
|
907
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
829
908
|
// Wrap selectedMock in a proxy that returns FieldRefs for property access
|
|
830
909
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock);
|
|
831
910
|
const result = selector(fieldRefProxy);
|
|
@@ -857,7 +936,7 @@ class SelectQueryBuilder {
|
|
|
857
936
|
const newJoinCounter = this.joinCounter + 1;
|
|
858
937
|
// Create mock for the current selection (left side)
|
|
859
938
|
const mockRow = this._createMockRow();
|
|
860
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
939
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
861
940
|
// Create mock for the subquery result (right side)
|
|
862
941
|
// For subqueries, we create a mock based on the result type
|
|
863
942
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
@@ -875,7 +954,7 @@ class SelectQueryBuilder {
|
|
|
875
954
|
}];
|
|
876
955
|
// Create a new selector
|
|
877
956
|
const composedSelector = (row) => {
|
|
878
|
-
const leftResult = this.selector(row);
|
|
957
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
879
958
|
const freshMockRight = this.createMockRowForSubquery(alias, subquery);
|
|
880
959
|
return selector(leftResult, freshMockRight);
|
|
881
960
|
};
|
|
@@ -901,7 +980,7 @@ class SelectQueryBuilder {
|
|
|
901
980
|
// Create mock for the current selection (left side)
|
|
902
981
|
// IMPORTANT: We call the selector with the mock row to get a result that contains FieldRef objects
|
|
903
982
|
const mockRow = this._createMockRow();
|
|
904
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
983
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
905
984
|
// The mockLeftSelection now contains FieldRef objects (with __fieldName, __dbColumnName, __tableAlias)
|
|
906
985
|
// These FieldRef objects preserve the table context
|
|
907
986
|
// Create mock for the right table
|
|
@@ -919,7 +998,7 @@ class SelectQueryBuilder {
|
|
|
919
998
|
}];
|
|
920
999
|
// Create a new selector that first applies the current selector, then the new selector
|
|
921
1000
|
const composedSelector = (row) => {
|
|
922
|
-
const leftResult = this.selector(row);
|
|
1001
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
923
1002
|
const freshMockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
924
1003
|
return selector(leftResult, freshMockRight);
|
|
925
1004
|
};
|
|
@@ -932,7 +1011,7 @@ class SelectQueryBuilder {
|
|
|
932
1011
|
const newJoinCounter = this.joinCounter + 1;
|
|
933
1012
|
// Create mock for the current selection (left side)
|
|
934
1013
|
const mockRow = this._createMockRow();
|
|
935
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1014
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
936
1015
|
// Create mock for the CTE columns (right side)
|
|
937
1016
|
const mockRight = this.createMockRowForCte(cte);
|
|
938
1017
|
// Evaluate the join condition
|
|
@@ -948,7 +1027,7 @@ class SelectQueryBuilder {
|
|
|
948
1027
|
}];
|
|
949
1028
|
// Create a new selector
|
|
950
1029
|
const composedSelector = (row) => {
|
|
951
|
-
const leftResult = this.selector(row);
|
|
1030
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
952
1031
|
const freshMockRight = this.createMockRowForCte(cte);
|
|
953
1032
|
return selector(leftResult, freshMockRight);
|
|
954
1033
|
};
|
|
@@ -965,7 +1044,7 @@ class SelectQueryBuilder {
|
|
|
965
1044
|
const newJoinCounter = this.joinCounter + 1;
|
|
966
1045
|
// Create mock for the current selection (left side)
|
|
967
1046
|
const mockRow = this._createMockRow();
|
|
968
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1047
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
969
1048
|
// Create mock for the subquery result (right side)
|
|
970
1049
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
971
1050
|
// Evaluate the join condition
|
|
@@ -982,7 +1061,7 @@ class SelectQueryBuilder {
|
|
|
982
1061
|
}];
|
|
983
1062
|
// Create a new selector
|
|
984
1063
|
const composedSelector = (row) => {
|
|
985
|
-
const leftResult = this.selector(row);
|
|
1064
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
986
1065
|
const freshMockRight = this.createMockRowForSubquery(alias, subquery);
|
|
987
1066
|
return selector(leftResult, freshMockRight);
|
|
988
1067
|
};
|
|
@@ -1007,7 +1086,7 @@ class SelectQueryBuilder {
|
|
|
1007
1086
|
const newJoinCounter = this.joinCounter + 1;
|
|
1008
1087
|
// Create mock for the current selection (left side)
|
|
1009
1088
|
const mockRow = this._createMockRow();
|
|
1010
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1089
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1011
1090
|
// Create mock for the right table
|
|
1012
1091
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
1013
1092
|
// Evaluate the join condition
|
|
@@ -1022,7 +1101,7 @@ class SelectQueryBuilder {
|
|
|
1022
1101
|
}];
|
|
1023
1102
|
// Create a new selector that first applies the current selector, then the new selector
|
|
1024
1103
|
const composedSelector = (row) => {
|
|
1025
|
-
const leftResult = this.selector(row);
|
|
1104
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
1026
1105
|
const freshMockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
1027
1106
|
return selector(leftResult, freshMockRight);
|
|
1028
1107
|
};
|
|
@@ -1201,7 +1280,7 @@ class SelectQueryBuilder {
|
|
|
1201
1280
|
*/
|
|
1202
1281
|
selectDistinct(selector) {
|
|
1203
1282
|
const composedSelector = (row) => {
|
|
1204
|
-
const firstResult = this.selector(row);
|
|
1283
|
+
const firstResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
1205
1284
|
return selector(firstResult);
|
|
1206
1285
|
};
|
|
1207
1286
|
return new SelectQueryBuilder(this.schema, this.client, composedSelector, this.whereCond, this.limitValue, this.offsetValue, this.orderByFields, this.executor, this.manualJoins, this.joinCounter, true, // Set isDistinct to true
|
|
@@ -1223,13 +1302,13 @@ class SelectQueryBuilder {
|
|
|
1223
1302
|
let fieldToAggregate;
|
|
1224
1303
|
if (selector) {
|
|
1225
1304
|
const mockRow = this._createMockRow();
|
|
1226
|
-
const mockSelection = this.selector(mockRow);
|
|
1305
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1227
1306
|
fieldToAggregate = selector(mockSelection);
|
|
1228
1307
|
}
|
|
1229
1308
|
else {
|
|
1230
1309
|
// No selector - use the current selection
|
|
1231
1310
|
const mockRow = this._createMockRow();
|
|
1232
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1311
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1233
1312
|
}
|
|
1234
1313
|
// Build aggregation query
|
|
1235
1314
|
const { sql, params } = this.buildAggregationQuery('MIN', fieldToAggregate, context);
|
|
@@ -1255,13 +1334,13 @@ class SelectQueryBuilder {
|
|
|
1255
1334
|
let fieldToAggregate;
|
|
1256
1335
|
if (selector) {
|
|
1257
1336
|
const mockRow = this._createMockRow();
|
|
1258
|
-
const mockSelection = this.selector(mockRow);
|
|
1337
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1259
1338
|
fieldToAggregate = selector(mockSelection);
|
|
1260
1339
|
}
|
|
1261
1340
|
else {
|
|
1262
1341
|
// No selector - use the current selection
|
|
1263
1342
|
const mockRow = this._createMockRow();
|
|
1264
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1343
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1265
1344
|
}
|
|
1266
1345
|
// Build aggregation query
|
|
1267
1346
|
const { sql, params } = this.buildAggregationQuery('MAX', fieldToAggregate, context);
|
|
@@ -1287,13 +1366,13 @@ class SelectQueryBuilder {
|
|
|
1287
1366
|
let fieldToAggregate;
|
|
1288
1367
|
if (selector) {
|
|
1289
1368
|
const mockRow = this._createMockRow();
|
|
1290
|
-
const mockSelection = this.selector(mockRow);
|
|
1369
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1291
1370
|
fieldToAggregate = selector(mockSelection);
|
|
1292
1371
|
}
|
|
1293
1372
|
else {
|
|
1294
1373
|
// No selector - use the current selection
|
|
1295
1374
|
const mockRow = this._createMockRow();
|
|
1296
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1375
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1297
1376
|
}
|
|
1298
1377
|
// Build aggregation query
|
|
1299
1378
|
const { sql, params } = this.buildAggregationQuery('SUM', fieldToAggregate, context);
|
|
@@ -1341,7 +1420,7 @@ class SelectQueryBuilder {
|
|
|
1341
1420
|
executor: this.executor,
|
|
1342
1421
|
}));
|
|
1343
1422
|
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1344
|
-
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1423
|
+
const selectionResult = tracer.trace('evaluateSelector', () => (0, exports.materializeMockSelection)(this.selector(mockRow)));
|
|
1345
1424
|
const { sql, params, nestedPaths } = tracer.trace('buildQuery', () => this.buildQuery(selectionResult, context));
|
|
1346
1425
|
tracer.endPhase();
|
|
1347
1426
|
tracer.startPhase('queryExecution');
|
|
@@ -1449,7 +1528,7 @@ class SelectQueryBuilder {
|
|
|
1449
1528
|
hoistedCteNames: context.hoistedCteNames,
|
|
1450
1529
|
};
|
|
1451
1530
|
const mockRow = this._createMockRow();
|
|
1452
|
-
const selectionResult = this.selector(mockRow);
|
|
1531
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1453
1532
|
// Build query without ORDER BY, LIMIT, OFFSET for union component
|
|
1454
1533
|
const { sql, nestedPaths } = this.buildQueryCore(selectionResult, queryContext, false);
|
|
1455
1534
|
// Update context's param counter
|
|
@@ -1517,7 +1596,7 @@ class SelectQueryBuilder {
|
|
|
1517
1596
|
executor: this.executor,
|
|
1518
1597
|
};
|
|
1519
1598
|
const mockRow = this._createMockRow();
|
|
1520
|
-
const selectionResult = this.selector(mockRow);
|
|
1599
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1521
1600
|
const { sql, params, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
1522
1601
|
// Create transform function that captures current state
|
|
1523
1602
|
const transformFn = (rows) => {
|
|
@@ -1564,7 +1643,7 @@ class SelectQueryBuilder {
|
|
|
1564
1643
|
executor: this.executor,
|
|
1565
1644
|
};
|
|
1566
1645
|
const mockRow = this._createMockRow();
|
|
1567
|
-
const selectionResult = this.selector(mockRow);
|
|
1646
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1568
1647
|
const { sql, params, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
1569
1648
|
// Restore original limit
|
|
1570
1649
|
this.limitValue = originalLimit;
|
|
@@ -1793,7 +1872,7 @@ class SelectQueryBuilder {
|
|
|
1793
1872
|
}));
|
|
1794
1873
|
// Analyze the selector to extract nested queries
|
|
1795
1874
|
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1796
|
-
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1875
|
+
const selectionResult = tracer.trace('evaluateSelector', () => (0, exports.materializeMockSelection)(this.selector(mockRow)));
|
|
1797
1876
|
// Check if we're using temp table strategy and have collections
|
|
1798
1877
|
const collections = tracer.trace('detectCollections', () => this.detectCollections(selectionResult));
|
|
1799
1878
|
const useTempTableStrategy = this.collectionStrategy === 'temptable' && collections.length > 0;
|
|
@@ -2055,7 +2134,7 @@ class SelectQueryBuilder {
|
|
|
2055
2134
|
'jsonb',
|
|
2056
2135
|
]);
|
|
2057
2136
|
try {
|
|
2058
|
-
const selected = b.selector(b.createMockItem());
|
|
2137
|
+
const selected = (0, exports.materializeMockSelection)(b.selector(b.createMockItem()));
|
|
2059
2138
|
for (const value of Object.values(selected)) {
|
|
2060
2139
|
if (!(value && typeof value === 'object' && '__dbColumnName' in value)) {
|
|
2061
2140
|
return false;
|
|
@@ -2120,10 +2199,9 @@ class SelectQueryBuilder {
|
|
|
2120
2199
|
// Find primary key column from schema, fallback to "id" if not found
|
|
2121
2200
|
let pkColumn = null;
|
|
2122
2201
|
if (targetSchema) {
|
|
2123
|
-
for (const
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
pkColumn = config.name;
|
|
2202
|
+
for (const meta of getSchemaColumnMeta(targetSchema).values()) {
|
|
2203
|
+
if (meta.primaryKey && meta.name != null) {
|
|
2204
|
+
pkColumn = meta.name;
|
|
2127
2205
|
break;
|
|
2128
2206
|
}
|
|
2129
2207
|
}
|
|
@@ -2504,7 +2582,7 @@ class SelectQueryBuilder {
|
|
|
2504
2582
|
};
|
|
2505
2583
|
// Analyze the selector to extract nested queries
|
|
2506
2584
|
const mockRow = this._createMockRow();
|
|
2507
|
-
const selectionResult = this.selector(mockRow);
|
|
2585
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2508
2586
|
// Build the query - this populates context.placeholders
|
|
2509
2587
|
const { sql, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
2510
2588
|
// Create transform function (closure over schema, selection, nestedPaths)
|
|
@@ -2893,7 +2971,7 @@ class SelectQueryBuilder {
|
|
|
2893
2971
|
}
|
|
2894
2972
|
// Selector function - extract selected columns
|
|
2895
2973
|
const mockRow = this._createMockRow();
|
|
2896
|
-
const selectedMock = this.selector(mockRow);
|
|
2974
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2897
2975
|
const selection = returning(selectedMock);
|
|
2898
2976
|
if (typeof selection === 'object' && selection !== null) {
|
|
2899
2977
|
const columns = [];
|
|
@@ -2933,12 +3011,14 @@ class SelectQueryBuilder {
|
|
|
2933
3011
|
mapDeleteReturningResults(rows, returning, fragmentMappers) {
|
|
2934
3012
|
if (returning === true) {
|
|
2935
3013
|
// Full entity mapping - apply fromDriver mappers
|
|
3014
|
+
// getSchemaColumnMeta: `colBuilder.build()` per column PER ROW was pure waste for an
|
|
3015
|
+
// immutable schema — the per-schema meta map is computed once.
|
|
3016
|
+
const columnMeta = getSchemaColumnMeta(this.schema);
|
|
2936
3017
|
return rows.map(row => {
|
|
2937
3018
|
const mapped = {};
|
|
2938
|
-
for (const [propName,
|
|
2939
|
-
const
|
|
2940
|
-
|
|
2941
|
-
mapped[propName] = config.mapper ? config.mapper.fromDriver(dbValue) : dbValue;
|
|
3019
|
+
for (const [propName, meta] of columnMeta) {
|
|
3020
|
+
const dbValue = row[meta.name];
|
|
3021
|
+
mapped[propName] = meta.mapper ? meta.mapper.fromDriver(dbValue) : dbValue;
|
|
2942
3022
|
}
|
|
2943
3023
|
return mapped;
|
|
2944
3024
|
});
|
|
@@ -2954,15 +3034,13 @@ class SelectQueryBuilder {
|
|
|
2954
3034
|
continue;
|
|
2955
3035
|
}
|
|
2956
3036
|
// Try to find column by alias or name
|
|
2957
|
-
const colEntry =
|
|
2958
|
-
|
|
2959
|
-
return propName === key || config.name === key;
|
|
3037
|
+
const colEntry = [...getSchemaColumnMeta(this.schema)].find(([propName, meta]) => {
|
|
3038
|
+
return propName === key || meta.name === key;
|
|
2960
3039
|
});
|
|
2961
3040
|
if (colEntry) {
|
|
2962
|
-
const [,
|
|
2963
|
-
const config = col.build();
|
|
3041
|
+
const [, meta] = colEntry;
|
|
2964
3042
|
// Apply fromDriver mapper if present
|
|
2965
|
-
mapped[key] =
|
|
3043
|
+
mapped[key] = meta.mapper ? meta.mapper.fromDriver(value) : value;
|
|
2966
3044
|
}
|
|
2967
3045
|
else {
|
|
2968
3046
|
mapped[key] = value;
|
|
@@ -2983,7 +3061,7 @@ class SelectQueryBuilder {
|
|
|
2983
3061
|
}
|
|
2984
3062
|
// Analyze the returning selector
|
|
2985
3063
|
const mockRow = this._createMockRow();
|
|
2986
|
-
const selectedMock = this.selector(mockRow);
|
|
3064
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2987
3065
|
const selection = returning(selectedMock);
|
|
2988
3066
|
if (typeof selection !== 'object' || selection === null) {
|
|
2989
3067
|
return null;
|
|
@@ -3071,7 +3149,7 @@ class SelectQueryBuilder {
|
|
|
3071
3149
|
const selectParts = [];
|
|
3072
3150
|
const nestedPaths = new Set();
|
|
3073
3151
|
const mockRow = this._createMockRow();
|
|
3074
|
-
const selectedMock = this.selector(mockRow);
|
|
3152
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
3075
3153
|
const selection = returning(selectedMock);
|
|
3076
3154
|
// Helper to get FK db column name from a schema
|
|
3077
3155
|
const getFkDbColumnName = (sourceSchema, fkPropName) => {
|
|
@@ -3340,37 +3418,61 @@ ${joinClauses.join('\n')}`;
|
|
|
3340
3418
|
* @internal
|
|
3341
3419
|
*/
|
|
3342
3420
|
_createMockRow() {
|
|
3343
|
-
|
|
3421
|
+
// Prototype-level cache — same pattern as the SelectQueryBuilder mock (see
|
|
3422
|
+
// MockRowCache). The signature includes the manual-join shape because join
|
|
3423
|
+
// sub-objects are part of the mock's surface; joined queries carry a per-query
|
|
3424
|
+
// alias counter, so their signatures are effectively unique and they degrade to
|
|
3425
|
+
// the uncached cost (built and discarded per row), while the far more common
|
|
3426
|
+
// join-less `qb|<table>` signature hits the shared prototype. Per-query state
|
|
3427
|
+
// (`chainId`, FieldRef cache, join sub-objects) lives in symbol-keyed slots read
|
|
3428
|
+
// through `this`. Opt-in via the same static switch; OFF = fresh prototype per row
|
|
3429
|
+
// (the pre-0.4.70 behaviour).
|
|
3430
|
+
const joinSig = this.manualJoins
|
|
3431
|
+
.filter(j => !j.isSubquery && j.schema)
|
|
3432
|
+
.map(j => `${j.alias}:${j.schema.name}`)
|
|
3433
|
+
.join('+');
|
|
3434
|
+
const prototype = mock_row_cache_1.MockRowCache.getOrBuild(`qb|${this.schema.name}|${joinSig}`, () => Object.defineProperties({}, this.buildRootMockDescriptors()));
|
|
3435
|
+
const mock = Object.create(prototype);
|
|
3436
|
+
mock[MOCK_ROW_FIELD_REFS] = {};
|
|
3437
|
+
mock[MOCK_ROW_NAV_CACHE] = {};
|
|
3438
|
+
mock[MOCK_ROW_CHAIN_ID] = this.chainId;
|
|
3439
|
+
return mock;
|
|
3440
|
+
}
|
|
3441
|
+
/**
|
|
3442
|
+
* Builds the shared property-descriptor map for {@link _createMockRow}'s cached path.
|
|
3443
|
+
* The getters read per-row state through `this`-bound symbol slots; values captured at
|
|
3444
|
+
* build time are signature-constants identical for every row of the signature.
|
|
3445
|
+
*/
|
|
3446
|
+
buildRootMockDescriptors() {
|
|
3344
3447
|
const tableAlias = this.schema.name;
|
|
3345
|
-
const chainId = this.chainId;
|
|
3346
3448
|
// Performance: Use pre-computed column name map if available
|
|
3347
3449
|
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
3348
|
-
// Performance: Lazy-cache FieldRef objects
|
|
3349
|
-
const fieldRefCache = {};
|
|
3350
3450
|
// Build a mapper lookup for columns (only when needed)
|
|
3351
3451
|
const columnMappers = {};
|
|
3352
3452
|
const columnSqlTypes = {};
|
|
3353
|
-
for (const [colName,
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
columnMappers[colName] = config.mapper;
|
|
3453
|
+
for (const [colName, meta] of getSchemaColumnMeta(this.schema)) {
|
|
3454
|
+
if (meta.mapper) {
|
|
3455
|
+
columnMappers[colName] = meta.mapper;
|
|
3357
3456
|
}
|
|
3358
|
-
if (
|
|
3359
|
-
columnSqlTypes[colName] =
|
|
3457
|
+
if (meta.type) {
|
|
3458
|
+
columnSqlTypes[colName] = meta.type;
|
|
3360
3459
|
}
|
|
3361
3460
|
}
|
|
3461
|
+
const descriptors = {};
|
|
3362
3462
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
3363
3463
|
for (const [colName, dbColumnName] of columnNameMap) {
|
|
3364
3464
|
const mapper = columnMappers[colName];
|
|
3365
|
-
|
|
3465
|
+
descriptors[colName] = {
|
|
3366
3466
|
get() {
|
|
3467
|
+
const slots = this;
|
|
3468
|
+
const fieldRefCache = slots[MOCK_ROW_FIELD_REFS] ?? (slots[MOCK_ROW_FIELD_REFS] = {});
|
|
3367
3469
|
let cached = fieldRefCache[colName];
|
|
3368
3470
|
if (!cached) {
|
|
3369
3471
|
cached = fieldRefCache[colName] = {
|
|
3370
3472
|
__fieldName: colName,
|
|
3371
3473
|
__dbColumnName: dbColumnName,
|
|
3372
3474
|
__tableAlias: tableAlias,
|
|
3373
|
-
__chainId:
|
|
3475
|
+
__chainId: slots[MOCK_ROW_CHAIN_ID],
|
|
3374
3476
|
// Include mapper for toDriver transformation in conditions
|
|
3375
3477
|
__mapper: mapper,
|
|
3376
3478
|
// Column SQL type — lets flag* emit width-exact mask casts
|
|
@@ -3381,48 +3483,66 @@ ${joinClauses.join('\n')}`;
|
|
|
3381
3483
|
},
|
|
3382
3484
|
enumerable: true,
|
|
3383
3485
|
configurable: true,
|
|
3384
|
-
}
|
|
3486
|
+
};
|
|
3385
3487
|
}
|
|
3488
|
+
// Captured at descriptor-build time — identical for every row of this signature.
|
|
3489
|
+
const manualJoins = this.manualJoins;
|
|
3386
3490
|
// Add columns from manually joined tables
|
|
3387
|
-
for (const join of
|
|
3491
|
+
for (const join of manualJoins) {
|
|
3388
3492
|
// Skip subquery joins (they don't have a schema)
|
|
3389
3493
|
if (join.isSubquery || !join.schema) {
|
|
3390
3494
|
continue;
|
|
3391
3495
|
}
|
|
3392
|
-
//
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
}
|
|
3397
|
-
// Lazy-cache for joined table
|
|
3398
|
-
const joinFieldRefCache = {};
|
|
3496
|
+
// The join sub-object is built lazily per row (its FieldRef cache and identity are
|
|
3497
|
+
// row-scoped, exactly as the pre-prototype own-property sub-object was) and
|
|
3498
|
+
// memoized in the row's navigation slot so repeated accesses share one object.
|
|
3499
|
+
const joinSchema = join.schema;
|
|
3399
3500
|
const joinAlias = join.alias;
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3501
|
+
descriptors[joinAlias] = {
|
|
3502
|
+
get() {
|
|
3503
|
+
const slots = this;
|
|
3504
|
+
const navCache = slots[MOCK_ROW_NAV_CACHE] ?? (slots[MOCK_ROW_NAV_CACHE] = {});
|
|
3505
|
+
let sub = navCache[joinAlias];
|
|
3506
|
+
if (sub === undefined) {
|
|
3507
|
+
sub = navCache[joinAlias] = {};
|
|
3508
|
+
const joinColumnNameMap = getColumnNameMapForSchema(joinSchema);
|
|
3509
|
+
const joinFieldRefCache = {};
|
|
3510
|
+
for (const [colName, dbColumnName] of joinColumnNameMap) {
|
|
3511
|
+
Object.defineProperty(sub, colName, {
|
|
3512
|
+
get() {
|
|
3513
|
+
let cached = joinFieldRefCache[colName];
|
|
3514
|
+
if (!cached) {
|
|
3515
|
+
cached = joinFieldRefCache[colName] = {
|
|
3516
|
+
__fieldName: colName,
|
|
3517
|
+
__dbColumnName: dbColumnName,
|
|
3518
|
+
__tableAlias: joinAlias,
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
return cached;
|
|
3522
|
+
},
|
|
3523
|
+
enumerable: true,
|
|
3524
|
+
configurable: true,
|
|
3525
|
+
});
|
|
3410
3526
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
}
|
|
3527
|
+
}
|
|
3528
|
+
return sub;
|
|
3529
|
+
},
|
|
3530
|
+
enumerable: true,
|
|
3531
|
+
configurable: true,
|
|
3532
|
+
};
|
|
3417
3533
|
}
|
|
3418
3534
|
// Performance: Use pre-computed relation entries
|
|
3419
3535
|
const relationEntries = getRelationEntriesForSchema(this.schema);
|
|
3536
|
+
// Values captured at descriptor-build time — identical for every row of this
|
|
3537
|
+
// signature (the registry is the process-wide schema registry).
|
|
3538
|
+
const schemaRegistry = this.schemaRegistry;
|
|
3539
|
+
const sourceTableName = this.schema.name;
|
|
3420
3540
|
// Add relations as CollectionQueryBuilder or ReferenceQueryBuilder
|
|
3421
3541
|
for (const [relName, relConfig] of relationEntries) {
|
|
3422
3542
|
// Try to get target schema from registry (preferred, has full relations) or cached schema
|
|
3423
3543
|
let targetSchema;
|
|
3424
|
-
if (
|
|
3425
|
-
targetSchema =
|
|
3544
|
+
if (schemaRegistry) {
|
|
3545
|
+
targetSchema = schemaRegistry.get(relConfig.targetTable);
|
|
3426
3546
|
}
|
|
3427
3547
|
if (!targetSchema) {
|
|
3428
3548
|
// Performance: Use cached target schema
|
|
@@ -3430,36 +3550,36 @@ ${joinClauses.join('\n')}`;
|
|
|
3430
3550
|
}
|
|
3431
3551
|
if (relConfig.type === 'many') {
|
|
3432
3552
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3433
|
-
|
|
3553
|
+
descriptors[relName] = {
|
|
3434
3554
|
get: () => {
|
|
3435
|
-
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '',
|
|
3436
|
-
|
|
3555
|
+
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '', sourceTableName, targetSchema, // Pass the target schema directly
|
|
3556
|
+
schemaRegistry, // Pass schema registry for nested resolution
|
|
3437
3557
|
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
3438
3558
|
relConfig.matches);
|
|
3439
3559
|
},
|
|
3440
3560
|
enumerable: false,
|
|
3441
3561
|
configurable: true,
|
|
3442
|
-
}
|
|
3562
|
+
};
|
|
3443
3563
|
}
|
|
3444
3564
|
else {
|
|
3445
3565
|
// For single reference (many-to-one), create a ReferenceQueryBuilder
|
|
3446
3566
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3447
|
-
|
|
3567
|
+
descriptors[relName] = {
|
|
3448
3568
|
get: () => {
|
|
3449
3569
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema, // Pass the target schema directly
|
|
3450
|
-
|
|
3570
|
+
schemaRegistry, // Pass schema registry for nested resolution
|
|
3451
3571
|
[], // Empty navigation path for first level navigation
|
|
3452
|
-
|
|
3572
|
+
sourceTableName // Pass source table name for lateral join correlation
|
|
3453
3573
|
);
|
|
3454
3574
|
// Return a mock object that exposes the target table's columns
|
|
3455
3575
|
return refBuilder.createMockTargetRow();
|
|
3456
3576
|
},
|
|
3457
3577
|
enumerable: false,
|
|
3458
3578
|
configurable: true,
|
|
3459
|
-
}
|
|
3579
|
+
};
|
|
3460
3580
|
}
|
|
3461
3581
|
}
|
|
3462
|
-
return
|
|
3582
|
+
return descriptors;
|
|
3463
3583
|
}
|
|
3464
3584
|
/**
|
|
3465
3585
|
* Create a proxy that wraps selected values and returns FieldRefs for property access
|
|
@@ -5504,7 +5624,7 @@ ${joinClauses.join('\n')}`;
|
|
|
5504
5624
|
};
|
|
5505
5625
|
// Analyze the selector to extract nested queries
|
|
5506
5626
|
const mockRow = this._createMockRow();
|
|
5507
|
-
const selectionResult = this.selector(mockRow);
|
|
5627
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
5508
5628
|
// Build the query
|
|
5509
5629
|
const { sql } = this.buildQuery(selectionResult, context);
|
|
5510
5630
|
// Update the outer context's param counter
|
|
@@ -5665,13 +5785,12 @@ class ReferenceQueryBuilder {
|
|
|
5665
5785
|
// Build a mapper lookup for columns (only when needed)
|
|
5666
5786
|
const columnMappers = {};
|
|
5667
5787
|
const columnSqlTypes = {};
|
|
5668
|
-
for (const [colName,
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
columnMappers[colName] = config.mapper;
|
|
5788
|
+
for (const [colName, meta] of getSchemaColumnMeta(this.targetTableSchema)) {
|
|
5789
|
+
if (meta.mapper) {
|
|
5790
|
+
columnMappers[colName] = meta.mapper;
|
|
5672
5791
|
}
|
|
5673
|
-
if (
|
|
5674
|
-
columnSqlTypes[colName] =
|
|
5792
|
+
if (meta.type) {
|
|
5793
|
+
columnSqlTypes[colName] = meta.type;
|
|
5675
5794
|
}
|
|
5676
5795
|
}
|
|
5677
5796
|
const sourceTable = this.targetTable; // Actual table name for schema lookup
|
|
@@ -5878,74 +5997,16 @@ class CollectionQueryBuilder {
|
|
|
5878
5997
|
return this._cachedMockItem;
|
|
5879
5998
|
}
|
|
5880
5999
|
if (this.targetTableSchema) {
|
|
5881
|
-
//
|
|
5882
|
-
|
|
5883
|
-
//
|
|
5884
|
-
|
|
5885
|
-
//
|
|
5886
|
-
|
|
5887
|
-
//
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
const tableAlias = `__collection_${this.targetTable}__`;
|
|
5892
|
-
for (const [colName, dbColumnName] of columnNameMap) {
|
|
5893
|
-
Object.defineProperty(mock, colName, {
|
|
5894
|
-
get() {
|
|
5895
|
-
let cached = fieldRefCache[colName];
|
|
5896
|
-
if (!cached) {
|
|
5897
|
-
cached = fieldRefCache[colName] = {
|
|
5898
|
-
__fieldName: colName,
|
|
5899
|
-
__dbColumnName: dbColumnName,
|
|
5900
|
-
__tableAlias: tableAlias, // Include table alias for unambiguous references
|
|
5901
|
-
};
|
|
5902
|
-
}
|
|
5903
|
-
return cached;
|
|
5904
|
-
},
|
|
5905
|
-
enumerable: true,
|
|
5906
|
-
configurable: true,
|
|
5907
|
-
});
|
|
5908
|
-
}
|
|
5909
|
-
// Add navigation properties (both collections and references)
|
|
5910
|
-
if (this.targetTableSchema.relations) {
|
|
5911
|
-
for (const [relName, relConfig] of Object.entries(this.targetTableSchema.relations)) {
|
|
5912
|
-
if (relConfig.type === 'many') {
|
|
5913
|
-
// Collection navigation
|
|
5914
|
-
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
5915
|
-
Object.defineProperty(mock, relName, {
|
|
5916
|
-
get: () => {
|
|
5917
|
-
// Don't call build() - it returns schema without relations
|
|
5918
|
-
const fk = relConfig.foreignKey || relConfig.foreignKeys?.[0] || '';
|
|
5919
|
-
return new CollectionQueryBuilder(relName, relConfig.targetTable, fk, this.targetTable, undefined, // Don't pass schema, force registry lookup
|
|
5920
|
-
this.schemaRegistry, // Pass schema registry for nested resolution
|
|
5921
|
-
// No navigation path needed here - direct collection access from parent
|
|
5922
|
-
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
5923
|
-
relConfig.matches);
|
|
5924
|
-
},
|
|
5925
|
-
enumerable: false,
|
|
5926
|
-
configurable: true,
|
|
5927
|
-
});
|
|
5928
|
-
}
|
|
5929
|
-
else {
|
|
5930
|
-
// Reference navigation
|
|
5931
|
-
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
5932
|
-
Object.defineProperty(mock, relName, {
|
|
5933
|
-
get: () => {
|
|
5934
|
-
// Don't call build() - it returns schema without relations
|
|
5935
|
-
// Instead, pass undefined and let ReferenceQueryBuilder look it up from registry
|
|
5936
|
-
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, undefined, // Don't pass schema, force registry lookup
|
|
5937
|
-
this.schemaRegistry, // Pass schema registry for nested resolution
|
|
5938
|
-
[], // Empty navigation path - this is the first reference in the chain
|
|
5939
|
-
this.targetTable // Source alias is this collection's target table
|
|
5940
|
-
);
|
|
5941
|
-
return refBuilder.createMockTargetRow();
|
|
5942
|
-
},
|
|
5943
|
-
enumerable: false,
|
|
5944
|
-
configurable: true,
|
|
5945
|
-
});
|
|
5946
|
-
}
|
|
5947
|
-
}
|
|
5948
|
-
}
|
|
6000
|
+
// Prototype-level cache — same pattern as the root mock and
|
|
6001
|
+
// ReferenceQueryBuilder.createMockTargetRow (see MockRowCache): the getters are
|
|
6002
|
+
// fully determined by the target schema, so every collection item mock of the same
|
|
6003
|
+
// table shares one prebuilt prototype and a new item is `Object.create` plus its
|
|
6004
|
+
// FieldRef slot — O(1) instead of one property definition per column+relation per
|
|
6005
|
+
// `.where()`/`.select()`/`.orderBy()` call. Opt-in via the same static switch; OFF
|
|
6006
|
+
// = fresh prototype per item (the pre-0.4.70 behaviour).
|
|
6007
|
+
const prototype = mock_row_cache_1.MockRowCache.getOrBuild(`citem|${this.targetTable}`, () => Object.defineProperties({}, this.buildMockItemDescriptors()));
|
|
6008
|
+
const mock = Object.create(prototype);
|
|
6009
|
+
mock[MOCK_ROW_FIELD_REFS] = {};
|
|
5949
6010
|
// Cache the mock for reuse
|
|
5950
6011
|
this._cachedMockItem = mock;
|
|
5951
6012
|
return mock;
|
|
@@ -5955,6 +6016,85 @@ class CollectionQueryBuilder {
|
|
|
5955
6016
|
return createNestedFieldRefProxy(this.targetTable);
|
|
5956
6017
|
}
|
|
5957
6018
|
}
|
|
6019
|
+
/**
|
|
6020
|
+
* Builds the shared property-descriptor map for {@link createMockItem}'s cached path.
|
|
6021
|
+
* The getters read per-row state through `this`-bound symbol slots; values captured at
|
|
6022
|
+
* build time are signature-constants identical for every item mock of the table.
|
|
6023
|
+
*/
|
|
6024
|
+
buildMockItemDescriptors() {
|
|
6025
|
+
// Performance: Use pre-computed column name map if available
|
|
6026
|
+
const columnNameMap = getColumnNameMapForSchema(this.targetTableSchema);
|
|
6027
|
+
// Add columns - include tableAlias for unambiguous column references in WHERE clauses
|
|
6028
|
+
// Use a special marker alias for the collection's own table that can be rewritten later
|
|
6029
|
+
// This allows distinguishing between outer table references and inner collection references
|
|
6030
|
+
// when both target the same table (e.g., post.user.posts where both are "posts" table)
|
|
6031
|
+
const tableAlias = `__collection_${this.targetTable}__`;
|
|
6032
|
+
const descriptors = {};
|
|
6033
|
+
for (const [colName, dbColumnName] of columnNameMap) {
|
|
6034
|
+
descriptors[colName] = {
|
|
6035
|
+
get() {
|
|
6036
|
+
const slots = this;
|
|
6037
|
+
const fieldRefCache = slots[MOCK_ROW_FIELD_REFS] ?? (slots[MOCK_ROW_FIELD_REFS] = {});
|
|
6038
|
+
let cached = fieldRefCache[colName];
|
|
6039
|
+
if (!cached) {
|
|
6040
|
+
cached = fieldRefCache[colName] = {
|
|
6041
|
+
__fieldName: colName,
|
|
6042
|
+
__dbColumnName: dbColumnName,
|
|
6043
|
+
__tableAlias: tableAlias, // Include table alias for unambiguous references
|
|
6044
|
+
};
|
|
6045
|
+
}
|
|
6046
|
+
return cached;
|
|
6047
|
+
},
|
|
6048
|
+
enumerable: true,
|
|
6049
|
+
configurable: true,
|
|
6050
|
+
};
|
|
6051
|
+
}
|
|
6052
|
+
// Values captured at descriptor-build time — identical for every item of this
|
|
6053
|
+
// signature (the registry is the process-wide schema registry).
|
|
6054
|
+
const targetTable = this.targetTable;
|
|
6055
|
+
const schemaRegistry = this.schemaRegistry;
|
|
6056
|
+
// Add navigation properties (both collections and references)
|
|
6057
|
+
if (this.targetTableSchema.relations) {
|
|
6058
|
+
for (const [relName, relConfig] of Object.entries(this.targetTableSchema.relations)) {
|
|
6059
|
+
if (relConfig.type === 'many') {
|
|
6060
|
+
// Collection navigation
|
|
6061
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
6062
|
+
descriptors[relName] = {
|
|
6063
|
+
get: () => {
|
|
6064
|
+
// Don't call build() - it returns schema without relations
|
|
6065
|
+
const fk = relConfig.foreignKey || relConfig.foreignKeys?.[0] || '';
|
|
6066
|
+
return new CollectionQueryBuilder(relName, relConfig.targetTable, fk, targetTable, undefined, // Don't pass schema, force registry lookup
|
|
6067
|
+
schemaRegistry, // Pass schema registry for nested resolution
|
|
6068
|
+
// No navigation path needed here - direct collection access from parent
|
|
6069
|
+
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
6070
|
+
relConfig.matches);
|
|
6071
|
+
},
|
|
6072
|
+
enumerable: false,
|
|
6073
|
+
configurable: true,
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
else {
|
|
6077
|
+
// Reference navigation
|
|
6078
|
+
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
6079
|
+
descriptors[relName] = {
|
|
6080
|
+
get: () => {
|
|
6081
|
+
// Don't call build() - it returns schema without relations
|
|
6082
|
+
// Instead, pass undefined and let ReferenceQueryBuilder look it up from registry
|
|
6083
|
+
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, undefined, // Don't pass schema, force registry lookup
|
|
6084
|
+
schemaRegistry, // Pass schema registry for nested resolution
|
|
6085
|
+
[], // Empty navigation path - this is the first reference in the chain
|
|
6086
|
+
targetTable // Source alias is this collection's target table
|
|
6087
|
+
);
|
|
6088
|
+
return refBuilder.createMockTargetRow();
|
|
6089
|
+
},
|
|
6090
|
+
enumerable: false,
|
|
6091
|
+
configurable: true,
|
|
6092
|
+
};
|
|
6093
|
+
}
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
return descriptors;
|
|
6097
|
+
}
|
|
5958
6098
|
/**
|
|
5959
6099
|
* Limit collection items
|
|
5960
6100
|
*/
|
|
@@ -6759,7 +6899,7 @@ class CollectionQueryBuilder {
|
|
|
6759
6899
|
// (field collection, aggregate-expression discovery, navigation-join detection) all
|
|
6760
6900
|
// need to walk the same selection, and re-invoking the selector is expensive
|
|
6761
6901
|
// (rebuilds proxy mocks and any nested CollectionQueryBuilder instances).
|
|
6762
|
-
const selectorResult = this.selector ? this.selector(this.createMockItem()) : undefined;
|
|
6902
|
+
const selectorResult = this.selector ? (0, exports.materializeMockSelection)(this.selector(this.createMockItem())) : undefined;
|
|
6763
6903
|
// Step 1: Build field selection configuration
|
|
6764
6904
|
if (this.selector) {
|
|
6765
6905
|
const selectedFields = selectorResult;
|
|
@@ -6836,9 +6976,8 @@ class CollectionQueryBuilder {
|
|
|
6836
6976
|
let dbToPropertyMap = null;
|
|
6837
6977
|
if (this.targetTableSchema) {
|
|
6838
6978
|
dbToPropertyMap = new Map();
|
|
6839
|
-
for (const [propName,
|
|
6840
|
-
|
|
6841
|
-
dbToPropertyMap.set(config.name, propName);
|
|
6979
|
+
for (const [propName, meta] of getSchemaColumnMeta(this.targetTableSchema)) {
|
|
6980
|
+
dbToPropertyMap.set(meta.name, propName);
|
|
6842
6981
|
}
|
|
6843
6982
|
}
|
|
6844
6983
|
const orderPartsDb = this.orderByFields.map(({ field, direction }) => {
|