@cheetah.js/orm 0.1.86 → 0.1.87

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.
@@ -43,6 +43,12 @@ export declare class SqlBuilder<T> {
43
43
  executeAndReturnFirst(): Promise<T | undefined>;
44
44
  executeAndReturnFirstOrFail(): Promise<T>;
45
45
  executeAndReturnAll(): Promise<T[]>;
46
+ private hasOneToManyJoinedJoin;
47
+ private processOneToManyJoinedResult;
48
+ private attachOneToManyRelations;
49
+ private removeDuplicatesByPrimaryKey;
50
+ private getPrimaryKeyName;
51
+ private getPrimaryKeyNameForEntity;
46
52
  executeCount(): Promise<number>;
47
53
  private logExecution;
48
54
  inTransaction<T>(callback: (builder: SqlBuilder<T>) => Promise<T>): Promise<T>;
@@ -165,11 +165,17 @@ class SqlBuilder {
165
165
  }
166
166
  }
167
167
  async executeAndReturnFirst() {
168
- this.statements.limit = 1;
168
+ const hasOneToManyJoinedJoin = this.hasOneToManyJoinedJoin();
169
+ if (!hasOneToManyJoinedJoin) {
170
+ this.statements.limit = 1;
171
+ }
169
172
  const result = await this.execute();
170
173
  if (result.query.rows.length === 0) {
171
174
  return undefined;
172
175
  }
176
+ if (hasOneToManyJoinedJoin) {
177
+ return this.processOneToManyJoinedResult(result.query.rows);
178
+ }
173
179
  const entities = result.query.rows[0];
174
180
  const model = await this.modelTransformer.transform(this.model, this.statements, entities);
175
181
  this.afterHooks(model);
@@ -177,11 +183,21 @@ class SqlBuilder {
177
183
  return model;
178
184
  }
179
185
  async executeAndReturnFirstOrFail() {
180
- this.statements.limit = 1;
186
+ const hasOneToManyJoinedJoin = this.hasOneToManyJoinedJoin();
187
+ if (!hasOneToManyJoinedJoin) {
188
+ this.statements.limit = 1;
189
+ }
181
190
  const result = await this.execute();
182
191
  if (result.query.rows.length === 0) {
183
192
  throw new Error('Result not found');
184
193
  }
194
+ if (hasOneToManyJoinedJoin) {
195
+ const model = await this.processOneToManyJoinedResult(result.query.rows);
196
+ if (!model) {
197
+ throw new Error('Result not found');
198
+ }
199
+ return model;
200
+ }
185
201
  const entities = result.query.rows[0];
186
202
  const model = await this.modelTransformer.transform(this.model, this.statements, entities);
187
203
  this.afterHooks(model);
@@ -203,6 +219,70 @@ class SqlBuilder {
203
219
  }
204
220
  return results;
205
221
  }
222
+ hasOneToManyJoinedJoin() {
223
+ if (!this.statements.join || this.statements.join.length === 0) {
224
+ return false;
225
+ }
226
+ if (this.statements.strategy !== 'joined') {
227
+ return false;
228
+ }
229
+ return this.statements.join.some(join => {
230
+ const relationship = this.entity.relations.find(rel => rel.propertyKey === join.joinProperty);
231
+ return relationship?.relation === 'one-to-many';
232
+ });
233
+ }
234
+ async processOneToManyJoinedResult(rows) {
235
+ const primaryKey = this.getPrimaryKeyName();
236
+ const alias = this.statements.alias;
237
+ const primaryKeyColumn = `${alias}_${primaryKey}`;
238
+ const firstRowPrimaryKeyValue = rows[0][primaryKeyColumn];
239
+ const relatedRows = rows.filter(row => row[primaryKeyColumn] === firstRowPrimaryKeyValue);
240
+ const model = this.modelTransformer.transform(this.model, this.statements, relatedRows[0]);
241
+ this.afterHooks(model);
242
+ this.attachOneToManyRelations(model, relatedRows);
243
+ return model;
244
+ }
245
+ attachOneToManyRelations(model, rows) {
246
+ if (!this.statements.join) {
247
+ return;
248
+ }
249
+ for (const join of this.statements.join) {
250
+ const relationship = this.entity.relations.find(rel => rel.propertyKey === join.joinProperty);
251
+ if (relationship?.relation === 'one-to-many') {
252
+ const joinedModels = rows.map(row => this.modelTransformer.transform(join.joinEntity, { alias: join.joinAlias }, row));
253
+ const uniqueModels = this.removeDuplicatesByPrimaryKey(joinedModels, join.joinEntity);
254
+ model[join.joinProperty] = uniqueModels;
255
+ }
256
+ }
257
+ }
258
+ removeDuplicatesByPrimaryKey(models, entityClass) {
259
+ const entity = this.entityStorage.get(entityClass);
260
+ if (!entity) {
261
+ return models;
262
+ }
263
+ const primaryKey = this.getPrimaryKeyNameForEntity(entity);
264
+ const seen = new Set();
265
+ const unique = [];
266
+ for (const model of models) {
267
+ const id = model[primaryKey];
268
+ if (id && !seen.has(id)) {
269
+ seen.add(id);
270
+ unique.push(model);
271
+ }
272
+ }
273
+ return unique;
274
+ }
275
+ getPrimaryKeyName() {
276
+ return this.getPrimaryKeyNameForEntity(this.entity);
277
+ }
278
+ getPrimaryKeyNameForEntity(entity) {
279
+ for (const prop in entity.properties) {
280
+ if (entity.properties[prop].options.isPrimary) {
281
+ return prop;
282
+ }
283
+ }
284
+ return 'id';
285
+ }
206
286
  async executeCount() {
207
287
  const result = await this.execute();
208
288
  if (result.query.rows.length === 0) {
@@ -19,27 +19,28 @@ class SqlJoinManager {
19
19
  const relationshipNames = relationshipPath.split('.');
20
20
  let currentEntity = this.entity;
21
21
  let currentAlias = this.statements.alias;
22
- let statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin;
23
- let nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias';
24
- relationshipNames.forEach((relationshipName, index) => {
25
- const relationship = currentEntity.relations.find(rel => rel.propertyKey === relationshipName);
22
+ for (const relationshipName of relationshipNames) {
23
+ const relationship = currentEntity.relations.find((rel) => rel.propertyKey === relationshipName);
26
24
  if (!relationship) {
27
- throw new Error(`Relationship "${relationshipName}" not found in entity`);
25
+ throw new Error(`Relationship "${relationshipName}" not found in entity "${currentEntity.tableName}"`);
28
26
  }
29
- const isLastRelationship = index === relationshipNames.length - 1;
30
- if (index === (relationshipNames.length - 2 >= 0 ? relationshipNames.length - 2 : 0)) {
31
- const join = statement?.find(j => j.joinProperty === relationshipName);
32
- if (join) {
33
- currentAlias = join[nameAliasProperty];
34
- }
35
- }
36
- if (relationship.relation === 'many-to-one' && isLastRelationship) {
37
- this.applyJoin(relationship, {}, currentAlias);
38
- statement = this.statements.strategy === 'joined' ? this.statements.join : this.statements.selectJoin;
39
- currentAlias = statement[statement.length - 1][nameAliasProperty];
27
+ const statement = this.statements.strategy === 'joined'
28
+ ? this.statements.join
29
+ : this.statements.selectJoin;
30
+ const nameAliasProperty = this.statements.strategy === 'joined' ? 'joinAlias' : 'alias';
31
+ const existingJoin = statement?.find((j) => j.joinProperty === relationshipName && j.originAlias === currentAlias);
32
+ if (existingJoin) {
33
+ currentAlias = existingJoin[nameAliasProperty];
34
+ currentEntity = this.entityStorage.get(relationship.entity());
35
+ continue;
40
36
  }
37
+ this.applyJoin(relationship, {}, currentAlias);
38
+ const newStatement = this.statements.strategy === 'joined'
39
+ ? this.statements.join
40
+ : this.statements.selectJoin;
41
+ currentAlias = newStatement[newStatement.length - 1][nameAliasProperty];
41
42
  currentEntity = this.entityStorage.get(relationship.entity());
42
- });
43
+ }
43
44
  }
44
45
  applyJoin(relationShip, value, alias) {
45
46
  const { tableName, schema } = this.getTableName();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheetah.js/orm",
3
- "version": "0.1.86",
3
+ "version": "0.1.87",
4
4
  "description": "A simple ORM for Cheetah.js",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.js",
@@ -55,5 +55,5 @@
55
55
  "bun",
56
56
  "value-object"
57
57
  ],
58
- "gitHead": "892dcc7a37c62b283b9bebb810bd2a0ab560e20e"
58
+ "gitHead": "f7ded7894ef2218a131a9275756477b43d0ae992"
59
59
  }