linkgress-orm 0.4.67 → 0.4.69
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/entity/db-context.d.ts +29 -0
- package/dist/entity/db-context.d.ts.map +1 -1
- package/dist/entity/db-context.js +97 -80
- package/dist/entity/db-context.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.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/navigation-path-cache.d.ts +41 -0
- package/dist/query/navigation-path-cache.d.ts.map +1 -0
- package/dist/query/navigation-path-cache.js +66 -0
- package/dist/query/navigation-path-cache.js.map +1 -0
- package/dist/query/query-builder.d.ts +38 -0
- package/dist/query/query-builder.d.ts.map +1 -1
- package/dist/query/query-builder.js +360 -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");
|
|
@@ -16,6 +17,7 @@ const collection_strategy_factory_1 = require("./collection-strategy.factory");
|
|
|
16
17
|
const union_builder_1 = require("./union-builder");
|
|
17
18
|
const future_query_1 = require("./future-query");
|
|
18
19
|
const mock_row_cache_1 = require("./mock-row-cache");
|
|
20
|
+
const navigation_path_cache_1 = require("./navigation-path-cache");
|
|
19
21
|
const join_utils_1 = require("./join-utils");
|
|
20
22
|
/**
|
|
21
23
|
* Field type categories for optimized result transformation
|
|
@@ -58,6 +60,26 @@ function getRelationEntriesForSchema(schema) {
|
|
|
58
60
|
// Fallback: build the array (for schemas that weren't built with the new TableBuilder)
|
|
59
61
|
return Object.entries(schema.relations);
|
|
60
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
|
+
}
|
|
61
83
|
/**
|
|
62
84
|
* Mock-row descriptor cache for {@link ReferenceQueryBuilder.createMockTargetRow}.
|
|
63
85
|
*
|
|
@@ -76,14 +98,18 @@ function getRelationEntriesForSchema(schema) {
|
|
|
76
98
|
*/
|
|
77
99
|
const MOCK_ROW_FIELD_REFS = Symbol('linkgressMockFieldRefs');
|
|
78
100
|
const MOCK_ROW_NAV_CACHE = Symbol('linkgressMockNavCache');
|
|
101
|
+
const MOCK_ROW_CHAIN_ID = Symbol('linkgressMockChainId');
|
|
79
102
|
const navigationPathSignature = (path) => path
|
|
80
103
|
.map(step => `${step.alias}:${step.targetTable}:${(step.foreignKeys ?? []).join('+')}:${(step.matches ?? []).join('+')}:${step.isMandatory ? 1 : 0}:${step.sourceAlias ?? ''}`)
|
|
81
104
|
.join('>');
|
|
82
105
|
/**
|
|
83
|
-
* Whether `value` is a
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
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.
|
|
87
113
|
*/
|
|
88
114
|
const isReferenceMockRow = (value) => value != null && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, MOCK_ROW_FIELD_REFS);
|
|
89
115
|
/** `Object.getOwnPropertyDescriptor` that walks the prototype chain (stops before Object.prototype). */
|
|
@@ -98,10 +124,32 @@ const findPropertyDescriptor = (value, key) => {
|
|
|
98
124
|
}
|
|
99
125
|
return undefined;
|
|
100
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;
|
|
101
149
|
/**
|
|
102
150
|
* First enumerable key (own or inherited) backed by a getter — the "is this a mock row" probe
|
|
103
|
-
* shared by the selection resolvers.
|
|
104
|
-
*
|
|
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
|
|
105
153
|
* enumerable getter is always a column and `row[key]` yields its FieldRef.
|
|
106
154
|
*/
|
|
107
155
|
const findFirstGetterKey = (value) => {
|
|
@@ -298,37 +346,67 @@ class QueryBuilder {
|
|
|
298
346
|
if (this._cachedMockRow) {
|
|
299
347
|
return this._cachedMockRow;
|
|
300
348
|
}
|
|
301
|
-
|
|
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() {
|
|
302
377
|
const tableAlias = this.schema.name;
|
|
303
|
-
const chainId = this.chainId;
|
|
304
378
|
// Performance: Use pre-computed column name map if available
|
|
305
379
|
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
306
|
-
// Performance: Lazy-cache FieldRef objects - only create when first accessed
|
|
307
|
-
const fieldRefCache = {};
|
|
308
380
|
// Build a mapper lookup for columns (only when needed)
|
|
309
381
|
const columnMappers = {};
|
|
310
382
|
const columnSqlTypes = {};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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;
|
|
315
390
|
}
|
|
316
|
-
if (
|
|
317
|
-
columnSqlTypes[colName] =
|
|
391
|
+
if (meta.type) {
|
|
392
|
+
columnSqlTypes[colName] = meta.type;
|
|
318
393
|
}
|
|
319
394
|
}
|
|
395
|
+
const descriptors = {};
|
|
320
396
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
321
397
|
for (const [colName, dbColumnName] of columnNameMap) {
|
|
322
398
|
const mapper = columnMappers[colName];
|
|
323
|
-
|
|
399
|
+
descriptors[colName] = {
|
|
324
400
|
get() {
|
|
401
|
+
const slots = this;
|
|
402
|
+
const fieldRefCache = slots[MOCK_ROW_FIELD_REFS] ?? (slots[MOCK_ROW_FIELD_REFS] = {});
|
|
325
403
|
let cached = fieldRefCache[colName];
|
|
326
404
|
if (!cached) {
|
|
327
405
|
cached = fieldRefCache[colName] = {
|
|
328
406
|
__fieldName: colName,
|
|
329
407
|
__dbColumnName: dbColumnName,
|
|
330
408
|
__tableAlias: tableAlias,
|
|
331
|
-
__chainId:
|
|
409
|
+
__chainId: slots[MOCK_ROW_CHAIN_ID],
|
|
332
410
|
// Include mapper for toDriver transformation in conditions
|
|
333
411
|
__mapper: mapper,
|
|
334
412
|
// Column SQL type — lets flag* emit width-exact mask casts
|
|
@@ -339,49 +417,51 @@ class QueryBuilder {
|
|
|
339
417
|
},
|
|
340
418
|
enumerable: true,
|
|
341
419
|
configurable: true,
|
|
342
|
-
}
|
|
420
|
+
};
|
|
343
421
|
}
|
|
344
422
|
// Performance: Use pre-computed relation entries and cached schemas
|
|
345
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;
|
|
346
428
|
// Add relations (both collections and single references)
|
|
347
429
|
for (const [relName, relConfig] of relationEntries) {
|
|
348
430
|
// Performance: Use cached target schema, but prefer registry lookup for full relations
|
|
349
|
-
let targetSchema =
|
|
431
|
+
let targetSchema = schemaRegistry?.get(relConfig.targetTable);
|
|
350
432
|
if (!targetSchema) {
|
|
351
433
|
targetSchema = getTargetSchemaForRelation(this.schema, relName, relConfig);
|
|
352
434
|
}
|
|
353
435
|
if (relConfig.type === 'many') {
|
|
354
436
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
355
|
-
|
|
437
|
+
descriptors[relName] = {
|
|
356
438
|
get: () => {
|
|
357
|
-
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
|
|
358
440
|
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
359
441
|
relConfig.matches);
|
|
360
442
|
},
|
|
361
443
|
enumerable: false,
|
|
362
444
|
configurable: true,
|
|
363
|
-
}
|
|
445
|
+
};
|
|
364
446
|
}
|
|
365
447
|
else {
|
|
366
448
|
// Single reference navigation (many-to-one, one-to-one)
|
|
367
449
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow
|
|
368
450
|
// with circular relations like User->Posts->User)
|
|
369
|
-
|
|
451
|
+
descriptors[relName] = {
|
|
370
452
|
get: () => {
|
|
371
|
-
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
|
|
372
454
|
[], // Empty navigation path for first level navigation
|
|
373
|
-
|
|
455
|
+
sourceTableName // Pass source table name for lateral join correlation
|
|
374
456
|
);
|
|
375
457
|
return refBuilder.createMockTargetRow();
|
|
376
458
|
},
|
|
377
459
|
enumerable: false,
|
|
378
460
|
configurable: true,
|
|
379
|
-
}
|
|
461
|
+
};
|
|
380
462
|
}
|
|
381
463
|
}
|
|
382
|
-
|
|
383
|
-
this._cachedMockRow = mock;
|
|
384
|
-
return mock;
|
|
464
|
+
return descriptors;
|
|
385
465
|
}
|
|
386
466
|
/**
|
|
387
467
|
* Add a LEFT JOIN to the query with a selector (supports both tables and subqueries)
|
|
@@ -425,7 +505,7 @@ class QueryBuilder {
|
|
|
425
505
|
// Create fresh mocks for the selector invocation
|
|
426
506
|
const freshMockLeft = createLeftMock();
|
|
427
507
|
const freshMockRight = createRightMock();
|
|
428
|
-
return selector(freshMockLeft, freshMockRight);
|
|
508
|
+
return (0, exports.materializeMockSelection)(selector(freshMockLeft, freshMockRight));
|
|
429
509
|
};
|
|
430
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
|
|
431
511
|
undefined, // schemaRegistry
|
|
@@ -474,7 +554,7 @@ class QueryBuilder {
|
|
|
474
554
|
// Create fresh mocks for the selector invocation
|
|
475
555
|
const freshMockLeft = createLeftMock();
|
|
476
556
|
const freshMockRight = createRightMock();
|
|
477
|
-
return selector(freshMockLeft, freshMockRight);
|
|
557
|
+
return (0, exports.materializeMockSelection)(selector(freshMockLeft, freshMockRight));
|
|
478
558
|
};
|
|
479
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
|
|
480
560
|
undefined, // schemaRegistry
|
|
@@ -560,7 +640,7 @@ class QueryBuilder {
|
|
|
560
640
|
}
|
|
561
641
|
orderBy(selector) {
|
|
562
642
|
const mockRow = this._createMockRow();
|
|
563
|
-
const result = selector(mockRow);
|
|
643
|
+
const result = (0, exports.materializeMockSelection)(selector(mockRow));
|
|
564
644
|
(0, query_utils_1.parseOrderBy)(result, this.orderByFields);
|
|
565
645
|
return this;
|
|
566
646
|
}
|
|
@@ -630,7 +710,7 @@ class SelectQueryBuilder {
|
|
|
630
710
|
select(selector) {
|
|
631
711
|
// Create a composed selector that applies both transformations
|
|
632
712
|
const composedSelector = (row) => {
|
|
633
|
-
const firstResult = this.selector(row);
|
|
713
|
+
const firstResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
634
714
|
return selector(firstResult);
|
|
635
715
|
};
|
|
636
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);
|
|
@@ -678,7 +758,7 @@ class SelectQueryBuilder {
|
|
|
678
758
|
const rightAlias = `${rightSchema.name}_${this.joinCounter}`;
|
|
679
759
|
this.joinCounter = this.joinCounter + 1;
|
|
680
760
|
const mockRow = this._createMockRow();
|
|
681
|
-
const selectedMock = this.selector(mockRow);
|
|
761
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
682
762
|
const leftMock = this.createFieldRefProxy(selectedMock, true);
|
|
683
763
|
const rightMock = this.createMockRowForTable(rightSchema, rightAlias);
|
|
684
764
|
const onCondition = on(leftMock, rightMock);
|
|
@@ -707,7 +787,7 @@ class SelectQueryBuilder {
|
|
|
707
787
|
*/
|
|
708
788
|
addCteFilterJoin(type, cte, on, filter) {
|
|
709
789
|
const mockRow = this._createMockRow();
|
|
710
|
-
const selectedMock = this.selector(mockRow);
|
|
790
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
711
791
|
const leftMock = this.createFieldRefProxy(selectedMock, true);
|
|
712
792
|
const rightMock = this.createMockRowForCte(cte);
|
|
713
793
|
const onCondition = on(leftMock, rightMock);
|
|
@@ -734,7 +814,7 @@ class SelectQueryBuilder {
|
|
|
734
814
|
where(condition) {
|
|
735
815
|
const mockRow = this._createMockRow();
|
|
736
816
|
// Apply the selector to get the selected shape that the user sees in the WHERE condition
|
|
737
|
-
const selectedMock = this.selector(mockRow);
|
|
817
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
738
818
|
// Wrap in proxy - for WHERE, we preserve original column names
|
|
739
819
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock, true);
|
|
740
820
|
const newCondition = condition(fieldRefProxy);
|
|
@@ -824,7 +904,7 @@ class SelectQueryBuilder {
|
|
|
824
904
|
}
|
|
825
905
|
orderBy(selector) {
|
|
826
906
|
const mockRow = this._createMockRow();
|
|
827
|
-
const selectedMock = this.selector(mockRow);
|
|
907
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
828
908
|
// Wrap selectedMock in a proxy that returns FieldRefs for property access
|
|
829
909
|
const fieldRefProxy = this.createFieldRefProxy(selectedMock);
|
|
830
910
|
const result = selector(fieldRefProxy);
|
|
@@ -856,7 +936,7 @@ class SelectQueryBuilder {
|
|
|
856
936
|
const newJoinCounter = this.joinCounter + 1;
|
|
857
937
|
// Create mock for the current selection (left side)
|
|
858
938
|
const mockRow = this._createMockRow();
|
|
859
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
939
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
860
940
|
// Create mock for the subquery result (right side)
|
|
861
941
|
// For subqueries, we create a mock based on the result type
|
|
862
942
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
@@ -874,7 +954,7 @@ class SelectQueryBuilder {
|
|
|
874
954
|
}];
|
|
875
955
|
// Create a new selector
|
|
876
956
|
const composedSelector = (row) => {
|
|
877
|
-
const leftResult = this.selector(row);
|
|
957
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
878
958
|
const freshMockRight = this.createMockRowForSubquery(alias, subquery);
|
|
879
959
|
return selector(leftResult, freshMockRight);
|
|
880
960
|
};
|
|
@@ -900,7 +980,7 @@ class SelectQueryBuilder {
|
|
|
900
980
|
// Create mock for the current selection (left side)
|
|
901
981
|
// IMPORTANT: We call the selector with the mock row to get a result that contains FieldRef objects
|
|
902
982
|
const mockRow = this._createMockRow();
|
|
903
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
983
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
904
984
|
// The mockLeftSelection now contains FieldRef objects (with __fieldName, __dbColumnName, __tableAlias)
|
|
905
985
|
// These FieldRef objects preserve the table context
|
|
906
986
|
// Create mock for the right table
|
|
@@ -918,7 +998,7 @@ class SelectQueryBuilder {
|
|
|
918
998
|
}];
|
|
919
999
|
// Create a new selector that first applies the current selector, then the new selector
|
|
920
1000
|
const composedSelector = (row) => {
|
|
921
|
-
const leftResult = this.selector(row);
|
|
1001
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
922
1002
|
const freshMockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
923
1003
|
return selector(leftResult, freshMockRight);
|
|
924
1004
|
};
|
|
@@ -931,7 +1011,7 @@ class SelectQueryBuilder {
|
|
|
931
1011
|
const newJoinCounter = this.joinCounter + 1;
|
|
932
1012
|
// Create mock for the current selection (left side)
|
|
933
1013
|
const mockRow = this._createMockRow();
|
|
934
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1014
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
935
1015
|
// Create mock for the CTE columns (right side)
|
|
936
1016
|
const mockRight = this.createMockRowForCte(cte);
|
|
937
1017
|
// Evaluate the join condition
|
|
@@ -947,7 +1027,7 @@ class SelectQueryBuilder {
|
|
|
947
1027
|
}];
|
|
948
1028
|
// Create a new selector
|
|
949
1029
|
const composedSelector = (row) => {
|
|
950
|
-
const leftResult = this.selector(row);
|
|
1030
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
951
1031
|
const freshMockRight = this.createMockRowForCte(cte);
|
|
952
1032
|
return selector(leftResult, freshMockRight);
|
|
953
1033
|
};
|
|
@@ -964,7 +1044,7 @@ class SelectQueryBuilder {
|
|
|
964
1044
|
const newJoinCounter = this.joinCounter + 1;
|
|
965
1045
|
// Create mock for the current selection (left side)
|
|
966
1046
|
const mockRow = this._createMockRow();
|
|
967
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1047
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
968
1048
|
// Create mock for the subquery result (right side)
|
|
969
1049
|
const mockRight = this.createMockRowForSubquery(alias, subquery);
|
|
970
1050
|
// Evaluate the join condition
|
|
@@ -981,7 +1061,7 @@ class SelectQueryBuilder {
|
|
|
981
1061
|
}];
|
|
982
1062
|
// Create a new selector
|
|
983
1063
|
const composedSelector = (row) => {
|
|
984
|
-
const leftResult = this.selector(row);
|
|
1064
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
985
1065
|
const freshMockRight = this.createMockRowForSubquery(alias, subquery);
|
|
986
1066
|
return selector(leftResult, freshMockRight);
|
|
987
1067
|
};
|
|
@@ -1006,7 +1086,7 @@ class SelectQueryBuilder {
|
|
|
1006
1086
|
const newJoinCounter = this.joinCounter + 1;
|
|
1007
1087
|
// Create mock for the current selection (left side)
|
|
1008
1088
|
const mockRow = this._createMockRow();
|
|
1009
|
-
const mockLeftSelection = this.selector(mockRow);
|
|
1089
|
+
const mockLeftSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1010
1090
|
// Create mock for the right table
|
|
1011
1091
|
const mockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
1012
1092
|
// Evaluate the join condition
|
|
@@ -1021,7 +1101,7 @@ class SelectQueryBuilder {
|
|
|
1021
1101
|
}];
|
|
1022
1102
|
// Create a new selector that first applies the current selector, then the new selector
|
|
1023
1103
|
const composedSelector = (row) => {
|
|
1024
|
-
const leftResult = this.selector(row);
|
|
1104
|
+
const leftResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
1025
1105
|
const freshMockRight = this.createMockRowForTable(rightSchema, rightAlias);
|
|
1026
1106
|
return selector(leftResult, freshMockRight);
|
|
1027
1107
|
};
|
|
@@ -1200,7 +1280,7 @@ class SelectQueryBuilder {
|
|
|
1200
1280
|
*/
|
|
1201
1281
|
selectDistinct(selector) {
|
|
1202
1282
|
const composedSelector = (row) => {
|
|
1203
|
-
const firstResult = this.selector(row);
|
|
1283
|
+
const firstResult = (0, exports.materializeMockSelection)(this.selector(row));
|
|
1204
1284
|
return selector(firstResult);
|
|
1205
1285
|
};
|
|
1206
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
|
|
@@ -1222,13 +1302,13 @@ class SelectQueryBuilder {
|
|
|
1222
1302
|
let fieldToAggregate;
|
|
1223
1303
|
if (selector) {
|
|
1224
1304
|
const mockRow = this._createMockRow();
|
|
1225
|
-
const mockSelection = this.selector(mockRow);
|
|
1305
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1226
1306
|
fieldToAggregate = selector(mockSelection);
|
|
1227
1307
|
}
|
|
1228
1308
|
else {
|
|
1229
1309
|
// No selector - use the current selection
|
|
1230
1310
|
const mockRow = this._createMockRow();
|
|
1231
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1311
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1232
1312
|
}
|
|
1233
1313
|
// Build aggregation query
|
|
1234
1314
|
const { sql, params } = this.buildAggregationQuery('MIN', fieldToAggregate, context);
|
|
@@ -1254,13 +1334,13 @@ class SelectQueryBuilder {
|
|
|
1254
1334
|
let fieldToAggregate;
|
|
1255
1335
|
if (selector) {
|
|
1256
1336
|
const mockRow = this._createMockRow();
|
|
1257
|
-
const mockSelection = this.selector(mockRow);
|
|
1337
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1258
1338
|
fieldToAggregate = selector(mockSelection);
|
|
1259
1339
|
}
|
|
1260
1340
|
else {
|
|
1261
1341
|
// No selector - use the current selection
|
|
1262
1342
|
const mockRow = this._createMockRow();
|
|
1263
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1343
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1264
1344
|
}
|
|
1265
1345
|
// Build aggregation query
|
|
1266
1346
|
const { sql, params } = this.buildAggregationQuery('MAX', fieldToAggregate, context);
|
|
@@ -1286,13 +1366,13 @@ class SelectQueryBuilder {
|
|
|
1286
1366
|
let fieldToAggregate;
|
|
1287
1367
|
if (selector) {
|
|
1288
1368
|
const mockRow = this._createMockRow();
|
|
1289
|
-
const mockSelection = this.selector(mockRow);
|
|
1369
|
+
const mockSelection = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1290
1370
|
fieldToAggregate = selector(mockSelection);
|
|
1291
1371
|
}
|
|
1292
1372
|
else {
|
|
1293
1373
|
// No selector - use the current selection
|
|
1294
1374
|
const mockRow = this._createMockRow();
|
|
1295
|
-
fieldToAggregate = this.selector(mockRow);
|
|
1375
|
+
fieldToAggregate = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1296
1376
|
}
|
|
1297
1377
|
// Build aggregation query
|
|
1298
1378
|
const { sql, params } = this.buildAggregationQuery('SUM', fieldToAggregate, context);
|
|
@@ -1340,7 +1420,7 @@ class SelectQueryBuilder {
|
|
|
1340
1420
|
executor: this.executor,
|
|
1341
1421
|
}));
|
|
1342
1422
|
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1343
|
-
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1423
|
+
const selectionResult = tracer.trace('evaluateSelector', () => (0, exports.materializeMockSelection)(this.selector(mockRow)));
|
|
1344
1424
|
const { sql, params, nestedPaths } = tracer.trace('buildQuery', () => this.buildQuery(selectionResult, context));
|
|
1345
1425
|
tracer.endPhase();
|
|
1346
1426
|
tracer.startPhase('queryExecution');
|
|
@@ -1448,7 +1528,7 @@ class SelectQueryBuilder {
|
|
|
1448
1528
|
hoistedCteNames: context.hoistedCteNames,
|
|
1449
1529
|
};
|
|
1450
1530
|
const mockRow = this._createMockRow();
|
|
1451
|
-
const selectionResult = this.selector(mockRow);
|
|
1531
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1452
1532
|
// Build query without ORDER BY, LIMIT, OFFSET for union component
|
|
1453
1533
|
const { sql, nestedPaths } = this.buildQueryCore(selectionResult, queryContext, false);
|
|
1454
1534
|
// Update context's param counter
|
|
@@ -1516,7 +1596,7 @@ class SelectQueryBuilder {
|
|
|
1516
1596
|
executor: this.executor,
|
|
1517
1597
|
};
|
|
1518
1598
|
const mockRow = this._createMockRow();
|
|
1519
|
-
const selectionResult = this.selector(mockRow);
|
|
1599
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1520
1600
|
const { sql, params, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
1521
1601
|
// Create transform function that captures current state
|
|
1522
1602
|
const transformFn = (rows) => {
|
|
@@ -1563,7 +1643,7 @@ class SelectQueryBuilder {
|
|
|
1563
1643
|
executor: this.executor,
|
|
1564
1644
|
};
|
|
1565
1645
|
const mockRow = this._createMockRow();
|
|
1566
|
-
const selectionResult = this.selector(mockRow);
|
|
1646
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
1567
1647
|
const { sql, params, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
1568
1648
|
// Restore original limit
|
|
1569
1649
|
this.limitValue = originalLimit;
|
|
@@ -1792,7 +1872,7 @@ class SelectQueryBuilder {
|
|
|
1792
1872
|
}));
|
|
1793
1873
|
// Analyze the selector to extract nested queries
|
|
1794
1874
|
const mockRow = tracer.trace('createMockRow', () => this._createMockRow());
|
|
1795
|
-
const selectionResult = tracer.trace('evaluateSelector', () => this.selector(mockRow));
|
|
1875
|
+
const selectionResult = tracer.trace('evaluateSelector', () => (0, exports.materializeMockSelection)(this.selector(mockRow)));
|
|
1796
1876
|
// Check if we're using temp table strategy and have collections
|
|
1797
1877
|
const collections = tracer.trace('detectCollections', () => this.detectCollections(selectionResult));
|
|
1798
1878
|
const useTempTableStrategy = this.collectionStrategy === 'temptable' && collections.length > 0;
|
|
@@ -2054,7 +2134,7 @@ class SelectQueryBuilder {
|
|
|
2054
2134
|
'jsonb',
|
|
2055
2135
|
]);
|
|
2056
2136
|
try {
|
|
2057
|
-
const selected = b.selector(b.createMockItem());
|
|
2137
|
+
const selected = (0, exports.materializeMockSelection)(b.selector(b.createMockItem()));
|
|
2058
2138
|
for (const value of Object.values(selected)) {
|
|
2059
2139
|
if (!(value && typeof value === 'object' && '__dbColumnName' in value)) {
|
|
2060
2140
|
return false;
|
|
@@ -2119,10 +2199,9 @@ class SelectQueryBuilder {
|
|
|
2119
2199
|
// Find primary key column from schema, fallback to "id" if not found
|
|
2120
2200
|
let pkColumn = null;
|
|
2121
2201
|
if (targetSchema) {
|
|
2122
|
-
for (const
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
pkColumn = config.name;
|
|
2202
|
+
for (const meta of getSchemaColumnMeta(targetSchema).values()) {
|
|
2203
|
+
if (meta.primaryKey && meta.name != null) {
|
|
2204
|
+
pkColumn = meta.name;
|
|
2126
2205
|
break;
|
|
2127
2206
|
}
|
|
2128
2207
|
}
|
|
@@ -2503,7 +2582,7 @@ class SelectQueryBuilder {
|
|
|
2503
2582
|
};
|
|
2504
2583
|
// Analyze the selector to extract nested queries
|
|
2505
2584
|
const mockRow = this._createMockRow();
|
|
2506
|
-
const selectionResult = this.selector(mockRow);
|
|
2585
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2507
2586
|
// Build the query - this populates context.placeholders
|
|
2508
2587
|
const { sql, nestedPaths } = this.buildQuery(selectionResult, context);
|
|
2509
2588
|
// Create transform function (closure over schema, selection, nestedPaths)
|
|
@@ -2892,7 +2971,7 @@ class SelectQueryBuilder {
|
|
|
2892
2971
|
}
|
|
2893
2972
|
// Selector function - extract selected columns
|
|
2894
2973
|
const mockRow = this._createMockRow();
|
|
2895
|
-
const selectedMock = this.selector(mockRow);
|
|
2974
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2896
2975
|
const selection = returning(selectedMock);
|
|
2897
2976
|
if (typeof selection === 'object' && selection !== null) {
|
|
2898
2977
|
const columns = [];
|
|
@@ -2932,12 +3011,14 @@ class SelectQueryBuilder {
|
|
|
2932
3011
|
mapDeleteReturningResults(rows, returning, fragmentMappers) {
|
|
2933
3012
|
if (returning === true) {
|
|
2934
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);
|
|
2935
3017
|
return rows.map(row => {
|
|
2936
3018
|
const mapped = {};
|
|
2937
|
-
for (const [propName,
|
|
2938
|
-
const
|
|
2939
|
-
|
|
2940
|
-
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;
|
|
2941
3022
|
}
|
|
2942
3023
|
return mapped;
|
|
2943
3024
|
});
|
|
@@ -2953,15 +3034,13 @@ class SelectQueryBuilder {
|
|
|
2953
3034
|
continue;
|
|
2954
3035
|
}
|
|
2955
3036
|
// Try to find column by alias or name
|
|
2956
|
-
const colEntry =
|
|
2957
|
-
|
|
2958
|
-
return propName === key || config.name === key;
|
|
3037
|
+
const colEntry = [...getSchemaColumnMeta(this.schema)].find(([propName, meta]) => {
|
|
3038
|
+
return propName === key || meta.name === key;
|
|
2959
3039
|
});
|
|
2960
3040
|
if (colEntry) {
|
|
2961
|
-
const [,
|
|
2962
|
-
const config = col.build();
|
|
3041
|
+
const [, meta] = colEntry;
|
|
2963
3042
|
// Apply fromDriver mapper if present
|
|
2964
|
-
mapped[key] =
|
|
3043
|
+
mapped[key] = meta.mapper ? meta.mapper.fromDriver(value) : value;
|
|
2965
3044
|
}
|
|
2966
3045
|
else {
|
|
2967
3046
|
mapped[key] = value;
|
|
@@ -2982,7 +3061,7 @@ class SelectQueryBuilder {
|
|
|
2982
3061
|
}
|
|
2983
3062
|
// Analyze the returning selector
|
|
2984
3063
|
const mockRow = this._createMockRow();
|
|
2985
|
-
const selectedMock = this.selector(mockRow);
|
|
3064
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
2986
3065
|
const selection = returning(selectedMock);
|
|
2987
3066
|
if (typeof selection !== 'object' || selection === null) {
|
|
2988
3067
|
return null;
|
|
@@ -3070,7 +3149,7 @@ class SelectQueryBuilder {
|
|
|
3070
3149
|
const selectParts = [];
|
|
3071
3150
|
const nestedPaths = new Set();
|
|
3072
3151
|
const mockRow = this._createMockRow();
|
|
3073
|
-
const selectedMock = this.selector(mockRow);
|
|
3152
|
+
const selectedMock = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
3074
3153
|
const selection = returning(selectedMock);
|
|
3075
3154
|
// Helper to get FK db column name from a schema
|
|
3076
3155
|
const getFkDbColumnName = (sourceSchema, fkPropName) => {
|
|
@@ -3339,37 +3418,61 @@ ${joinClauses.join('\n')}`;
|
|
|
3339
3418
|
* @internal
|
|
3340
3419
|
*/
|
|
3341
3420
|
_createMockRow() {
|
|
3342
|
-
|
|
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() {
|
|
3343
3447
|
const tableAlias = this.schema.name;
|
|
3344
|
-
const chainId = this.chainId;
|
|
3345
3448
|
// Performance: Use pre-computed column name map if available
|
|
3346
3449
|
const columnNameMap = getColumnNameMapForSchema(this.schema);
|
|
3347
|
-
// Performance: Lazy-cache FieldRef objects
|
|
3348
|
-
const fieldRefCache = {};
|
|
3349
3450
|
// Build a mapper lookup for columns (only when needed)
|
|
3350
3451
|
const columnMappers = {};
|
|
3351
3452
|
const columnSqlTypes = {};
|
|
3352
|
-
for (const [colName,
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
columnMappers[colName] = config.mapper;
|
|
3453
|
+
for (const [colName, meta] of getSchemaColumnMeta(this.schema)) {
|
|
3454
|
+
if (meta.mapper) {
|
|
3455
|
+
columnMappers[colName] = meta.mapper;
|
|
3356
3456
|
}
|
|
3357
|
-
if (
|
|
3358
|
-
columnSqlTypes[colName] =
|
|
3457
|
+
if (meta.type) {
|
|
3458
|
+
columnSqlTypes[colName] = meta.type;
|
|
3359
3459
|
}
|
|
3360
3460
|
}
|
|
3461
|
+
const descriptors = {};
|
|
3361
3462
|
// Add columns as FieldRef objects - type-safe with property name and database column name
|
|
3362
3463
|
for (const [colName, dbColumnName] of columnNameMap) {
|
|
3363
3464
|
const mapper = columnMappers[colName];
|
|
3364
|
-
|
|
3465
|
+
descriptors[colName] = {
|
|
3365
3466
|
get() {
|
|
3467
|
+
const slots = this;
|
|
3468
|
+
const fieldRefCache = slots[MOCK_ROW_FIELD_REFS] ?? (slots[MOCK_ROW_FIELD_REFS] = {});
|
|
3366
3469
|
let cached = fieldRefCache[colName];
|
|
3367
3470
|
if (!cached) {
|
|
3368
3471
|
cached = fieldRefCache[colName] = {
|
|
3369
3472
|
__fieldName: colName,
|
|
3370
3473
|
__dbColumnName: dbColumnName,
|
|
3371
3474
|
__tableAlias: tableAlias,
|
|
3372
|
-
__chainId:
|
|
3475
|
+
__chainId: slots[MOCK_ROW_CHAIN_ID],
|
|
3373
3476
|
// Include mapper for toDriver transformation in conditions
|
|
3374
3477
|
__mapper: mapper,
|
|
3375
3478
|
// Column SQL type — lets flag* emit width-exact mask casts
|
|
@@ -3380,48 +3483,66 @@ ${joinClauses.join('\n')}`;
|
|
|
3380
3483
|
},
|
|
3381
3484
|
enumerable: true,
|
|
3382
3485
|
configurable: true,
|
|
3383
|
-
}
|
|
3486
|
+
};
|
|
3384
3487
|
}
|
|
3488
|
+
// Captured at descriptor-build time — identical for every row of this signature.
|
|
3489
|
+
const manualJoins = this.manualJoins;
|
|
3385
3490
|
// Add columns from manually joined tables
|
|
3386
|
-
for (const join of
|
|
3491
|
+
for (const join of manualJoins) {
|
|
3387
3492
|
// Skip subquery joins (they don't have a schema)
|
|
3388
3493
|
if (join.isSubquery || !join.schema) {
|
|
3389
3494
|
continue;
|
|
3390
3495
|
}
|
|
3391
|
-
//
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
}
|
|
3396
|
-
// Lazy-cache for joined table
|
|
3397
|
-
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;
|
|
3398
3500
|
const joinAlias = join.alias;
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
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
|
+
});
|
|
3409
3526
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
}
|
|
3527
|
+
}
|
|
3528
|
+
return sub;
|
|
3529
|
+
},
|
|
3530
|
+
enumerable: true,
|
|
3531
|
+
configurable: true,
|
|
3532
|
+
};
|
|
3416
3533
|
}
|
|
3417
3534
|
// Performance: Use pre-computed relation entries
|
|
3418
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;
|
|
3419
3540
|
// Add relations as CollectionQueryBuilder or ReferenceQueryBuilder
|
|
3420
3541
|
for (const [relName, relConfig] of relationEntries) {
|
|
3421
3542
|
// Try to get target schema from registry (preferred, has full relations) or cached schema
|
|
3422
3543
|
let targetSchema;
|
|
3423
|
-
if (
|
|
3424
|
-
targetSchema =
|
|
3544
|
+
if (schemaRegistry) {
|
|
3545
|
+
targetSchema = schemaRegistry.get(relConfig.targetTable);
|
|
3425
3546
|
}
|
|
3426
3547
|
if (!targetSchema) {
|
|
3427
3548
|
// Performance: Use cached target schema
|
|
@@ -3429,36 +3550,36 @@ ${joinClauses.join('\n')}`;
|
|
|
3429
3550
|
}
|
|
3430
3551
|
if (relConfig.type === 'many') {
|
|
3431
3552
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3432
|
-
|
|
3553
|
+
descriptors[relName] = {
|
|
3433
3554
|
get: () => {
|
|
3434
|
-
return new CollectionQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKey || relConfig.foreignKeys?.[0] || '',
|
|
3435
|
-
|
|
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
|
|
3436
3557
|
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
3437
3558
|
relConfig.matches);
|
|
3438
3559
|
},
|
|
3439
3560
|
enumerable: false,
|
|
3440
3561
|
configurable: true,
|
|
3441
|
-
}
|
|
3562
|
+
};
|
|
3442
3563
|
}
|
|
3443
3564
|
else {
|
|
3444
3565
|
// For single reference (many-to-one), create a ReferenceQueryBuilder
|
|
3445
3566
|
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
3446
|
-
|
|
3567
|
+
descriptors[relName] = {
|
|
3447
3568
|
get: () => {
|
|
3448
3569
|
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, targetSchema, // Pass the target schema directly
|
|
3449
|
-
|
|
3570
|
+
schemaRegistry, // Pass schema registry for nested resolution
|
|
3450
3571
|
[], // Empty navigation path for first level navigation
|
|
3451
|
-
|
|
3572
|
+
sourceTableName // Pass source table name for lateral join correlation
|
|
3452
3573
|
);
|
|
3453
3574
|
// Return a mock object that exposes the target table's columns
|
|
3454
3575
|
return refBuilder.createMockTargetRow();
|
|
3455
3576
|
},
|
|
3456
3577
|
enumerable: false,
|
|
3457
3578
|
configurable: true,
|
|
3458
|
-
}
|
|
3579
|
+
};
|
|
3459
3580
|
}
|
|
3460
3581
|
}
|
|
3461
|
-
return
|
|
3582
|
+
return descriptors;
|
|
3462
3583
|
}
|
|
3463
3584
|
/**
|
|
3464
3585
|
* Create a proxy that wraps selected values and returns FieldRefs for property access
|
|
@@ -5503,7 +5624,7 @@ ${joinClauses.join('\n')}`;
|
|
|
5503
5624
|
};
|
|
5504
5625
|
// Analyze the selector to extract nested queries
|
|
5505
5626
|
const mockRow = this._createMockRow();
|
|
5506
|
-
const selectionResult = this.selector(mockRow);
|
|
5627
|
+
const selectionResult = (0, exports.materializeMockSelection)(this.selector(mockRow));
|
|
5507
5628
|
// Build the query
|
|
5508
5629
|
const { sql } = this.buildQuery(selectionResult, context);
|
|
5509
5630
|
// Update the outer context's param counter
|
|
@@ -5664,13 +5785,12 @@ class ReferenceQueryBuilder {
|
|
|
5664
5785
|
// Build a mapper lookup for columns (only when needed)
|
|
5665
5786
|
const columnMappers = {};
|
|
5666
5787
|
const columnSqlTypes = {};
|
|
5667
|
-
for (const [colName,
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
columnMappers[colName] = config.mapper;
|
|
5788
|
+
for (const [colName, meta] of getSchemaColumnMeta(this.targetTableSchema)) {
|
|
5789
|
+
if (meta.mapper) {
|
|
5790
|
+
columnMappers[colName] = meta.mapper;
|
|
5671
5791
|
}
|
|
5672
|
-
if (
|
|
5673
|
-
columnSqlTypes[colName] =
|
|
5792
|
+
if (meta.type) {
|
|
5793
|
+
columnSqlTypes[colName] = meta.type;
|
|
5674
5794
|
}
|
|
5675
5795
|
}
|
|
5676
5796
|
const sourceTable = this.targetTable; // Actual table name for schema lookup
|
|
@@ -5877,74 +5997,16 @@ class CollectionQueryBuilder {
|
|
|
5877
5997
|
return this._cachedMockItem;
|
|
5878
5998
|
}
|
|
5879
5999
|
if (this.targetTableSchema) {
|
|
5880
|
-
//
|
|
5881
|
-
|
|
5882
|
-
//
|
|
5883
|
-
|
|
5884
|
-
//
|
|
5885
|
-
|
|
5886
|
-
//
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
const tableAlias = `__collection_${this.targetTable}__`;
|
|
5891
|
-
for (const [colName, dbColumnName] of columnNameMap) {
|
|
5892
|
-
Object.defineProperty(mock, colName, {
|
|
5893
|
-
get() {
|
|
5894
|
-
let cached = fieldRefCache[colName];
|
|
5895
|
-
if (!cached) {
|
|
5896
|
-
cached = fieldRefCache[colName] = {
|
|
5897
|
-
__fieldName: colName,
|
|
5898
|
-
__dbColumnName: dbColumnName,
|
|
5899
|
-
__tableAlias: tableAlias, // Include table alias for unambiguous references
|
|
5900
|
-
};
|
|
5901
|
-
}
|
|
5902
|
-
return cached;
|
|
5903
|
-
},
|
|
5904
|
-
enumerable: true,
|
|
5905
|
-
configurable: true,
|
|
5906
|
-
});
|
|
5907
|
-
}
|
|
5908
|
-
// Add navigation properties (both collections and references)
|
|
5909
|
-
if (this.targetTableSchema.relations) {
|
|
5910
|
-
for (const [relName, relConfig] of Object.entries(this.targetTableSchema.relations)) {
|
|
5911
|
-
if (relConfig.type === 'many') {
|
|
5912
|
-
// Collection navigation
|
|
5913
|
-
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
5914
|
-
Object.defineProperty(mock, relName, {
|
|
5915
|
-
get: () => {
|
|
5916
|
-
// Don't call build() - it returns schema without relations
|
|
5917
|
-
const fk = relConfig.foreignKey || relConfig.foreignKeys?.[0] || '';
|
|
5918
|
-
return new CollectionQueryBuilder(relName, relConfig.targetTable, fk, this.targetTable, undefined, // Don't pass schema, force registry lookup
|
|
5919
|
-
this.schemaRegistry, // Pass schema registry for nested resolution
|
|
5920
|
-
// No navigation path needed here - direct collection access from parent
|
|
5921
|
-
undefined, relConfig.foreignKeys, // Propagate composite FK / literal predicates
|
|
5922
|
-
relConfig.matches);
|
|
5923
|
-
},
|
|
5924
|
-
enumerable: false,
|
|
5925
|
-
configurable: true,
|
|
5926
|
-
});
|
|
5927
|
-
}
|
|
5928
|
-
else {
|
|
5929
|
-
// Reference navigation
|
|
5930
|
-
// Non-enumerable to prevent Object.entries triggering getters (avoids stack overflow)
|
|
5931
|
-
Object.defineProperty(mock, relName, {
|
|
5932
|
-
get: () => {
|
|
5933
|
-
// Don't call build() - it returns schema without relations
|
|
5934
|
-
// Instead, pass undefined and let ReferenceQueryBuilder look it up from registry
|
|
5935
|
-
const refBuilder = new ReferenceQueryBuilder(relName, relConfig.targetTable, relConfig.foreignKeys || [relConfig.foreignKey || ''], relConfig.matches || [], relConfig.isMandatory ?? false, undefined, // Don't pass schema, force registry lookup
|
|
5936
|
-
this.schemaRegistry, // Pass schema registry for nested resolution
|
|
5937
|
-
[], // Empty navigation path - this is the first reference in the chain
|
|
5938
|
-
this.targetTable // Source alias is this collection's target table
|
|
5939
|
-
);
|
|
5940
|
-
return refBuilder.createMockTargetRow();
|
|
5941
|
-
},
|
|
5942
|
-
enumerable: false,
|
|
5943
|
-
configurable: true,
|
|
5944
|
-
});
|
|
5945
|
-
}
|
|
5946
|
-
}
|
|
5947
|
-
}
|
|
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] = {};
|
|
5948
6010
|
// Cache the mock for reuse
|
|
5949
6011
|
this._cachedMockItem = mock;
|
|
5950
6012
|
return mock;
|
|
@@ -5954,6 +6016,85 @@ class CollectionQueryBuilder {
|
|
|
5954
6016
|
return createNestedFieldRefProxy(this.targetTable);
|
|
5955
6017
|
}
|
|
5956
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
|
+
}
|
|
5957
6098
|
/**
|
|
5958
6099
|
* Limit collection items
|
|
5959
6100
|
*/
|
|
@@ -6524,6 +6665,19 @@ class CollectionQueryBuilder {
|
|
|
6524
6665
|
* Uses BFS to find the shortest path through the schema graph
|
|
6525
6666
|
*/
|
|
6526
6667
|
findNavigationPath(targetAlias, joinedSchemas, _startSchema) {
|
|
6668
|
+
if (!this.schemaRegistry) {
|
|
6669
|
+
return [];
|
|
6670
|
+
}
|
|
6671
|
+
// Memoised per registry — the BFS below depends only on the target alias and the ORDERED
|
|
6672
|
+
// joined alias→table pairs (order decides which of several equal-length paths wins), so
|
|
6673
|
+
// the same query shape resolves to the same path on every build. See NavigationPathCache.
|
|
6674
|
+
const signature = `${targetAlias}|${[...joinedSchemas]
|
|
6675
|
+
.map(([alias, schema]) => `${alias}:${schema.schema ?? ''}.${schema.name}`)
|
|
6676
|
+
.join(',')}`;
|
|
6677
|
+
return navigation_path_cache_1.NavigationPathCache.getOrBuild(this.schemaRegistry, signature, () => this.computeNavigationPath(targetAlias, joinedSchemas));
|
|
6678
|
+
}
|
|
6679
|
+
/** The uncached BFS behind {@link findNavigationPath}. */
|
|
6680
|
+
computeNavigationPath(targetAlias, joinedSchemas) {
|
|
6527
6681
|
if (!this.schemaRegistry) {
|
|
6528
6682
|
return [];
|
|
6529
6683
|
}
|
|
@@ -6745,7 +6899,7 @@ class CollectionQueryBuilder {
|
|
|
6745
6899
|
// (field collection, aggregate-expression discovery, navigation-join detection) all
|
|
6746
6900
|
// need to walk the same selection, and re-invoking the selector is expensive
|
|
6747
6901
|
// (rebuilds proxy mocks and any nested CollectionQueryBuilder instances).
|
|
6748
|
-
const selectorResult = this.selector ? this.selector(this.createMockItem()) : undefined;
|
|
6902
|
+
const selectorResult = this.selector ? (0, exports.materializeMockSelection)(this.selector(this.createMockItem())) : undefined;
|
|
6749
6903
|
// Step 1: Build field selection configuration
|
|
6750
6904
|
if (this.selector) {
|
|
6751
6905
|
const selectedFields = selectorResult;
|
|
@@ -6822,9 +6976,8 @@ class CollectionQueryBuilder {
|
|
|
6822
6976
|
let dbToPropertyMap = null;
|
|
6823
6977
|
if (this.targetTableSchema) {
|
|
6824
6978
|
dbToPropertyMap = new Map();
|
|
6825
|
-
for (const [propName,
|
|
6826
|
-
|
|
6827
|
-
dbToPropertyMap.set(config.name, propName);
|
|
6979
|
+
for (const [propName, meta] of getSchemaColumnMeta(this.targetTableSchema)) {
|
|
6980
|
+
dbToPropertyMap.set(meta.name, propName);
|
|
6828
6981
|
}
|
|
6829
6982
|
}
|
|
6830
6983
|
const orderPartsDb = this.orderByFields.map(({ field, direction }) => {
|