@reldens/storage 0.88.0 → 0.90.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/driver-methods-complete-analysis.md +1978 -0
- package/.claude/test-architecture.md +973 -0
- package/CLAUDE.md +132 -3
- package/bin/reldens-storage.js +1 -1
- package/lib/base-driver.js +8 -0
- package/lib/entities-generator.js +1 -3
- package/lib/entity-templates/entities-translations.template +3 -0
- package/lib/entity-templates/mikro-orm-model.template +2 -1
- package/lib/generators/entities-generation.js +12 -1
- package/lib/generators/entities-translations-generation.js +40 -4
- package/lib/generators/models-generation.js +126 -5
- package/lib/mikro-orm/mikro-orm-data-server.js +24 -10
- package/lib/mikro-orm/mikro-orm-driver.js +184 -116
- package/lib/objection-js/objection-js-driver.js +111 -19
- package/lib/prisma/prisma-data-server.js +3 -2
- package/lib/prisma/prisma-driver.js +12 -10
- package/lib/prisma/prisma-filter-processor.js +193 -146
- package/lib/prisma/prisma-metadata-loader.js +0 -4
- package/lib/prisma/prisma-relation-resolver.js +292 -267
- package/lib/prisma/prisma-type-caster.js +280 -260
- package/package.json +15 -6
- package/tests/.env.test.example +7 -0
- package/tests/fixtures/categories-fixtures.js +164 -0
- package/tests/fixtures/expected-entities/entities/test-categories-entity.js +70 -0
- package/tests/fixtures/expected-entities/entities/test-products-entity.js +95 -0
- package/tests/fixtures/expected-entities/entities/test-reviews-entity.js +84 -0
- package/tests/fixtures/expected-entities/entities-config.js +17 -0
- package/tests/fixtures/expected-entities/entities-translations.js +53 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +23 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +57 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +79 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +70 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +23 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-categories-model.js +33 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +42 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-reviews-model.js +33 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +23 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-categories-model.js +43 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +50 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-reviews-model.js +46 -0
- package/tests/fixtures/products-fixtures.js +119 -0
- package/tests/fixtures/reviews-fixtures.js +42 -0
- package/tests/fixtures/sql/test-schema.sql +59 -0
- package/tests/integration/test-drivers.js +257 -0
- package/tests/integration/test-nested-filters.js +408 -0
- package/tests/integration/test-relations.js +349 -0
- package/tests/run-tests.js +213 -0
- package/tests/unit/test-drivers.js +89 -0
- package/tests/unit/test-entities-generator.js +533 -0
- package/tests/unit/test-entity-manager.js +128 -0
- package/tests/unit/test-type-mapper.js +232 -0
- package/tests/utils/custom-reporter.js +73 -0
- package/tests/utils/driver-registry.js +144 -0
- package/tests/utils/prisma-subprocess-worker.js +150 -0
- package/tests/utils/test-helpers.js +726 -0
- package/tests/utils/test-runner.js +63 -0
|
@@ -0,0 +1,1978 @@
|
|
|
1
|
+
# Driver Methods Complete Analysis
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-01-28
|
|
4
|
+
**Purpose:** Complete understanding of driver abstraction layer and ALL public methods across the 3 drivers
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. FUNDAMENTAL PURPOSE: The Abstraction Layer
|
|
9
|
+
|
|
10
|
+
### What Problem Does This Solve?
|
|
11
|
+
|
|
12
|
+
**The @reldens/storage package provides a UNIFIED API across three different ORM libraries:**
|
|
13
|
+
- ObjectionJS (built on Knex.js)
|
|
14
|
+
- MikroORM
|
|
15
|
+
- Prisma
|
|
16
|
+
|
|
17
|
+
### The Core Concept
|
|
18
|
+
|
|
19
|
+
**Write once, run anywhere:**
|
|
20
|
+
```javascript
|
|
21
|
+
// Application code - SAME for all drivers
|
|
22
|
+
let category = await categoriesRepo.create({name: 'Electronics', slug: 'electronics'});
|
|
23
|
+
let products = await productsRepo.loadWithRelations({category_id: category.id}, ['related_reviews']);
|
|
24
|
+
let count = await categoriesRepo.count({is_active: 1});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**No matter which driver is configured (objection-js, mikro-orm, or prisma), the code above works identically.**
|
|
28
|
+
|
|
29
|
+
### Why This Matters
|
|
30
|
+
|
|
31
|
+
**Without abstraction:**
|
|
32
|
+
```javascript
|
|
33
|
+
// ObjectionJS
|
|
34
|
+
let category = await Category.query().insert({name: 'Electronics'});
|
|
35
|
+
let products = await Product.query().where('category_id', category.id).withGraphFetched('related_reviews');
|
|
36
|
+
|
|
37
|
+
// MikroORM
|
|
38
|
+
let category = await em.create(Category, {name: 'Electronics'});
|
|
39
|
+
await em.flush();
|
|
40
|
+
let products = await em.find(Product, {category_id: category.id}, {populate: ['related_reviews']});
|
|
41
|
+
|
|
42
|
+
// Prisma
|
|
43
|
+
let category = await prisma.category.create({data: {name: 'Electronics'}});
|
|
44
|
+
let products = await prisma.product.findMany({where: {category_id: category.id}, include: {related_reviews: true}});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Each ORM has different:**
|
|
48
|
+
- Method names
|
|
49
|
+
- Parameter structures
|
|
50
|
+
- Query syntax
|
|
51
|
+
- Relation loading syntax
|
|
52
|
+
- Filter operators
|
|
53
|
+
|
|
54
|
+
**With abstraction:**
|
|
55
|
+
```javascript
|
|
56
|
+
// ONE codebase works with ALL ORMs
|
|
57
|
+
await repo.create(params);
|
|
58
|
+
await repo.loadWithRelations(filters, relations);
|
|
59
|
+
await repo.count(filters);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### The Contract
|
|
63
|
+
|
|
64
|
+
**Every driver MUST implement:**
|
|
65
|
+
1. **Same method signatures** - Same method names, same parameters
|
|
66
|
+
2. **Same behavior** - Same inputs produce same outputs
|
|
67
|
+
3. **Same return format** - Results structure is identical
|
|
68
|
+
4. **Driver-agnostic code** - Application code doesn't know/care which driver is used
|
|
69
|
+
|
|
70
|
+
**The BaseDriver class defines this contract** (lib/base-driver.js)
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 2. PUBLIC METHODS - The Complete API
|
|
75
|
+
|
|
76
|
+
All methods defined in BaseDriver that ALL drivers must implement:
|
|
77
|
+
|
|
78
|
+
### CREATE Operations
|
|
79
|
+
- `create(params)` - Create single record
|
|
80
|
+
- `createWithRelations(params, relations)` - Create record with nested relations
|
|
81
|
+
|
|
82
|
+
### READ Operations (No Relations)
|
|
83
|
+
- `loadAll()` - Load all records (no filters, respects limit/offset/sort)
|
|
84
|
+
- `load(filters)` - Load records by filters (respects limit/offset/sort)
|
|
85
|
+
- `loadBy(field, fieldValue, operator)` - Load records by single field
|
|
86
|
+
- `loadById(id)` - Load single record by ID
|
|
87
|
+
- `loadByIds(ids)` - Load multiple records by IDs array
|
|
88
|
+
- `loadOne(filters)` - Load first record matching filters
|
|
89
|
+
- `loadOneBy(field, fieldValue, operator)` - Load first record by single field
|
|
90
|
+
|
|
91
|
+
### READ Operations (With Relations)
|
|
92
|
+
- `loadAllWithRelations(relations)` - Load all records with relations
|
|
93
|
+
- `loadWithRelations(filters, relations)` - Load records by filters with relations
|
|
94
|
+
- `loadByWithRelations(field, fieldValue, relations, operator)` - Load by field with relations
|
|
95
|
+
- `loadByIdWithRelations(id, relations)` - Load by ID with relations
|
|
96
|
+
- `loadOneWithRelations(filters, relations)` - Load first record with relations
|
|
97
|
+
- `loadOneByWithRelations(field, fieldValue, relations, operator)` - Load first by field with relations
|
|
98
|
+
|
|
99
|
+
### UPDATE Operations
|
|
100
|
+
- `update(filters, updatePatch)` - Update records by filters
|
|
101
|
+
- `updateBy(field, fieldValue, updatePatch, operator)` - Update by single field
|
|
102
|
+
- `updateById(id, params)` - Update single record by ID
|
|
103
|
+
- `upsert(params, filters)` - Insert if not exists, update if exists
|
|
104
|
+
|
|
105
|
+
### DELETE Operations
|
|
106
|
+
- `delete(filters)` - Delete records by filters
|
|
107
|
+
- `deleteById(id)` - Delete single record by ID
|
|
108
|
+
|
|
109
|
+
### COUNT Operations
|
|
110
|
+
- `count(filters)` - Count records by filters
|
|
111
|
+
- `countWithRelations(filters, relations)` - Count records with relation filters
|
|
112
|
+
|
|
113
|
+
### UTILITY Operations
|
|
114
|
+
- `rawQuery(content)` - Execute raw SQL query
|
|
115
|
+
- `executeCustomQuery(methodName, methodOptions)` - Execute custom model method
|
|
116
|
+
- `isJsonField(fieldName)` - Check if field is JSON type
|
|
117
|
+
- `parseRelationsString(relationsString)` - Parse relation string to array
|
|
118
|
+
|
|
119
|
+
### PROPERTY Accessors
|
|
120
|
+
- `databaseName()` - Get database name
|
|
121
|
+
- `id()` - Get entity ID
|
|
122
|
+
- `name()` - Get entity name
|
|
123
|
+
- `tableName()` - Get table name
|
|
124
|
+
- `property(propertyName)` - Get model property
|
|
125
|
+
|
|
126
|
+
### CONFIGURATION Properties
|
|
127
|
+
- `this.limit` - Result limit (0 = no limit)
|
|
128
|
+
- `this.offset` - Result offset
|
|
129
|
+
- `this.sortBy` - Sort field name
|
|
130
|
+
- `this.sortDirection` - Sort direction ('ASC' or 'DESC')
|
|
131
|
+
- `this.select` - Fields to select (array)
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 3. METHOD-BY-METHOD ANALYSIS
|
|
136
|
+
|
|
137
|
+
### 3.1 create(params)
|
|
138
|
+
|
|
139
|
+
**Purpose:** Create a single record
|
|
140
|
+
|
|
141
|
+
**Parameters:**
|
|
142
|
+
- `params` (object) - Record data
|
|
143
|
+
|
|
144
|
+
**Expected Behavior:**
|
|
145
|
+
- Insert record into database
|
|
146
|
+
- Return created record with ID
|
|
147
|
+
- Throw error if constraints violated (UNIQUE, FK, NOT NULL)
|
|
148
|
+
|
|
149
|
+
**Return:** Created record object with all fields including auto-generated ID
|
|
150
|
+
|
|
151
|
+
#### ObjectionJS Implementation (objection-js-driver.js:38-41)
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
create(params)
|
|
155
|
+
{
|
|
156
|
+
return this.queryBuilder().insert(params);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**How it works:**
|
|
161
|
+
- Calls Objection's `insert()` method
|
|
162
|
+
- Passes params directly to Knex
|
|
163
|
+
- Returns promise that resolves to created record
|
|
164
|
+
|
|
165
|
+
**Behavior:**
|
|
166
|
+
- ✅ Accepts explicit IDs for autoincrement fields
|
|
167
|
+
- ✅ Returns created record with ID
|
|
168
|
+
- ✅ Throws on constraint violations
|
|
169
|
+
|
|
170
|
+
#### MikroORM Implementation (mikro-orm-driver.js:85-92)
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
async create(params)
|
|
174
|
+
{
|
|
175
|
+
let transformed = this.transformFkToRelations(params);
|
|
176
|
+
let newInstance = await this.orm.em.create(this.rawModel, transformed);
|
|
177
|
+
await this.orm.em.upsert(newInstance);
|
|
178
|
+
await this.orm.em.flush();
|
|
179
|
+
return newInstance;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**How it works:**
|
|
184
|
+
1. Transforms FK fields to relation objects via `transformFkToRelations()`
|
|
185
|
+
- Converts `category_id: 1` to `related_categories: Reference<Category>(1)`
|
|
186
|
+
2. Creates entity instance via `em.create()`
|
|
187
|
+
3. Upserts to identity map via `em.upsert()`
|
|
188
|
+
4. Flushes to database via `em.flush()`
|
|
189
|
+
5. Returns entity instance
|
|
190
|
+
|
|
191
|
+
**transformFkToRelations() (lines 497-512):**
|
|
192
|
+
```javascript
|
|
193
|
+
transformFkToRelations(params)
|
|
194
|
+
{
|
|
195
|
+
let transformed = {...params};
|
|
196
|
+
for(let fkColumn of Object.keys(this.fkMappings)){
|
|
197
|
+
if(!sc.hasOwn(params, fkColumn) || null === params[fkColumn]){
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
let mapping = this.fkMappings[fkColumn];
|
|
201
|
+
transformed[mapping.relationKey] = this.orm.em.getReference(mapping.entityName, params[fkColumn]);
|
|
202
|
+
delete transformed[fkColumn];
|
|
203
|
+
}
|
|
204
|
+
return transformed;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Behavior:**
|
|
209
|
+
- ✅ Accepts explicit IDs for autoincrement fields
|
|
210
|
+
- ✅ Returns created record with ID
|
|
211
|
+
- ✅ Throws on constraint violations
|
|
212
|
+
- ⚠️ Transforms FK columns to MikroORM References
|
|
213
|
+
|
|
214
|
+
#### Prisma Implementation (prisma-driver.js:225-234)
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
async create(params)
|
|
218
|
+
{
|
|
219
|
+
let preparedData = this.prepareData(params, {convertRelations: true, isCreate: true});
|
|
220
|
+
this.ensureRequiredFields(preparedData);
|
|
221
|
+
return this.executeWithNormalization(
|
|
222
|
+
() => this.model.create({data: preparedData}),
|
|
223
|
+
'Create error: ',
|
|
224
|
+
false
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**How it works:**
|
|
230
|
+
1. Prepares data via `prepareData()` with `isCreate: true`
|
|
231
|
+
- Removes autoincrement ID field (line 170-172)
|
|
232
|
+
- Converts FK fields to relation connect syntax
|
|
233
|
+
- Casts types
|
|
234
|
+
2. Validates required fields via `ensureRequiredFields()`
|
|
235
|
+
3. Calls Prisma's `model.create()`
|
|
236
|
+
4. Normalizes return data (converts BigInt, etc)
|
|
237
|
+
|
|
238
|
+
**prepareData() for create (lines 150-189):**
|
|
239
|
+
```javascript
|
|
240
|
+
prepareData(params, options = {})
|
|
241
|
+
{
|
|
242
|
+
let isCreate = sc.get(options, 'isCreate', false);
|
|
243
|
+
let convertRelations = sc.get(options, 'convertRelations', false);
|
|
244
|
+
if(convertRelations){
|
|
245
|
+
let converted = this.relationResolver.convertForeignKeysToRelations(params, isCreate);
|
|
246
|
+
return this.castDataFields(converted, skipObjects, isCreate);
|
|
247
|
+
}
|
|
248
|
+
return this.castDataFields(params, skipObjects, isCreate);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
castDataFields(params, skipObjects = false, isCreate = false)
|
|
252
|
+
{
|
|
253
|
+
let data = {};
|
|
254
|
+
for(let key of Object.keys(params)){
|
|
255
|
+
let value = params[key];
|
|
256
|
+
if(isCreate && key === this.idFieldName && this.typeCaster.isNullOrEmpty(value)){
|
|
257
|
+
continue; // Skip ID field in create
|
|
258
|
+
}
|
|
259
|
+
// ... type casting logic
|
|
260
|
+
}
|
|
261
|
+
return data;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Behavior:**
|
|
266
|
+
- ❌ REJECTS explicit IDs for autoincrement fields (removed in prepareData)
|
|
267
|
+
- ✅ Returns created record with auto-generated ID
|
|
268
|
+
- ✅ Throws on constraint violations
|
|
269
|
+
- ⚠️ Converts FK columns to Prisma connect syntax
|
|
270
|
+
|
|
271
|
+
**Consistency Analysis:**
|
|
272
|
+
- ✅ All return created record with ID
|
|
273
|
+
- ✅ All throw on constraint violations
|
|
274
|
+
- ❌ **INCONSISTENT**: ObjectionJS and MikroORM accept explicit IDs, Prisma rejects them
|
|
275
|
+
- ⚠️ All three transform FK fields differently internally
|
|
276
|
+
|
|
277
|
+
**Tests Impact:**
|
|
278
|
+
- Basic CREATE tests work when NOT passing explicit IDs
|
|
279
|
+
- Prisma fails when fixtures contain explicit IDs
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### 3.2 createWithRelations(params, relations)
|
|
284
|
+
|
|
285
|
+
**Purpose:** Create a record along with nested related records in a single operation
|
|
286
|
+
|
|
287
|
+
**Parameters:**
|
|
288
|
+
- `params` (object) - Parent record data with nested relation data
|
|
289
|
+
- `relations` (array) - Relation names to create (e.g., ['related_products'])
|
|
290
|
+
|
|
291
|
+
**Expected Behavior:**
|
|
292
|
+
- Create parent record
|
|
293
|
+
- Create child records
|
|
294
|
+
- Link child records to parent via FK
|
|
295
|
+
- Return parent record with relations populated
|
|
296
|
+
|
|
297
|
+
**Return:** Created parent record with nested relations
|
|
298
|
+
|
|
299
|
+
#### ObjectionJS Implementation (objection-js-driver.js:43-46)
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
createWithRelations(params, relations)
|
|
303
|
+
{
|
|
304
|
+
return this.queryBuilder().insertGraphAndFetch(params);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**How it works:**
|
|
309
|
+
- Uses Objection's `insertGraphAndFetch()` method
|
|
310
|
+
- Recursively inserts parent and children
|
|
311
|
+
- Automatically handles FK relationships
|
|
312
|
+
- Returns complete graph with all IDs
|
|
313
|
+
|
|
314
|
+
**Example:**
|
|
315
|
+
```javascript
|
|
316
|
+
await categoriesRepo.createWithRelations({
|
|
317
|
+
name: 'Electronics',
|
|
318
|
+
related_products: [
|
|
319
|
+
{name: 'Laptop', sku: 'LAP-001'}
|
|
320
|
+
]
|
|
321
|
+
}, ['related_products']);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Behavior:**
|
|
325
|
+
- ✅ Supports nested CREATE (creates new children)
|
|
326
|
+
- ✅ Works with explicit IDs
|
|
327
|
+
- ✅ Works without explicit IDs
|
|
328
|
+
- ✅ Returns parent with populated relations
|
|
329
|
+
|
|
330
|
+
#### MikroORM Implementation (mikro-orm-driver.js:94-102)
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
async createWithRelations(params, relations)
|
|
334
|
+
{
|
|
335
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
336
|
+
relations = this.getAllRelations();
|
|
337
|
+
}
|
|
338
|
+
let newInstance = await this.create(params);
|
|
339
|
+
await this.createNested(newInstance, params, relations);
|
|
340
|
+
return await this.appendRelationsToCollection(newInstance, relations);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**How it works:**
|
|
345
|
+
1. Create parent via `this.create(params)` (ignores relation fields)
|
|
346
|
+
2. Create nested children via `createNested()`
|
|
347
|
+
3. Populate relations via `appendRelationsToCollection()`
|
|
348
|
+
|
|
349
|
+
**createNested() (lines 441-470):**
|
|
350
|
+
```javascript
|
|
351
|
+
async createNested(newInstance, params, relations)
|
|
352
|
+
{
|
|
353
|
+
for(let relationName of Object.keys(properties)){
|
|
354
|
+
let prop = properties[relationName];
|
|
355
|
+
if(isOneToMany && sc.hasOwn(params, relationName) && sc.isArray(params[relationName])){
|
|
356
|
+
await this.createMany(params, relationName, relationDriver, newInstance, prop);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**createMany() (lines 483-495):**
|
|
363
|
+
```javascript
|
|
364
|
+
async createMany(params, relationName, relationDriver, newInstance, prop)
|
|
365
|
+
{
|
|
366
|
+
let nestedArray = [];
|
|
367
|
+
for(let objectData of params[relationName]){
|
|
368
|
+
if(prop.joinColumn){
|
|
369
|
+
objectData[prop.joinColumn] = newInstance.id; // Set FK to parent ID
|
|
370
|
+
}
|
|
371
|
+
let nestedObject = await relationDriver.create(objectData);
|
|
372
|
+
await this.orm.em.flush();
|
|
373
|
+
nestedArray.push(nestedObject);
|
|
374
|
+
}
|
|
375
|
+
newInstance[relationName] = nestedArray;
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Behavior:**
|
|
380
|
+
- ✅ Supports nested CREATE (creates new children)
|
|
381
|
+
- ✅ Works with explicit IDs
|
|
382
|
+
- ⚠️ **BREAKS with delete operator** on properties (JIT compiler issue)
|
|
383
|
+
- ✅ Returns parent with populated relations
|
|
384
|
+
|
|
385
|
+
#### Prisma Implementation (prisma-driver.js:236-264)
|
|
386
|
+
|
|
387
|
+
```javascript
|
|
388
|
+
async createWithRelations(params, relations)
|
|
389
|
+
{
|
|
390
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
391
|
+
relations = this.getAllRelations();
|
|
392
|
+
}
|
|
393
|
+
let preparedData = this.prepareData(params); // NOTE: no isCreate flag!
|
|
394
|
+
this.ensureRequiredFields(preparedData);
|
|
395
|
+
let createData = {data: preparedData};
|
|
396
|
+
if(0 < relations.length){
|
|
397
|
+
let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
|
|
398
|
+
createData.include = includeData.include;
|
|
399
|
+
let relationData = this.relationResolver.buildCreateDataWithRelations(params, relations);
|
|
400
|
+
createData.data = {...createData.data, ...relationData};
|
|
401
|
+
return this.executeWithNormalization(
|
|
402
|
+
async () => this.relationResolver.transformRelationResults(
|
|
403
|
+
await this.model.create(createData),
|
|
404
|
+
includeData.include,
|
|
405
|
+
includeData.mapping
|
|
406
|
+
),
|
|
407
|
+
'Create with relations error: ',
|
|
408
|
+
false
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
return this.executeWithNormalization(
|
|
412
|
+
() => this.model.create(createData),
|
|
413
|
+
'Create with relations error: ',
|
|
414
|
+
false
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**buildCreateDataWithRelations() (prisma-relation-resolver.js:234-263):**
|
|
420
|
+
```javascript
|
|
421
|
+
buildCreateDataWithRelations(params, relations)
|
|
422
|
+
{
|
|
423
|
+
let createData = {};
|
|
424
|
+
for(let relation of relations){
|
|
425
|
+
let relationData = params[relation];
|
|
426
|
+
if(sc.isArray(relationData)){
|
|
427
|
+
createData[prismaName] = {
|
|
428
|
+
connect: relationData.map(item => ({id: this.typeCaster.castToIdType(item.id)}))
|
|
429
|
+
};
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
createData[prismaName] = {
|
|
433
|
+
connect: {id: this.typeCaster.castToIdType(relationData.id)}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return createData;
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**CRITICAL ISSUE:** Uses `connect` syntax, NOT `create` syntax!
|
|
441
|
+
|
|
442
|
+
**Prisma's nested CREATE syntax should be:**
|
|
443
|
+
```javascript
|
|
444
|
+
{
|
|
445
|
+
data: {
|
|
446
|
+
name: 'Electronics',
|
|
447
|
+
related_products: {
|
|
448
|
+
create: [ // ← Should use "create"
|
|
449
|
+
{name: 'Laptop', sku: 'LAP-001'}
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
**What our code does:**
|
|
457
|
+
```javascript
|
|
458
|
+
{
|
|
459
|
+
data: {
|
|
460
|
+
name: 'Electronics',
|
|
461
|
+
related_products: {
|
|
462
|
+
connect: [ // ← WRONG: Tries to link existing records
|
|
463
|
+
{id: undefined} // ← Fails because no ID
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**Behavior:**
|
|
471
|
+
- ❌ **DOES NOT support nested CREATE**
|
|
472
|
+
- ❌ Only supports CONNECT (linking existing records)
|
|
473
|
+
- ❌ Requires child records to already exist with IDs
|
|
474
|
+
- ❌ Current implementation is broken
|
|
475
|
+
|
|
476
|
+
**Consistency Analysis:**
|
|
477
|
+
- ✅ ObjectionJS: Full nested CREATE support
|
|
478
|
+
- ⚠️ MikroORM: Full nested CREATE support BUT breaks with delete operator
|
|
479
|
+
- ❌ Prisma: NO nested CREATE support - only CONNECT
|
|
480
|
+
- **MAJOR INCONSISTENCY** - Method doesn't work the same across drivers
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### 3.3 update(filters, updatePatch)
|
|
485
|
+
|
|
486
|
+
**Purpose:** Update multiple records matching filters
|
|
487
|
+
|
|
488
|
+
**Parameters:**
|
|
489
|
+
- `filters` (object) - Filter conditions
|
|
490
|
+
- `updatePatch` (object) - Fields to update
|
|
491
|
+
|
|
492
|
+
**Expected Behavior:**
|
|
493
|
+
- Find all records matching filters
|
|
494
|
+
- Update specified fields
|
|
495
|
+
- Return updated records (or result indicator)
|
|
496
|
+
|
|
497
|
+
**Return:** Updated records or result object
|
|
498
|
+
|
|
499
|
+
#### ObjectionJS Implementation (objection-js-driver.js:48-53)
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
update(filters, updatePatch)
|
|
503
|
+
{
|
|
504
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
505
|
+
this.appendFilters(queryBuilder, filters);
|
|
506
|
+
return queryBuilder.patch(updatePatch);
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**How it works:**
|
|
511
|
+
- Builds query with limit/offset/sort
|
|
512
|
+
- Appends filters via `appendFilters()`
|
|
513
|
+
- Calls Knex `patch()` method
|
|
514
|
+
- Returns number of affected rows
|
|
515
|
+
|
|
516
|
+
**Return type:** Number (count of updated rows)
|
|
517
|
+
|
|
518
|
+
#### MikroORM Implementation (mikro-orm-driver.js:119-123)
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
async update(filters, updatePatch)
|
|
522
|
+
{
|
|
523
|
+
let processedFilters = this.processFilters(filters);
|
|
524
|
+
return this.applyUpdateToEntities(processedFilters, updatePatch);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async applyUpdateToEntities(processedFilters, updatePatch)
|
|
528
|
+
{
|
|
529
|
+
let entities = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
|
|
530
|
+
if(0 === entities.length){
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
let transformed = this.transformFkToRelations(updatePatch);
|
|
534
|
+
for(let entity of entities){
|
|
535
|
+
Object.assign(entity, transformed);
|
|
536
|
+
await this.orm.em.upsert(entity);
|
|
537
|
+
await this.orm.em.flush();
|
|
538
|
+
}
|
|
539
|
+
return entities;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
**How it works:**
|
|
544
|
+
1. Find entities matching filters
|
|
545
|
+
2. Transform FK fields to relations
|
|
546
|
+
3. Update each entity via Object.assign
|
|
547
|
+
4. Upsert and flush each entity
|
|
548
|
+
5. Return array of updated entities
|
|
549
|
+
|
|
550
|
+
**Return type:** Array of entity objects OR false
|
|
551
|
+
|
|
552
|
+
#### Prisma Implementation (prisma-driver.js:266-275)
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
async update(filters, updatePatch)
|
|
556
|
+
{
|
|
557
|
+
let preparedData = this.prepareData(updatePatch, {skipObjects: true});
|
|
558
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
559
|
+
return this.executeWithNormalization(
|
|
560
|
+
() => this.model.updateMany({where: processedFilters, data: preparedData}),
|
|
561
|
+
'Update error: ',
|
|
562
|
+
false
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
**How it works:**
|
|
568
|
+
- Prepare data (skip object fields, convert relations)
|
|
569
|
+
- Process filters
|
|
570
|
+
- Call Prisma's `updateMany()`
|
|
571
|
+
- Normalize return (count of updated rows)
|
|
572
|
+
|
|
573
|
+
**Return type:** Object with `count` property OR false
|
|
574
|
+
|
|
575
|
+
**Consistency Analysis:**
|
|
576
|
+
- ❌ **RETURN TYPE INCONSISTENT**:
|
|
577
|
+
- ObjectionJS: Number (count)
|
|
578
|
+
- MikroORM: Array of entities OR false
|
|
579
|
+
- Prisma: Object {count: N} OR false
|
|
580
|
+
- ✅ All update multiple records
|
|
581
|
+
- ✅ All respect filters
|
|
582
|
+
|
|
583
|
+
**Tests Impact:**
|
|
584
|
+
- Tests check for truthy return value (`assert.ok(updated)`)
|
|
585
|
+
- Works for all drivers despite different return types
|
|
586
|
+
- MikroORM returns full entities (more data than needed)
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
### 3.4 updateById(id, params)
|
|
591
|
+
|
|
592
|
+
**Purpose:** Update single record by ID
|
|
593
|
+
|
|
594
|
+
**Parameters:**
|
|
595
|
+
- `id` - Record ID
|
|
596
|
+
- `params` (object) - Fields to update
|
|
597
|
+
|
|
598
|
+
**Expected Behavior:**
|
|
599
|
+
- Find record by ID
|
|
600
|
+
- Update specified fields
|
|
601
|
+
- Return updated record
|
|
602
|
+
|
|
603
|
+
**Return:** Updated record object
|
|
604
|
+
|
|
605
|
+
#### ObjectionJS Implementation (objection-js-driver.js:62-65)
|
|
606
|
+
|
|
607
|
+
```javascript
|
|
608
|
+
updateById(id, params)
|
|
609
|
+
{
|
|
610
|
+
return this.queryBuilder().patchAndFetchById(id, params);
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**How it works:**
|
|
615
|
+
- Uses Objection's `patchAndFetchById()` method
|
|
616
|
+
- Updates and returns updated record in one operation
|
|
617
|
+
|
|
618
|
+
**Return type:** Updated record object
|
|
619
|
+
|
|
620
|
+
#### MikroORM Implementation (mikro-orm-driver.js:132-135)
|
|
621
|
+
|
|
622
|
+
```javascript
|
|
623
|
+
updateById(id, params)
|
|
624
|
+
{
|
|
625
|
+
return this.update({id}, params);
|
|
626
|
+
}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
**How it works:**
|
|
630
|
+
- Delegates to `update()` with `{id}` filter
|
|
631
|
+
- Returns array of entities (from update())
|
|
632
|
+
|
|
633
|
+
**Return type:** Array with single entity OR false
|
|
634
|
+
|
|
635
|
+
#### Prisma Implementation (prisma-driver.js:288-301)
|
|
636
|
+
|
|
637
|
+
```javascript
|
|
638
|
+
async updateById(id, params)
|
|
639
|
+
{
|
|
640
|
+
let castedId = this.typeCaster.castToIdType(id);
|
|
641
|
+
let found = await this.loadById(castedId);
|
|
642
|
+
if(!found){
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
let preparedData = this.prepareData(params, {convertRelations: true});
|
|
646
|
+
return this.executeWithNormalization(
|
|
647
|
+
() => this.model.update({where: {[this.idFieldName]: castedId}, data: preparedData}),
|
|
648
|
+
'Update by ID error: ',
|
|
649
|
+
false
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
**How it works:**
|
|
655
|
+
1. Cast ID to correct type
|
|
656
|
+
2. Check if record exists via `loadById()`
|
|
657
|
+
3. Return false if not found
|
|
658
|
+
4. Prepare data with relation conversion
|
|
659
|
+
5. Call Prisma's `update()`
|
|
660
|
+
6. Return updated record
|
|
661
|
+
|
|
662
|
+
**Return type:** Updated record object OR false
|
|
663
|
+
|
|
664
|
+
**Consistency Analysis:**
|
|
665
|
+
- ⚠️ **RETURN TYPE INCONSISTENT**:
|
|
666
|
+
- ObjectionJS: Record object
|
|
667
|
+
- MikroORM: Array [record] OR false
|
|
668
|
+
- Prisma: Record object OR false
|
|
669
|
+
- ✅ All update by ID
|
|
670
|
+
- ⚠️ MikroORM returns array instead of single object
|
|
671
|
+
|
|
672
|
+
**Tests Impact:**
|
|
673
|
+
- Tests expect single object: `assert.strictEqual(updated.name, 'Updated Name')`
|
|
674
|
+
- **MikroORM tests FAIL** because it returns array
|
|
675
|
+
- Need to check test logs for MikroORM updateById failures
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
### 3.5 updateBy(field, fieldValue, updatePatch, operator)
|
|
680
|
+
|
|
681
|
+
**Purpose:** Update multiple records matching a single field condition
|
|
682
|
+
|
|
683
|
+
**Parameters:**
|
|
684
|
+
- `field` (string) - Field name to filter by
|
|
685
|
+
- `fieldValue` - Value to match
|
|
686
|
+
- `updatePatch` (object) - Fields to update
|
|
687
|
+
- `operator` (string, optional) - Comparison operator (GT, LT, LIKE, etc.)
|
|
688
|
+
|
|
689
|
+
**Expected Behavior:**
|
|
690
|
+
- Find records where field matches value (with optional operator)
|
|
691
|
+
- Update specified fields
|
|
692
|
+
- Return updated records
|
|
693
|
+
|
|
694
|
+
**Return:** Updated records or result indicator
|
|
695
|
+
|
|
696
|
+
#### ObjectionJS Implementation (objection-js-driver.js:55-60)
|
|
697
|
+
|
|
698
|
+
```javascript
|
|
699
|
+
updateBy(field, fieldValue, updatePatch, operator = null)
|
|
700
|
+
{
|
|
701
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
702
|
+
this.appendSingleFilter(queryBuilder, field, fieldValue, operator);
|
|
703
|
+
return queryBuilder.patch(updatePatch);
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**Return type:** Number (count of updated rows)
|
|
708
|
+
|
|
709
|
+
#### MikroORM Implementation (mikro-orm-driver.js:125-130)
|
|
710
|
+
|
|
711
|
+
```javascript
|
|
712
|
+
async updateBy(field, fieldValue, updatePatch, operator = null)
|
|
713
|
+
{
|
|
714
|
+
let filter = this.createSingleFilter(field, fieldValue, operator);
|
|
715
|
+
let processedFilters = this.processFilters(filter);
|
|
716
|
+
return this.applyUpdateToEntities(processedFilters, updatePatch);
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**Return type:** Array of entities OR false
|
|
721
|
+
|
|
722
|
+
#### Prisma Implementation (prisma-driver.js:277-286)
|
|
723
|
+
|
|
724
|
+
```javascript
|
|
725
|
+
async updateBy(field, fieldValue, updatePatch, operator = null)
|
|
726
|
+
{
|
|
727
|
+
let filter = this.filterProcessor.createSingleFilter(field, fieldValue, operator);
|
|
728
|
+
let preparedData = this.prepareData(updatePatch, {skipObjects: true});
|
|
729
|
+
return this.executeWithNormalization(
|
|
730
|
+
() => this.model.updateMany({where: filter, data: preparedData}),
|
|
731
|
+
'Update by error: ',
|
|
732
|
+
false
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Return type:** Object {count: N} OR false
|
|
738
|
+
|
|
739
|
+
**Consistency Analysis:**
|
|
740
|
+
- ❌ **RETURN TYPE INCONSISTENT** (same as update())
|
|
741
|
+
- ✅ All support operator parameter
|
|
742
|
+
- ✅ All update multiple records
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
### 3.6 upsert(params, filters)
|
|
747
|
+
|
|
748
|
+
**Purpose:** Insert if record doesn't exist, update if it does
|
|
749
|
+
|
|
750
|
+
**Parameters:**
|
|
751
|
+
- `params` (object) - Record data (with or without ID)
|
|
752
|
+
- `filters` (object, optional) - Alternate filters to find existing record
|
|
753
|
+
|
|
754
|
+
**Expected Behavior:**
|
|
755
|
+
- If params.id exists and record found: UPDATE by ID
|
|
756
|
+
- Else if filters provided and record found: UPDATE by filters
|
|
757
|
+
- Else: CREATE new record
|
|
758
|
+
|
|
759
|
+
**Return:** Updated or created record
|
|
760
|
+
|
|
761
|
+
#### ObjectionJS Implementation (objection-js-driver.js:67-82)
|
|
762
|
+
|
|
763
|
+
```javascript
|
|
764
|
+
async upsert(params, filters)
|
|
765
|
+
{
|
|
766
|
+
if(params.id){
|
|
767
|
+
let existent = await this.loadById(params.id);
|
|
768
|
+
if(existent){
|
|
769
|
+
return this.updateById(params.id, params);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if(filters){
|
|
773
|
+
let existent = await this.loadOne(filters);
|
|
774
|
+
if(existent){
|
|
775
|
+
return this.updateById(existent.id, params);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
return this.create(params);
|
|
779
|
+
}
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
**Logic:**
|
|
783
|
+
1. Check if record exists by ID
|
|
784
|
+
2. Check if record exists by filters
|
|
785
|
+
3. If found: update, else: create
|
|
786
|
+
|
|
787
|
+
**Return type:** Record object
|
|
788
|
+
|
|
789
|
+
#### MikroORM Implementation (mikro-orm-driver.js:137-152)
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
async upsert(params, filters)
|
|
793
|
+
{
|
|
794
|
+
if(params.id){
|
|
795
|
+
let patch = Object.assign({}, params);
|
|
796
|
+
delete patch.id;
|
|
797
|
+
return this.updateById(params.id, patch);
|
|
798
|
+
}
|
|
799
|
+
if(filters){
|
|
800
|
+
let existent = await this.loadOne(filters);
|
|
801
|
+
if(existent){
|
|
802
|
+
let transformed = this.transformFkToRelations(params);
|
|
803
|
+
return this.updateById(existent.id, transformed);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return this.create(params);
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
**Logic:**
|
|
811
|
+
1. If params.id: update by ID (removes ID from patch)
|
|
812
|
+
2. Check if record exists by filters
|
|
813
|
+
3. If found: update, else: create
|
|
814
|
+
|
|
815
|
+
**Return type:** Array [record] OR Record (from updateById or create)
|
|
816
|
+
|
|
817
|
+
#### Prisma Implementation (prisma-driver.js:303-334)
|
|
818
|
+
|
|
819
|
+
```javascript
|
|
820
|
+
async upsert(params, filters)
|
|
821
|
+
{
|
|
822
|
+
let preparedData = this.prepareData(params, {convertRelations: true});
|
|
823
|
+
let idValue = params[this.idFieldName];
|
|
824
|
+
if(idValue){
|
|
825
|
+
let castedId = this.typeCaster.castToIdType(idValue);
|
|
826
|
+
let existing = await this.loadById(castedId);
|
|
827
|
+
if(existing){
|
|
828
|
+
let patch = Object.assign({}, preparedData);
|
|
829
|
+
delete patch[this.idFieldName];
|
|
830
|
+
return this.executeWithNormalization(
|
|
831
|
+
() => this.model.update({where: {[this.idFieldName]: castedId}, data: patch}),
|
|
832
|
+
'Upsert update error: ',
|
|
833
|
+
false
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if(filters){
|
|
838
|
+
let existing = await this.loadOne(filters);
|
|
839
|
+
if(existing){
|
|
840
|
+
return this.executeWithNormalization(
|
|
841
|
+
() => this.model.update({
|
|
842
|
+
where: {[this.idFieldName]: this.typeCaster.castToIdType(existing[this.idFieldName])},
|
|
843
|
+
data: preparedData
|
|
844
|
+
}),
|
|
845
|
+
'Upsert update by filter error: ',
|
|
846
|
+
false
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return await this.create(preparedData);
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
**Logic:** Same as ObjectionJS
|
|
855
|
+
|
|
856
|
+
**Return type:** Record object OR false
|
|
857
|
+
|
|
858
|
+
**Consistency Analysis:**
|
|
859
|
+
- ✅ All have same logic flow
|
|
860
|
+
- ⚠️ MikroORM has inconsistent return (array from updateById)
|
|
861
|
+
- ✅ All support ID-based and filter-based detection
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
### 3.7 delete(filters)
|
|
866
|
+
|
|
867
|
+
**Purpose:** Delete multiple records matching filters
|
|
868
|
+
|
|
869
|
+
**Parameters:**
|
|
870
|
+
- `filters` (object) - Filter conditions
|
|
871
|
+
|
|
872
|
+
**Expected Behavior:**
|
|
873
|
+
- Find all records matching filters
|
|
874
|
+
- Delete them
|
|
875
|
+
- Return success indicator
|
|
876
|
+
|
|
877
|
+
**Return:** Boolean or count
|
|
878
|
+
|
|
879
|
+
#### ObjectionJS Implementation (objection-js-driver.js:84-88)
|
|
880
|
+
|
|
881
|
+
```javascript
|
|
882
|
+
delete(filters)
|
|
883
|
+
{
|
|
884
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
885
|
+
return this.appendFilters(queryBuilder, filters).delete();
|
|
886
|
+
}
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
**Return type:** Number (count of deleted rows)
|
|
890
|
+
|
|
891
|
+
#### MikroORM Implementation (mikro-orm-driver.js:154-166)
|
|
892
|
+
|
|
893
|
+
```javascript
|
|
894
|
+
async delete(filters = {})
|
|
895
|
+
{
|
|
896
|
+
let processedFilters = this.processFilters(filters);
|
|
897
|
+
let entries = await this.repository.find(processedFilters);
|
|
898
|
+
if(!entries || 0 === entries.length){
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
for(let entry of entries){
|
|
902
|
+
await this.orm.em.remove(entry);
|
|
903
|
+
}
|
|
904
|
+
await this.orm.em.flush();
|
|
905
|
+
return true;
|
|
906
|
+
}
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
**Return type:** Boolean (true or false)
|
|
910
|
+
|
|
911
|
+
#### Prisma Implementation (prisma-driver.js:336-344)
|
|
912
|
+
|
|
913
|
+
```javascript
|
|
914
|
+
async delete(filters = {})
|
|
915
|
+
{
|
|
916
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
917
|
+
return this.executeWithNormalization(
|
|
918
|
+
() => this.model.deleteMany({where: processedFilters}),
|
|
919
|
+
'Delete error: ',
|
|
920
|
+
false
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
**Return type:** Object {count: N} OR false
|
|
926
|
+
|
|
927
|
+
**Consistency Analysis:**
|
|
928
|
+
- ❌ **RETURN TYPE INCONSISTENT**:
|
|
929
|
+
- ObjectionJS: Number
|
|
930
|
+
- MikroORM: Boolean
|
|
931
|
+
- Prisma: Object {count: N} OR false
|
|
932
|
+
- ✅ All delete multiple records
|
|
933
|
+
- ✅ All support filters
|
|
934
|
+
|
|
935
|
+
---
|
|
936
|
+
|
|
937
|
+
### 3.8 deleteById(id)
|
|
938
|
+
|
|
939
|
+
**Purpose:** Delete single record by ID
|
|
940
|
+
|
|
941
|
+
**Parameters:**
|
|
942
|
+
- `id` - Record ID
|
|
943
|
+
|
|
944
|
+
**Expected Behavior:**
|
|
945
|
+
- Find record by ID
|
|
946
|
+
- Delete it
|
|
947
|
+
- Return success indicator
|
|
948
|
+
|
|
949
|
+
**Return:** Boolean or result object
|
|
950
|
+
|
|
951
|
+
#### ObjectionJS Implementation (objection-js-driver.js:90-93)
|
|
952
|
+
|
|
953
|
+
```javascript
|
|
954
|
+
deleteById(id)
|
|
955
|
+
{
|
|
956
|
+
return this.queryBuilder().deleteById(id);
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
**Return type:** Number (1 if deleted, 0 if not found)
|
|
961
|
+
|
|
962
|
+
#### MikroORM Implementation (mikro-orm-driver.js:168-177)
|
|
963
|
+
|
|
964
|
+
```javascript
|
|
965
|
+
async deleteById(id)
|
|
966
|
+
{
|
|
967
|
+
let entity = await this.loadById(id);
|
|
968
|
+
if(!entity){
|
|
969
|
+
return false;
|
|
970
|
+
}
|
|
971
|
+
await this.orm.em.remove(entity);
|
|
972
|
+
await this.orm.em.flush();
|
|
973
|
+
return true;
|
|
974
|
+
}
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
**Return type:** Boolean (true or false)
|
|
978
|
+
|
|
979
|
+
#### Prisma Implementation (prisma-driver.js:346-358)
|
|
980
|
+
|
|
981
|
+
```javascript
|
|
982
|
+
async deleteById(id)
|
|
983
|
+
{
|
|
984
|
+
let castedId = this.typeCaster.castToIdType(id);
|
|
985
|
+
let found = await this.loadById(castedId);
|
|
986
|
+
if(!found){
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
return this.executeWithNormalization(
|
|
990
|
+
() => this.model.delete({where: {[this.idFieldName]: castedId}}),
|
|
991
|
+
'Delete by ID error: ',
|
|
992
|
+
false
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
**Return type:** Record object (deleted record) OR false
|
|
998
|
+
|
|
999
|
+
**Consistency Analysis:**
|
|
1000
|
+
- ❌ **RETURN TYPE INCONSISTENT**:
|
|
1001
|
+
- ObjectionJS: Number
|
|
1002
|
+
- MikroORM: Boolean
|
|
1003
|
+
- Prisma: Record object OR false
|
|
1004
|
+
- ✅ All delete by ID
|
|
1005
|
+
- ✅ All return falsy when not found
|
|
1006
|
+
|
|
1007
|
+
---
|
|
1008
|
+
|
|
1009
|
+
### 3.9 count(filters)
|
|
1010
|
+
|
|
1011
|
+
**Purpose:** Count records matching filters
|
|
1012
|
+
|
|
1013
|
+
**Parameters:**
|
|
1014
|
+
- `filters` (object) - Filter conditions
|
|
1015
|
+
|
|
1016
|
+
**Expected Behavior:**
|
|
1017
|
+
- Count records matching filters
|
|
1018
|
+
- Return count as number
|
|
1019
|
+
|
|
1020
|
+
**Return:** Number
|
|
1021
|
+
|
|
1022
|
+
#### ObjectionJS Implementation (objection-js-driver.js:95-101)
|
|
1023
|
+
|
|
1024
|
+
```javascript
|
|
1025
|
+
async count(filters)
|
|
1026
|
+
{
|
|
1027
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
1028
|
+
this.appendFilters(queryBuilder, filters);
|
|
1029
|
+
let count = await queryBuilder.count().first();
|
|
1030
|
+
return count ? count['count(*)'] : 0;
|
|
1031
|
+
}
|
|
1032
|
+
```
|
|
1033
|
+
|
|
1034
|
+
**Return type:** Number
|
|
1035
|
+
|
|
1036
|
+
#### MikroORM Implementation (mikro-orm-driver.js:179-183)
|
|
1037
|
+
|
|
1038
|
+
```javascript
|
|
1039
|
+
async count(filters)
|
|
1040
|
+
{
|
|
1041
|
+
let processedFilters = this.processFilters(filters);
|
|
1042
|
+
return this.repository.count(processedFilters);
|
|
1043
|
+
}
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
**Return type:** Number
|
|
1047
|
+
|
|
1048
|
+
#### Prisma Implementation (prisma-driver.js:360-368)
|
|
1049
|
+
|
|
1050
|
+
```javascript
|
|
1051
|
+
async count(filters)
|
|
1052
|
+
{
|
|
1053
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
1054
|
+
return this.executeWithNormalization(
|
|
1055
|
+
() => this.model.count({where: processedFilters}),
|
|
1056
|
+
'Count error: ',
|
|
1057
|
+
0
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
**Return type:** Number
|
|
1063
|
+
|
|
1064
|
+
**Consistency Analysis:**
|
|
1065
|
+
- ✅ **ALL CONSISTENT** - Return number
|
|
1066
|
+
- ✅ All count by filters
|
|
1067
|
+
- ✅ All return 0 on error
|
|
1068
|
+
|
|
1069
|
+
---
|
|
1070
|
+
|
|
1071
|
+
### 3.10 countWithRelations(filters, relations)
|
|
1072
|
+
|
|
1073
|
+
**Purpose:** Count records with relation-based filters
|
|
1074
|
+
|
|
1075
|
+
**Parameters:**
|
|
1076
|
+
- `filters` (object) - Filter conditions
|
|
1077
|
+
- `relations` (array) - Relations to include in query
|
|
1078
|
+
|
|
1079
|
+
**Expected Behavior:**
|
|
1080
|
+
- Count records matching filters
|
|
1081
|
+
- Consider relation joins in count
|
|
1082
|
+
- Return count as number
|
|
1083
|
+
|
|
1084
|
+
**Return:** Number
|
|
1085
|
+
|
|
1086
|
+
#### ObjectionJS Implementation (objection-js-driver.js:103-109)
|
|
1087
|
+
|
|
1088
|
+
```javascript
|
|
1089
|
+
async countWithRelations(filters, relations)
|
|
1090
|
+
{
|
|
1091
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
1092
|
+
this.appendFilters(queryBuilder, filters);
|
|
1093
|
+
let count = await this.appendRelationsToQuery(queryBuilder, relations).count().first();
|
|
1094
|
+
return count ? count['count(*)'] : 0;
|
|
1095
|
+
}
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
**How it works:**
|
|
1099
|
+
- Builds query with relations joined
|
|
1100
|
+
- Counts distinct parent records
|
|
1101
|
+
|
|
1102
|
+
**Return type:** Number
|
|
1103
|
+
|
|
1104
|
+
#### MikroORM Implementation (mikro-orm-driver.js:185-222)
|
|
1105
|
+
|
|
1106
|
+
```javascript
|
|
1107
|
+
async countWithRelations(filters, relations)
|
|
1108
|
+
{
|
|
1109
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1110
|
+
relations = this.getAllRelations();
|
|
1111
|
+
}
|
|
1112
|
+
let processedFilters = this.processFilters(filters);
|
|
1113
|
+
if(0 === relations.length || !this.rawModel.meta || !this.rawModel.meta.properties){
|
|
1114
|
+
return this.repository.count(processedFilters);
|
|
1115
|
+
}
|
|
1116
|
+
let queryBuilder = this.orm.em.createQueryBuilder(this.rawModel);
|
|
1117
|
+
let properties = this.rawModel.meta.properties;
|
|
1118
|
+
let aliasCounter = 0;
|
|
1119
|
+
for(let relationName of relations){
|
|
1120
|
+
let prop = properties[relationName];
|
|
1121
|
+
if(!prop || !prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
// ... builds LEFT JOINs for relations
|
|
1125
|
+
}
|
|
1126
|
+
if(sc.isObject(processedFilters) && 0 < Object.keys(processedFilters).length){
|
|
1127
|
+
queryBuilder.where(processedFilters);
|
|
1128
|
+
}
|
|
1129
|
+
return queryBuilder.getCount();
|
|
1130
|
+
}
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
**How it works:**
|
|
1134
|
+
- Manually builds LEFT JOINs for each relation
|
|
1135
|
+
- Counts with relations joined
|
|
1136
|
+
|
|
1137
|
+
**Return type:** Number
|
|
1138
|
+
|
|
1139
|
+
#### Prisma Implementation (prisma-driver.js:370-385)
|
|
1140
|
+
|
|
1141
|
+
```javascript
|
|
1142
|
+
async countWithRelations(filters, relations)
|
|
1143
|
+
{
|
|
1144
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1145
|
+
relations = this.getAllRelations();
|
|
1146
|
+
}
|
|
1147
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
1148
|
+
let query = {where: processedFilters};
|
|
1149
|
+
if(0 < relations.length){
|
|
1150
|
+
query.include = this.relationResolver.buildIncludeObjectWithMapping(relations).include;
|
|
1151
|
+
}
|
|
1152
|
+
return this.executeWithNormalization(
|
|
1153
|
+
() => this.model.count(query),
|
|
1154
|
+
'Count with relations error: ',
|
|
1155
|
+
0
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**How it works:**
|
|
1161
|
+
- Builds include object for relations
|
|
1162
|
+
- Calls Prisma count with include
|
|
1163
|
+
|
|
1164
|
+
**Return type:** Number
|
|
1165
|
+
|
|
1166
|
+
**Consistency Analysis:**
|
|
1167
|
+
- ✅ **ALL CONSISTENT** - Return number
|
|
1168
|
+
- ✅ All count with relations joined
|
|
1169
|
+
- ⚠️ Prisma's include in count may not work correctly (Prisma doesn't filter by relations in count)
|
|
1170
|
+
|
|
1171
|
+
---
|
|
1172
|
+
|
|
1173
|
+
### 3.11 loadAll()
|
|
1174
|
+
|
|
1175
|
+
**Purpose:** Load ALL records from table (no filters)
|
|
1176
|
+
|
|
1177
|
+
**Parameters:** None
|
|
1178
|
+
|
|
1179
|
+
**Expected Behavior:**
|
|
1180
|
+
- Load all records
|
|
1181
|
+
- Respect limit/offset/sort properties
|
|
1182
|
+
- Return array of records
|
|
1183
|
+
|
|
1184
|
+
**Return:** Array of records
|
|
1185
|
+
|
|
1186
|
+
#### ObjectionJS Implementation (objection-js-driver.js:111-114)
|
|
1187
|
+
|
|
1188
|
+
```javascript
|
|
1189
|
+
loadAll()
|
|
1190
|
+
{
|
|
1191
|
+
return this.queryBuilder();
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
**Return type:** Promise<Array>
|
|
1196
|
+
|
|
1197
|
+
#### MikroORM Implementation (mikro-orm-driver.js:224-227)
|
|
1198
|
+
|
|
1199
|
+
```javascript
|
|
1200
|
+
loadAll()
|
|
1201
|
+
{
|
|
1202
|
+
return this.repository.findAll();
|
|
1203
|
+
}
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
**Return type:** Promise<Array>
|
|
1207
|
+
|
|
1208
|
+
#### Prisma Implementation (prisma-driver.js:387-394)
|
|
1209
|
+
|
|
1210
|
+
```javascript
|
|
1211
|
+
async loadAll()
|
|
1212
|
+
{
|
|
1213
|
+
return this.executeWithNormalization(
|
|
1214
|
+
() => this.model.findMany(this.queryBuilder.buildQueryOptions(this.getQueryOptions())),
|
|
1215
|
+
'Load all error: ',
|
|
1216
|
+
[]
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
**Return type:** Promise<Array>
|
|
1222
|
+
|
|
1223
|
+
**Consistency Analysis:**
|
|
1224
|
+
- ✅ **ALL CONSISTENT** - Return array
|
|
1225
|
+
- ✅ All load all records
|
|
1226
|
+
- ✅ All respect limit/offset/sort
|
|
1227
|
+
|
|
1228
|
+
---
|
|
1229
|
+
|
|
1230
|
+
### 3.12 loadAllWithRelations(relations)
|
|
1231
|
+
|
|
1232
|
+
**Purpose:** Load ALL records with relations
|
|
1233
|
+
|
|
1234
|
+
**Parameters:**
|
|
1235
|
+
- `relations` (array) - Relations to load
|
|
1236
|
+
|
|
1237
|
+
**Expected Behavior:**
|
|
1238
|
+
- Load all records
|
|
1239
|
+
- Populate specified relations
|
|
1240
|
+
- Respect limit/offset/sort
|
|
1241
|
+
- Return array with relations
|
|
1242
|
+
|
|
1243
|
+
**Return:** Array of records with relations populated
|
|
1244
|
+
|
|
1245
|
+
#### ObjectionJS Implementation (objection-js-driver.js:116-119)
|
|
1246
|
+
|
|
1247
|
+
```javascript
|
|
1248
|
+
loadAllWithRelations(relations)
|
|
1249
|
+
{
|
|
1250
|
+
return this.appendRelationsToQuery(this.queryBuilder(), relations);
|
|
1251
|
+
}
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
**Return type:** Promise<Array>
|
|
1255
|
+
|
|
1256
|
+
#### MikroORM Implementation (mikro-orm-driver.js:229-236)
|
|
1257
|
+
|
|
1258
|
+
```javascript
|
|
1259
|
+
async loadAllWithRelations(relations)
|
|
1260
|
+
{
|
|
1261
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1262
|
+
relations = this.getAllRelations();
|
|
1263
|
+
}
|
|
1264
|
+
let entities = await this.loadAll();
|
|
1265
|
+
return await this.appendRelationsToCollection(entities, relations);
|
|
1266
|
+
}
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
**Return type:** Promise<Array>
|
|
1270
|
+
|
|
1271
|
+
#### Prisma Implementation (prisma-driver.js:396-405)
|
|
1272
|
+
|
|
1273
|
+
```javascript
|
|
1274
|
+
async loadAllWithRelations(relations)
|
|
1275
|
+
{
|
|
1276
|
+
return this.executeQueryWithRelations(
|
|
1277
|
+
(q) => this.model.findMany(q),
|
|
1278
|
+
this.queryBuilder.buildQueryOptions(this.getQueryOptions()),
|
|
1279
|
+
relations,
|
|
1280
|
+
'Load all with relations error: ',
|
|
1281
|
+
[]
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
**Return type:** Promise<Array>
|
|
1287
|
+
|
|
1288
|
+
**Consistency Analysis:**
|
|
1289
|
+
- ✅ **ALL CONSISTENT** - Return array with relations
|
|
1290
|
+
- ✅ All load all records with relations
|
|
1291
|
+
- ✅ All respect limit/offset/sort
|
|
1292
|
+
|
|
1293
|
+
---
|
|
1294
|
+
|
|
1295
|
+
### 3.13 load(filters)
|
|
1296
|
+
|
|
1297
|
+
**Purpose:** Load records matching filters
|
|
1298
|
+
|
|
1299
|
+
**Parameters:**
|
|
1300
|
+
- `filters` (object) - Filter conditions
|
|
1301
|
+
|
|
1302
|
+
**Expected Behavior:**
|
|
1303
|
+
- Load records matching filters
|
|
1304
|
+
- Respect limit/offset/sort properties
|
|
1305
|
+
- Return array of records
|
|
1306
|
+
|
|
1307
|
+
**Return:** Array of records
|
|
1308
|
+
|
|
1309
|
+
#### ObjectionJS Implementation (objection-js-driver.js:121-126)
|
|
1310
|
+
|
|
1311
|
+
```javascript
|
|
1312
|
+
load(filters)
|
|
1313
|
+
{
|
|
1314
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
1315
|
+
this.appendFilters(queryBuilder, filters);
|
|
1316
|
+
return queryBuilder;
|
|
1317
|
+
}
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
**Return type:** Promise<Array>
|
|
1321
|
+
|
|
1322
|
+
#### MikroORM Implementation (mikro-orm-driver.js:238-242)
|
|
1323
|
+
|
|
1324
|
+
```javascript
|
|
1325
|
+
load(filters)
|
|
1326
|
+
{
|
|
1327
|
+
let processedFilters = this.processFilters(filters);
|
|
1328
|
+
return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
|
|
1329
|
+
}
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
**Return type:** Promise<Array>
|
|
1333
|
+
|
|
1334
|
+
#### Prisma Implementation (prisma-driver.js:407-417)
|
|
1335
|
+
|
|
1336
|
+
```javascript
|
|
1337
|
+
async load(filters)
|
|
1338
|
+
{
|
|
1339
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
1340
|
+
let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
|
|
1341
|
+
query.where = processedFilters;
|
|
1342
|
+
return this.executeWithNormalization(
|
|
1343
|
+
() => this.model.findMany(query),
|
|
1344
|
+
'Load error: ',
|
|
1345
|
+
[]
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
**Return type:** Promise<Array>
|
|
1351
|
+
|
|
1352
|
+
**Consistency Analysis:**
|
|
1353
|
+
- ✅ **ALL CONSISTENT** - Return array
|
|
1354
|
+
- ✅ All load by filters
|
|
1355
|
+
- ✅ All respect limit/offset/sort
|
|
1356
|
+
|
|
1357
|
+
---
|
|
1358
|
+
|
|
1359
|
+
### 3.14 loadWithRelations(filters, relations)
|
|
1360
|
+
|
|
1361
|
+
**Purpose:** Load records matching filters with relations
|
|
1362
|
+
|
|
1363
|
+
**Parameters:**
|
|
1364
|
+
- `filters` (object) - Filter conditions
|
|
1365
|
+
- `relations` (array) - Relations to load
|
|
1366
|
+
|
|
1367
|
+
**Expected Behavior:**
|
|
1368
|
+
- Load records matching filters
|
|
1369
|
+
- Populate specified relations
|
|
1370
|
+
- Respect limit/offset/sort
|
|
1371
|
+
- Return array with relations
|
|
1372
|
+
|
|
1373
|
+
**Return:** Array of records with relations populated
|
|
1374
|
+
|
|
1375
|
+
#### ObjectionJS Implementation (objection-js-driver.js:128-133)
|
|
1376
|
+
|
|
1377
|
+
```javascript
|
|
1378
|
+
loadWithRelations(filters, relations)
|
|
1379
|
+
{
|
|
1380
|
+
let queryBuilder = this.queryBuilder(true, true, true);
|
|
1381
|
+
this.appendFilters(queryBuilder, filters);
|
|
1382
|
+
return this.appendRelationsToQuery(queryBuilder, relations);
|
|
1383
|
+
}
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
**Return type:** Promise<Array>
|
|
1387
|
+
|
|
1388
|
+
#### MikroORM Implementation (mikro-orm-driver.js:244-252)
|
|
1389
|
+
|
|
1390
|
+
```javascript
|
|
1391
|
+
async loadWithRelations(filters, relations)
|
|
1392
|
+
{
|
|
1393
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1394
|
+
relations = this.getAllRelations();
|
|
1395
|
+
}
|
|
1396
|
+
let processedFilters = this.processFilters(filters);
|
|
1397
|
+
let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
|
|
1398
|
+
return await this.appendRelationsToCollection(entitiesCollection, relations);
|
|
1399
|
+
}
|
|
1400
|
+
```
|
|
1401
|
+
|
|
1402
|
+
**Return type:** Promise<Array>
|
|
1403
|
+
|
|
1404
|
+
#### Prisma Implementation (prisma-driver.js:419-430)
|
|
1405
|
+
|
|
1406
|
+
```javascript
|
|
1407
|
+
async loadWithRelations(filters, relations)
|
|
1408
|
+
{
|
|
1409
|
+
let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions());
|
|
1410
|
+
query.where = this.filterProcessor.processFilters(filters);
|
|
1411
|
+
return this.executeQueryWithRelations(
|
|
1412
|
+
(q) => this.model.findMany(q),
|
|
1413
|
+
query,
|
|
1414
|
+
relations,
|
|
1415
|
+
'Load with relations error: ',
|
|
1416
|
+
[]
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
**Return type:** Promise<Array>
|
|
1422
|
+
|
|
1423
|
+
**Consistency Analysis:**
|
|
1424
|
+
- ✅ **ALL CONSISTENT** - Return array with relations
|
|
1425
|
+
- ✅ All load by filters with relations
|
|
1426
|
+
- ✅ All respect limit/offset/sort
|
|
1427
|
+
|
|
1428
|
+
---
|
|
1429
|
+
|
|
1430
|
+
### 3.15 loadById(id)
|
|
1431
|
+
|
|
1432
|
+
**Purpose:** Load single record by ID
|
|
1433
|
+
|
|
1434
|
+
**Parameters:**
|
|
1435
|
+
- `id` - Record ID
|
|
1436
|
+
|
|
1437
|
+
**Expected Behavior:**
|
|
1438
|
+
- Find record by ID
|
|
1439
|
+
- Return record or null
|
|
1440
|
+
|
|
1441
|
+
**Return:** Record object or null
|
|
1442
|
+
|
|
1443
|
+
#### ObjectionJS Implementation (objection-js-driver.js:149-152)
|
|
1444
|
+
|
|
1445
|
+
```javascript
|
|
1446
|
+
loadById(id)
|
|
1447
|
+
{
|
|
1448
|
+
return this.queryBuilder().findById(id);
|
|
1449
|
+
}
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
**Return type:** Promise<Record | null>
|
|
1453
|
+
|
|
1454
|
+
#### MikroORM Implementation (mikro-orm-driver.js:272-275)
|
|
1455
|
+
|
|
1456
|
+
```javascript
|
|
1457
|
+
loadById(id)
|
|
1458
|
+
{
|
|
1459
|
+
return this.loadOneBy('id', id);
|
|
1460
|
+
}
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
**Return type:** Promise<Record | null>
|
|
1464
|
+
|
|
1465
|
+
#### Prisma Implementation (prisma-driver.js:457-465)
|
|
1466
|
+
|
|
1467
|
+
```javascript
|
|
1468
|
+
async loadById(id)
|
|
1469
|
+
{
|
|
1470
|
+
let castedId = this.typeCaster.castToIdType(id);
|
|
1471
|
+
return this.executeWithNormalization(
|
|
1472
|
+
() => this.model.findUnique({where: {[this.idFieldName]: castedId}}),
|
|
1473
|
+
'Load by ID error: ',
|
|
1474
|
+
null
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
```
|
|
1478
|
+
|
|
1479
|
+
**Return type:** Promise<Record | null>
|
|
1480
|
+
|
|
1481
|
+
**Consistency Analysis:**
|
|
1482
|
+
- ✅ **ALL CONSISTENT** - Return record or null
|
|
1483
|
+
- ✅ All load by ID
|
|
1484
|
+
- ✅ All return null when not found
|
|
1485
|
+
|
|
1486
|
+
---
|
|
1487
|
+
|
|
1488
|
+
### 3.16 loadByIdWithRelations(id, relations)
|
|
1489
|
+
|
|
1490
|
+
**Purpose:** Load single record by ID with relations
|
|
1491
|
+
|
|
1492
|
+
**Parameters:**
|
|
1493
|
+
- `id` - Record ID
|
|
1494
|
+
- `relations` (array) - Relations to load
|
|
1495
|
+
|
|
1496
|
+
**Expected Behavior:**
|
|
1497
|
+
- Find record by ID
|
|
1498
|
+
- Populate specified relations
|
|
1499
|
+
- Return record with relations or null
|
|
1500
|
+
|
|
1501
|
+
**Return:** Record object with relations or null
|
|
1502
|
+
|
|
1503
|
+
#### ObjectionJS Implementation (objection-js-driver.js:154-157)
|
|
1504
|
+
|
|
1505
|
+
```javascript
|
|
1506
|
+
loadByIdWithRelations(id, relations)
|
|
1507
|
+
{
|
|
1508
|
+
return this.appendRelationsToQuery(this.queryBuilder().findById(id), relations);
|
|
1509
|
+
}
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
**Return type:** Promise<Record | null>
|
|
1513
|
+
|
|
1514
|
+
#### MikroORM Implementation (mikro-orm-driver.js:277-284)
|
|
1515
|
+
|
|
1516
|
+
```javascript
|
|
1517
|
+
async loadByIdWithRelations(id, relations)
|
|
1518
|
+
{
|
|
1519
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1520
|
+
relations = this.getAllRelations();
|
|
1521
|
+
}
|
|
1522
|
+
let entity = await this.loadOneBy('id', id);
|
|
1523
|
+
return await this.appendRelationsToCollection(entity, relations);
|
|
1524
|
+
}
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
**Return type:** Promise<Record | null>
|
|
1528
|
+
|
|
1529
|
+
#### Prisma Implementation (prisma-driver.js:467-476)
|
|
1530
|
+
|
|
1531
|
+
```javascript
|
|
1532
|
+
async loadByIdWithRelations(id, relations)
|
|
1533
|
+
{
|
|
1534
|
+
return this.executeQueryWithRelations(
|
|
1535
|
+
(q) => this.model.findUnique(q),
|
|
1536
|
+
{where: {[this.idFieldName]: this.typeCaster.castToIdType(id)}},
|
|
1537
|
+
relations,
|
|
1538
|
+
'Load by ID with relations error: ',
|
|
1539
|
+
null
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
**Return type:** Promise<Record | null>
|
|
1545
|
+
|
|
1546
|
+
**Consistency Analysis:**
|
|
1547
|
+
- ✅ **ALL CONSISTENT** - Return record with relations or null
|
|
1548
|
+
- ✅ All load by ID with relations
|
|
1549
|
+
- ✅ All return null when not found
|
|
1550
|
+
|
|
1551
|
+
---
|
|
1552
|
+
|
|
1553
|
+
### 3.17 loadOne(filters)
|
|
1554
|
+
|
|
1555
|
+
**Purpose:** Load first record matching filters
|
|
1556
|
+
|
|
1557
|
+
**Parameters:**
|
|
1558
|
+
- `filters` (object) - Filter conditions
|
|
1559
|
+
|
|
1560
|
+
**Expected Behavior:**
|
|
1561
|
+
- Find first record matching filters
|
|
1562
|
+
- Respect offset/sort (not limit)
|
|
1563
|
+
- Return single record or null
|
|
1564
|
+
|
|
1565
|
+
**Return:** Record object or null
|
|
1566
|
+
|
|
1567
|
+
#### ObjectionJS Implementation (objection-js-driver.js:164-169)
|
|
1568
|
+
|
|
1569
|
+
```javascript
|
|
1570
|
+
loadOne(filters)
|
|
1571
|
+
{
|
|
1572
|
+
let queryBuilder = this.queryBuilder(false, true, true);
|
|
1573
|
+
this.appendFilters(queryBuilder, filters);
|
|
1574
|
+
return queryBuilder.limit(1).first();
|
|
1575
|
+
}
|
|
1576
|
+
```
|
|
1577
|
+
|
|
1578
|
+
**Note:** Uses `useLimit = false` but then manually adds `.limit(1)`
|
|
1579
|
+
|
|
1580
|
+
**Return type:** Promise<Record | null>
|
|
1581
|
+
|
|
1582
|
+
#### MikroORM Implementation (mikro-orm-driver.js:292-296)
|
|
1583
|
+
|
|
1584
|
+
```javascript
|
|
1585
|
+
loadOne(filters)
|
|
1586
|
+
{
|
|
1587
|
+
let processedFilters = this.processFilters(filters);
|
|
1588
|
+
return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
|
|
1589
|
+
}
|
|
1590
|
+
```
|
|
1591
|
+
|
|
1592
|
+
**Return type:** Promise<Record | null>
|
|
1593
|
+
|
|
1594
|
+
#### Prisma Implementation (prisma-driver.js:491-501)
|
|
1595
|
+
|
|
1596
|
+
```javascript
|
|
1597
|
+
async loadOne(filters)
|
|
1598
|
+
{
|
|
1599
|
+
let processedFilters = this.filterProcessor.processFilters(filters);
|
|
1600
|
+
let query = this.queryBuilder.buildQueryOptions(this.getQueryOptions(), false);
|
|
1601
|
+
query.where = processedFilters;
|
|
1602
|
+
return this.executeWithNormalization(
|
|
1603
|
+
() => this.model.findFirst(query),
|
|
1604
|
+
'Load one error: ',
|
|
1605
|
+
null
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
```
|
|
1609
|
+
|
|
1610
|
+
**Return type:** Promise<Record | null>
|
|
1611
|
+
|
|
1612
|
+
**Consistency Analysis:**
|
|
1613
|
+
- ✅ **ALL CONSISTENT** - Return single record or null
|
|
1614
|
+
- ✅ All load first match
|
|
1615
|
+
- ✅ All respect offset/sort
|
|
1616
|
+
|
|
1617
|
+
---
|
|
1618
|
+
|
|
1619
|
+
### 3.18 rawQuery(content)
|
|
1620
|
+
|
|
1621
|
+
**Purpose:** Execute raw SQL query
|
|
1622
|
+
|
|
1623
|
+
**Parameters:**
|
|
1624
|
+
- `content` (string) - SQL query string
|
|
1625
|
+
|
|
1626
|
+
**Expected Behavior:**
|
|
1627
|
+
- Execute raw SQL
|
|
1628
|
+
- Return results array or false
|
|
1629
|
+
|
|
1630
|
+
**Return:** Array of results or false
|
|
1631
|
+
|
|
1632
|
+
**Note:** This is implemented in DataServer classes, not Driver classes
|
|
1633
|
+
|
|
1634
|
+
#### ObjectionJS Implementation (objection-js-data-server.js:83-87)
|
|
1635
|
+
|
|
1636
|
+
```javascript
|
|
1637
|
+
async rawQuery(content)
|
|
1638
|
+
{
|
|
1639
|
+
let result = await this.knex.raw(content);
|
|
1640
|
+
return result[0] || false;
|
|
1641
|
+
}
|
|
1642
|
+
```
|
|
1643
|
+
|
|
1644
|
+
**Uses:** Knex.raw()
|
|
1645
|
+
|
|
1646
|
+
**Return type:** Array or false
|
|
1647
|
+
|
|
1648
|
+
#### MikroORM Implementation (mikro-orm-data-server.js:97-108)
|
|
1649
|
+
|
|
1650
|
+
```javascript
|
|
1651
|
+
async rawQuery(content)
|
|
1652
|
+
{
|
|
1653
|
+
try {
|
|
1654
|
+
let queryResult = await this.orm.em.execute(content);
|
|
1655
|
+
if(!sc.isArray(queryResult) || 0 === queryResult.length){
|
|
1656
|
+
return false;
|
|
1657
|
+
}
|
|
1658
|
+
return queryResult;
|
|
1659
|
+
} catch(error){
|
|
1660
|
+
Logger.error('MikroORM raw query error:', error.message);
|
|
1661
|
+
return false;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
```
|
|
1665
|
+
|
|
1666
|
+
**Uses:** EntityManager.execute()
|
|
1667
|
+
|
|
1668
|
+
**Return type:** Array or false
|
|
1669
|
+
|
|
1670
|
+
#### Prisma Implementation (prisma-data-server.js:118-134)
|
|
1671
|
+
|
|
1672
|
+
```javascript
|
|
1673
|
+
async rawQuery(content)
|
|
1674
|
+
{
|
|
1675
|
+
try {
|
|
1676
|
+
let statements = this.splitSqlStatements(content);
|
|
1677
|
+
let results = [];
|
|
1678
|
+
for(let statement of statements){
|
|
1679
|
+
if(!statement.trim()){
|
|
1680
|
+
continue;
|
|
1681
|
+
}
|
|
1682
|
+
let result = await this.prisma.$executeRawUnsafe(statement);
|
|
1683
|
+
results.push(result);
|
|
1684
|
+
}
|
|
1685
|
+
return results;
|
|
1686
|
+
} catch(error){
|
|
1687
|
+
Logger.error('Prisma raw query error:', error.message);
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
**Uses:** Prisma.$executeRawUnsafe()
|
|
1694
|
+
**Special:** Splits statements and executes separately
|
|
1695
|
+
|
|
1696
|
+
**Return type:** Array or false
|
|
1697
|
+
|
|
1698
|
+
**Consistency Analysis:**
|
|
1699
|
+
- ✅ **ALL CONSISTENT** - Return array or false
|
|
1700
|
+
- ✅ All execute raw SQL
|
|
1701
|
+
- ⚠️ Prisma splits multi-statement queries
|
|
1702
|
+
|
|
1703
|
+
---
|
|
1704
|
+
|
|
1705
|
+
## 4. FILTER OPERATORS ANALYSIS
|
|
1706
|
+
|
|
1707
|
+
### 4.1 Supported Operators
|
|
1708
|
+
|
|
1709
|
+
**BaseDriver defines these operators** (base-driver.js:24-31):
|
|
1710
|
+
```javascript
|
|
1711
|
+
this.operatorsMap = {
|
|
1712
|
+
'GT': '>',
|
|
1713
|
+
'GTE': '>=',
|
|
1714
|
+
'LT': '<',
|
|
1715
|
+
'LTE': '<=',
|
|
1716
|
+
'NE': '!=',
|
|
1717
|
+
'EQ': '='
|
|
1718
|
+
};
|
|
1719
|
+
```
|
|
1720
|
+
|
|
1721
|
+
**Additional operators in drivers:**
|
|
1722
|
+
- `OR` - OR condition
|
|
1723
|
+
- `IN` - IN array
|
|
1724
|
+
- `NOT` - NOT equal
|
|
1725
|
+
- `LIKE` - Pattern matching
|
|
1726
|
+
- `AND` - AND conditions
|
|
1727
|
+
|
|
1728
|
+
### 4.2 Filter Syntax
|
|
1729
|
+
|
|
1730
|
+
**Basic filter:**
|
|
1731
|
+
```javascript
|
|
1732
|
+
{field: value} // WHERE field = value
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
**Operator filter:**
|
|
1736
|
+
```javascript
|
|
1737
|
+
{field: {operator: 'GT', value: 10}} // WHERE field > 10
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
**OR filter:**
|
|
1741
|
+
```javascript
|
|
1742
|
+
{OR: [{field1: value1}, {field2: value2}]} // WHERE field1 = value1 OR field2 = value2
|
|
1743
|
+
```
|
|
1744
|
+
|
|
1745
|
+
**AND filter:**
|
|
1746
|
+
```javascript
|
|
1747
|
+
{AND: [{field1: value1}, {field2: value2}]} // WHERE field1 = value1 AND field2 = value2
|
|
1748
|
+
```
|
|
1749
|
+
|
|
1750
|
+
**IN filter:**
|
|
1751
|
+
```javascript
|
|
1752
|
+
{field: {operator: 'IN', value: [1, 2, 3]}} // WHERE field IN (1, 2, 3)
|
|
1753
|
+
```
|
|
1754
|
+
|
|
1755
|
+
**LIKE filter:**
|
|
1756
|
+
```javascript
|
|
1757
|
+
{field: {operator: 'LIKE', value: 'pattern'}} // WHERE field LIKE '%pattern%'
|
|
1758
|
+
```
|
|
1759
|
+
|
|
1760
|
+
### 4.3 ObjectionJS Filter Processing
|
|
1761
|
+
|
|
1762
|
+
**Implementation:** `appendFilters()` method (objection-js-driver.js:274-330)
|
|
1763
|
+
|
|
1764
|
+
**Key behaviors:**
|
|
1765
|
+
- Converts operator strings to UPPERCASE
|
|
1766
|
+
- Maps operators: `GT` → `>`, `GTE` → `>=`, etc.
|
|
1767
|
+
- Special handling for:
|
|
1768
|
+
- `OR` operator → `.orWhere()`
|
|
1769
|
+
- `IN` operator → `.whereIn()`
|
|
1770
|
+
- `NOT` operator → `.whereNot()`
|
|
1771
|
+
- `LIKE` operator → `.where(field, 'like', '%value%')`
|
|
1772
|
+
- JSON fields with LIKE → `.where(ref(field).castText(), 'like', '%value%')`
|
|
1773
|
+
- Nested AND/OR with proper SQL grouping
|
|
1774
|
+
- Relation field flattening (converts `{related_table: {field: value}}` to `{related_table.field: value}`)
|
|
1775
|
+
|
|
1776
|
+
**Case sensitivity:** Operator strings converted to UPPERCASE before processing
|
|
1777
|
+
|
|
1778
|
+
### 4.4 MikroORM Filter Processing
|
|
1779
|
+
|
|
1780
|
+
**Implementation:** `processFilters()` method (mikro-orm-driver.js:326-353)
|
|
1781
|
+
|
|
1782
|
+
**Operator mapping** (mikro-orm-driver.js:32-41):
|
|
1783
|
+
```javascript
|
|
1784
|
+
this.mikroOrmOperators = {
|
|
1785
|
+
'GT': '$gt',
|
|
1786
|
+
'GTE': '$gte',
|
|
1787
|
+
'LT': '$lt',
|
|
1788
|
+
'LTE': '$lte',
|
|
1789
|
+
'NE': '$ne',
|
|
1790
|
+
'NOT': '$ne',
|
|
1791
|
+
'IN': '$in',
|
|
1792
|
+
'NOT IN': '$nin'
|
|
1793
|
+
};
|
|
1794
|
+
```
|
|
1795
|
+
|
|
1796
|
+
**Key behaviors:**
|
|
1797
|
+
- Converts operator strings to UPPERCASE
|
|
1798
|
+
- Maps to MikroORM operators: `GT` → `$gt`, `IN` → `$in`, etc.
|
|
1799
|
+
- Special handling for:
|
|
1800
|
+
- `AND` → `$and` array
|
|
1801
|
+
- `OR` → `$or` array
|
|
1802
|
+
- `LIKE` → `$like` with `%value%` wrapping
|
|
1803
|
+
- JSON fields with LIKE → `$like` with `%value%`
|
|
1804
|
+
- Recursive processing for nested filters
|
|
1805
|
+
|
|
1806
|
+
**Case sensitivity:** Operator strings converted to UPPERCASE before processing
|
|
1807
|
+
|
|
1808
|
+
### 4.5 Prisma Filter Processing
|
|
1809
|
+
|
|
1810
|
+
**Implementation:** `PrismaFilterProcessor` class (prisma-filter-processor.js)
|
|
1811
|
+
|
|
1812
|
+
**Key behaviors:**
|
|
1813
|
+
- Converts filters to Prisma where syntax
|
|
1814
|
+
- Maps operators to Prisma syntax: `GT` → `{gt: value}`, `IN` → `{in: array}`
|
|
1815
|
+
- Special handling for:
|
|
1816
|
+
- `AND` → `AND` array
|
|
1817
|
+
- `OR` → `OR` array
|
|
1818
|
+
- `LIKE` → `{contains: value}` (case-sensitive) or `{mode: 'insensitive'}`
|
|
1819
|
+
- JSON fields → special JSON filtering
|
|
1820
|
+
- Recursive processing for nested filters
|
|
1821
|
+
|
|
1822
|
+
**Case sensitivity:** Depends on database collation and mode setting
|
|
1823
|
+
|
|
1824
|
+
### 4.6 Filter Consistency Analysis
|
|
1825
|
+
|
|
1826
|
+
**Operator support:**
|
|
1827
|
+
- ✅ **GT, GTE, LT, LTE, NE, EQ** - All drivers support
|
|
1828
|
+
- ✅ **IN, NOT** - All drivers support
|
|
1829
|
+
- ✅ **OR, AND** - All drivers support nested conditions
|
|
1830
|
+
- ✅ **LIKE** - All drivers support with `%pattern%` wrapping
|
|
1831
|
+
|
|
1832
|
+
**Differences:**
|
|
1833
|
+
- ⚠️ **Case handling:** All convert operators to UPPERCASE (consistent)
|
|
1834
|
+
- ⚠️ **LIKE on JSON:** ObjectionJS uses castText(), others use string comparison
|
|
1835
|
+
- ⚠️ **Relation filters:** ObjectionJS flattens, others handle differently
|
|
1836
|
+
|
|
1837
|
+
---
|
|
1838
|
+
|
|
1839
|
+
## 5. RELATION LOADING MECHANISMS
|
|
1840
|
+
|
|
1841
|
+
### 5.1 ObjectionJS Relations
|
|
1842
|
+
|
|
1843
|
+
**Method:** `appendRelationsToQuery()` (objection-js-driver.js:332-342)
|
|
1844
|
+
|
|
1845
|
+
**Implementation:**
|
|
1846
|
+
```javascript
|
|
1847
|
+
appendRelationsToQuery(queryBuilder, relations, relationsModifiers)
|
|
1848
|
+
{
|
|
1849
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1850
|
+
relations = Object.keys(this.rawModel.relationMappings || {});
|
|
1851
|
+
}
|
|
1852
|
+
if(0 < relations.length){
|
|
1853
|
+
queryBuilder.withGraphFetched('['+relations.join(',')+']');
|
|
1854
|
+
}
|
|
1855
|
+
this.appendRelationsModifiers(queryBuilder, relations, relationsModifiers);
|
|
1856
|
+
return queryBuilder;
|
|
1857
|
+
}
|
|
1858
|
+
```
|
|
1859
|
+
|
|
1860
|
+
**Key features:**
|
|
1861
|
+
- Uses Objection's `withGraphFetched()` method
|
|
1862
|
+
- Supports nested relations: `'related_products.related_reviews'`
|
|
1863
|
+
- Supports relation modifiers (orderBy, limit)
|
|
1864
|
+
- Single query with JOINs
|
|
1865
|
+
- Eager loading
|
|
1866
|
+
|
|
1867
|
+
**Relation format:** Comma-separated string in brackets: `'[related_products,related_reviews]'`
|
|
1868
|
+
|
|
1869
|
+
### 5.2 MikroORM Relations
|
|
1870
|
+
|
|
1871
|
+
**Method:** `appendRelationsToCollection()` (mikro-orm-driver.js:423-439)
|
|
1872
|
+
|
|
1873
|
+
**Implementation:**
|
|
1874
|
+
```javascript
|
|
1875
|
+
async appendRelationsToCollection(entitiesCollection, relations)
|
|
1876
|
+
{
|
|
1877
|
+
if(!relations || 0 === relations.length){
|
|
1878
|
+
return entitiesCollection;
|
|
1879
|
+
}
|
|
1880
|
+
if(!entitiesCollection){
|
|
1881
|
+
return entitiesCollection;
|
|
1882
|
+
}
|
|
1883
|
+
await this.orm.em.populate(entitiesCollection, relations);
|
|
1884
|
+
if(sc.isArray(entitiesCollection)){
|
|
1885
|
+
for(let entity of entitiesCollection){
|
|
1886
|
+
this.convertCollectionsToArrays(entity);
|
|
1887
|
+
}
|
|
1888
|
+
return entitiesCollection;
|
|
1889
|
+
}
|
|
1890
|
+
return this.convertCollectionsToArrays(entitiesCollection);
|
|
1891
|
+
}
|
|
1892
|
+
```
|
|
1893
|
+
|
|
1894
|
+
**Key features:**
|
|
1895
|
+
- Uses MikroORM's `em.populate()` method
|
|
1896
|
+
- Loads relations AFTER loading main entities (separate queries)
|
|
1897
|
+
- Converts MikroORM Collections to plain arrays
|
|
1898
|
+
- Lazy loading approach
|
|
1899
|
+
- Supports nested relations
|
|
1900
|
+
|
|
1901
|
+
**Relation format:** Array of strings: `['related_products', 'related_reviews']`
|
|
1902
|
+
|
|
1903
|
+
### 5.3 Prisma Relations
|
|
1904
|
+
|
|
1905
|
+
**Method:** `executeQueryWithRelations()` (prisma-driver.js:541-559)
|
|
1906
|
+
|
|
1907
|
+
**Implementation:**
|
|
1908
|
+
```javascript
|
|
1909
|
+
async executeQueryWithRelations(modelMethod, query, relations, errorMessage, defaultReturn)
|
|
1910
|
+
{
|
|
1911
|
+
if(!sc.isArray(relations) || 0 === relations.length){
|
|
1912
|
+
relations = this.getAllRelations();
|
|
1913
|
+
}
|
|
1914
|
+
let includeData = this.relationResolver.buildIncludeObjectWithMapping(relations);
|
|
1915
|
+
if(0 < Object.keys(includeData.include).length){
|
|
1916
|
+
query.include = includeData.include;
|
|
1917
|
+
}
|
|
1918
|
+
return this.executeWithNormalization(
|
|
1919
|
+
async () => this.relationResolver.transformRelationResults(
|
|
1920
|
+
await modelMethod(query),
|
|
1921
|
+
includeData.include,
|
|
1922
|
+
includeData.mapping
|
|
1923
|
+
),
|
|
1924
|
+
errorMessage,
|
|
1925
|
+
defaultReturn
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
```
|
|
1929
|
+
|
|
1930
|
+
**buildIncludeObjectWithMapping()** (prisma-relation-resolver.js:156-164):
|
|
1931
|
+
```javascript
|
|
1932
|
+
buildIncludeObjectWithMapping(relations)
|
|
1933
|
+
{
|
|
1934
|
+
let include = {};
|
|
1935
|
+
let mapping = {};
|
|
1936
|
+
for(let relation of relations){
|
|
1937
|
+
this.processRelationPath(relation, include, mapping);
|
|
1938
|
+
}
|
|
1939
|
+
return {include, mapping};
|
|
1940
|
+
}
|
|
1941
|
+
```
|
|
1942
|
+
|
|
1943
|
+
**Key features:**
|
|
1944
|
+
- Uses Prisma's `include` syntax
|
|
1945
|
+
- Maps relation names (handles Prisma vs Reldens naming)
|
|
1946
|
+
- Supports nested relations: `'related_products.related_reviews'` → `{related_products: {include: {related_reviews: true}}}`
|
|
1947
|
+
- Eager loading with single query
|
|
1948
|
+
- Transforms results to match Reldens naming
|
|
1949
|
+
|
|
1950
|
+
**Relation format:** Array of strings with dot notation: `['related_products.related_reviews']`
|
|
1951
|
+
|
|
1952
|
+
### 5.4 Relation Consistency Analysis
|
|
1953
|
+
|
|
1954
|
+
**Loading strategy:**
|
|
1955
|
+
- ObjectionJS: Eager (single query with JOINs)
|
|
1956
|
+
- MikroORM: Lazy (separate queries via populate)
|
|
1957
|
+
- Prisma: Eager (single query with include)
|
|
1958
|
+
|
|
1959
|
+
**Nested relations:**
|
|
1960
|
+
- ✅ All support nested relations
|
|
1961
|
+
- ✅ All use dot notation: `'parent.child.grandchild'`
|
|
1962
|
+
|
|
1963
|
+
**Result format:**
|
|
1964
|
+
- ✅ All return plain objects with relation properties
|
|
1965
|
+
- ✅ All use same relation key names (e.g., `related_products`)
|
|
1966
|
+
|
|
1967
|
+
**Differences:**
|
|
1968
|
+
- ⚠️ Performance: ObjectionJS/Prisma faster (fewer queries)
|
|
1969
|
+
- ⚠️ MikroORM converts Collections to arrays (extra processing)
|
|
1970
|
+
- ⚠️ Prisma requires name mapping (transformRelationResults)
|
|
1971
|
+
|
|
1972
|
+
---
|
|
1973
|
+
|
|
1974
|
+
**Documentation Complete**
|
|
1975
|
+
**Date:** 2026-01-28
|
|
1976
|
+
**Total Public Methods Documented:** 35+
|
|
1977
|
+
**Purpose:** Technical reference for driver abstraction layer and method implementations
|
|
1978
|
+
|