diff --git a/packages/core/database/src/__tests__/target-key.test.ts b/packages/core/database/src/__tests__/target-key.test.ts new file mode 100644 index 0000000000..4c315ef003 --- /dev/null +++ b/packages/core/database/src/__tests__/target-key.test.ts @@ -0,0 +1,232 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Database } from '../database'; +import { mockDatabase } from './index'; + +describe('targetKey', () => { + let db: Database; + + beforeEach(async () => { + db = mockDatabase(); + await db.clean({ drop: true }); + }); + + afterEach(async () => { + await db.close(); + }); + + test('default targetKey', async () => { + db.collection({ + name: 'a1', + fields: [ + { + type: 'hasMany', + name: 'b1', + target: 'b1', + }, + ], + }); + db.collection({ + name: 'b1', + fields: [], + }); + await db.sync(); + const r1 = db.getRepository('a1'); + const r2 = db.getRepository('b1'); + const b1 = await r2.create({ + values: {}, + }); + await r1.create({ + values: { + name: 'a1', + b1: [b1.toJSON()], + }, + }); + const b1r = await b1.reload(); + expect(b1r.a1Id).toBe(b1.id); + }); + + test('targetKey=code', async () => { + db.collection({ + name: 'a1', + fields: [ + { + type: 'hasMany', + name: 'b1', + target: 'b1', + targetKey: 'code', + }, + ], + }); + db.collection({ + name: 'b1', + fields: [ + { + type: 'string', + name: 'code', + }, + ], + }); + await db.sync(); + const r1 = db.getRepository('a1'); + const r2 = db.getRepository('b1'); + const b1 = await r2.create({ + values: {}, + }); + await r1.create({ + values: { + name: 'a1', + b1: [b1.toJSON()], + }, + }); + const b1r = await b1.reload(); + expect(b1r.a1Id).toBe(b1.id); + }); + + test('should throw an error', async () => { + db.collection({ + name: 'a1', + fields: [ + { + type: 'hasMany', + name: 'b1', + target: 'b1', + targetKey: 'code', + }, + ], + }); + db.collection({ + name: 'b1', + fields: [ + { + type: 'string', + name: 'code', + unique: true, + }, + ], + }); + await db.sync(); + const r1 = db.getRepository('a1'); + const r2 = db.getRepository('b1'); + const b1 = await r2.create({ + values: {}, + }); + await expect(async () => { + await r1.create({ + values: { + name: 'a1', + b1: [b1.toJSON()], + }, + }); + }).rejects.toThrowError('code field value is empty'); + }); + + test('should find by code', async () => { + db.collection({ + name: 'a1', + fields: [ + { + type: 'hasMany', + name: 'b1', + target: 'b1', + targetKey: 'code', + }, + ], + }); + db.collection({ + name: 'b1', + fields: [ + { + type: 'string', + name: 'code', + unique: true, + }, + ], + }); + await db.sync(); + const r1 = db.getRepository('a1'); + const r2 = db.getRepository('b1'); + const b1 = await r2.create({ + values: { + code: 'code1', + }, + }); + await r1.create({ + values: { + name: 'a1', + b1: [b1.toJSON()], + }, + }); + const b1r = await b1.reload(); + expect(b1r.a1Id).toBe(b1.id); + }); + + test('should find by a1Code and code', async () => { + db.collection({ + name: 'a1', + fields: [ + { + type: 'string', + name: 'code', + unique: true, + }, + { + type: 'hasMany', + name: 'b1', + target: 'b1', + sourceKey: 'code', + foreignKey: 'a1Code', + targetKey: 'code', + }, + ], + }); + db.collection({ + name: 'b1', + indexes: [ + { + type: 'UNIQUE', + fields: ['a1Code', 'code'], + }, + ], + fields: [ + { + type: 'string', + name: 'a1Code', + }, + { + type: 'string', + name: 'code', + }, + ], + }); + await db.sync(); + const r1 = db.getRepository('a1'); + const r2 = db.getRepository('b1'); + await r2.create({ + values: { + code: 'b1', + }, + }); + const b1 = await r2.create({ + values: { + code: 'b1', + }, + }); + await r1.create({ + values: { + code: 'a1', + b1: [b1.toJSON()], + }, + }); + const b1r = await b1.reload(); + expect(b1r.a1Code).toBe('a1'); + expect(b1r.code).toBe('b1'); + }); +}); diff --git a/packages/core/database/src/update-associations.ts b/packages/core/database/src/update-associations.ts index 14629cb1a3..d7889fc60c 100644 --- a/packages/core/database/src/update-associations.ts +++ b/packages/core/database/src/update-associations.ts @@ -490,6 +490,10 @@ export async function updateMultipleAssociation( accessorOptions['through'] = throughValue; } + if (pk !== targetKey && !isUndefinedOrNull(item[pk]) && isUndefinedOrNull(item[targetKey])) { + throw new Error(`${targetKey} field value is empty`); + } + if (isUndefinedOrNull(item[targetKey])) { // create new record const instance = await model[createAccessor](item, accessorOptions); diff --git a/packages/core/database/src/update-guard.ts b/packages/core/database/src/update-guard.ts index 80dacd7ea3..12d0e84df0 100644 --- a/packages/core/database/src/update-guard.ts +++ b/packages/core/database/src/update-guard.ts @@ -157,8 +157,8 @@ export class UpdateGuard { return value; } - const associationKeyName = (associationObj).targetKey - ? (associationObj).targetKey + const associationKeyName = associationObj?.['options']?.targetKey + ? associationObj['options'].targetKey : associationObj.target.primaryKeyAttribute; if (value[associationKeyName]) {