fix: reference options sync (#1061)

* fix: sync onDelete options between field and reverseField

* feat: reference compatibility
This commit is contained in:
ChengLei Shao 2022-11-10 21:28:33 +08:00 committed by GitHub
parent 8a8478cf4c
commit 83cc19a013
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 93 additions and 12 deletions

View File

@ -10,22 +10,25 @@ class ReferencesMap {
protected map: Map<string, Reference[]> = new Map(); protected map: Map<string, Reference[]> = new Map();
addReference(reference: Reference) { addReference(reference: Reference) {
if (!reference.onDelete) {
reference.onDelete = 'SET NULL';
}
const existReference = this.existReference(reference); const existReference = this.existReference(reference);
if (existReference) { if (existReference && existReference.onDelete !== reference.onDelete) {
if (reference.onDelete && existReference.onDelete !== reference.onDelete) { if (reference.onDelete === 'SET NULL') {
// using existing reference
return;
} else if (existReference.onDelete === 'SET NULL') {
this.removeReference(existReference);
} else {
throw new Error( throw new Error(
`On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify( `On Delete Conflict, exist reference ${JSON.stringify(existReference)}, new reference ${JSON.stringify(
reference, reference,
)}`, )}`,
); );
} }
return;
}
if (!reference.onDelete) {
reference.onDelete = 'SET NULL';
} }
this.map.set(reference.targetCollectionName, [...(this.map.get(reference.targetCollectionName) || []), reference]); this.map.set(reference.targetCollectionName, [...(this.map.get(reference.targetCollectionName) || []), reference]);

View File

@ -106,6 +106,9 @@ export class ApplicationVersion {
async get() { async get() {
if (await this.app.db.collectionExistsInDb('applicationVersion')) { if (await this.app.db.collectionExistsInDb('applicationVersion')) {
const model = await this.collection.model.findOne(); const model = await this.collection.model.findOne();
if (!model) {
return null;
}
return model.get('value') as any; return model.get('value') as any;
} }
return null; return null;

View File

@ -56,6 +56,49 @@ describe('reverseField options', () => {
expect(json.foreignKey).toBe(json.reverseField.foreignKey); expect(json.foreignKey).toBe(json.reverseField.foreignKey);
}); });
it('should sync onDelete options for reverse field', async () => {
const field = await Field.repository.create({
values: {
type: 'hasMany',
collectionName: 'tests',
target: 'targets',
onDelete: 'CASCADE',
reverseField: {},
},
});
const { reverseField } = field.toJSON();
expect(reverseField.onDelete).toBe('CASCADE');
});
it('should update reverseField onDelete options', async () => {
const field = await Field.repository.create({
values: {
type: 'hasMany',
collectionName: 'tests',
target: 'targets',
onDelete: 'CASCADE',
reverseField: {},
},
});
const { reverseField } = field.toJSON();
await Field.repository.update({
filterByTk: reverseField.key,
values: {
onDelete: 'SET NULL',
},
});
const mainField = await Field.repository.findOne({
filterByTk: field.get('key'),
});
expect(mainField.get('onDelete')).toBe('SET NULL');
});
it('should update reverseField', async () => { it('should update reverseField', async () => {
const field = await Field.repository.create({ const field = await Field.repository.create({
values: { values: {

View File

@ -81,9 +81,9 @@ export function afterCreateForForeignKeyField(db: Database) {
await r.update({ await r.update({
filter: { filter: {
collectionName, collectionName,
options:{ options: {
primaryKey: true primaryKey: true,
} },
}, },
values: { values: {
sort: 0, sort: 0,

View File

@ -4,6 +4,7 @@ export function afterCreateForReverseField(db: Database) {
return async (model, { transaction }) => { return async (model, { transaction }) => {
const Field = db.getCollection('fields'); const Field = db.getCollection('fields');
const reverseKey = model.get('reverseKey'); const reverseKey = model.get('reverseKey');
if (!reverseKey) { if (!reverseKey) {
return; return;
} }

View File

@ -4,19 +4,28 @@ export function beforeCreateForReverseField(db: Database) {
return async (model, { transaction }) => { return async (model, { transaction }) => {
const Field = db.getCollection('fields'); const Field = db.getCollection('fields');
const reverseKey = model.get('reverseKey'); const reverseKey = model.get('reverseKey');
if (!reverseKey) { if (!reverseKey) {
return; return;
} }
const reverse = await Field.model.findByPk(reverseKey, { transaction }); const reverse = await Field.model.findByPk(reverseKey, { transaction });
model.set('collectionName', reverse.get('target')); model.set('collectionName', reverse.get('target'));
model.set('target', reverse.get('collectionName')); model.set('target', reverse.get('collectionName'));
if (reverse.get('onDelete')) {
model.set('onDelete', reverse.get('onDelete'));
}
const reverseType = reverse.get('type') as any; const reverseType = reverse.get('type') as any;
if (['hasMany', 'hasOne'].includes(reverseType)) { if (['hasMany', 'hasOne'].includes(reverseType)) {
model.set('type', 'belongsTo'); model.set('type', 'belongsTo');
model.set('targetKey', reverse.get('sourceKey')); model.set('targetKey', reverse.get('sourceKey'));
model.set('foreignKey', reverse.get('foreignKey')); model.set('foreignKey', reverse.get('foreignKey'));
model.set('sourceKey', reverse.get('targetKey')); model.set('sourceKey', reverse.get('targetKey'));
} }
if (['belongsTo'].includes(reverseType)) { if (['belongsTo'].includes(reverseType)) {
if (!model.get('type')) { if (!model.get('type')) {
model.set('type', 'hasMany'); model.set('type', 'hasMany');
@ -25,6 +34,7 @@ export function beforeCreateForReverseField(db: Database) {
model.set('foreignKey', reverse.get('foreignKey')); model.set('foreignKey', reverse.get('foreignKey'));
model.set('targetKey', reverse.get('sourceKey')); model.set('targetKey', reverse.get('sourceKey'));
} }
if (['belongsToMany'].includes(reverseType)) { if (['belongsToMany'].includes(reverseType)) {
model.set('type', 'belongsToMany'); model.set('type', 'belongsToMany');
model.set('through', reverse.get('through')); model.set('through', reverse.get('through'));

View File

@ -156,6 +156,20 @@ export class FieldModel extends MagicAttributeModel {
); );
} }
async syncReferenceCheckOption(options: Transactionable) {
const reverseKey = this.get('reverseKey');
if (!reverseKey) return;
const reverseField = await this.db.getCollection('fields').repository.findOne({
filterByTk: reverseKey,
transaction: options.transaction,
});
if (!reverseField) return;
reverseField.set('onDelete', this.get('onDelete'));
await reverseField.save({ hooks: false, transaction: options.transaction });
}
protected getFieldCollection(): Collection | null { protected getFieldCollection(): Collection | null {
const collectionName = this.get('collectionName'); const collectionName = this.get('collectionName');

View File

@ -13,7 +13,7 @@ import {
beforeCreateForChildrenCollection, beforeCreateForChildrenCollection,
beforeCreateForReverseField, beforeCreateForReverseField,
beforeDestroyForeignKey, beforeDestroyForeignKey,
beforeInitOptions beforeInitOptions,
} from './hooks'; } from './hooks';
import { CollectionModel, FieldModel } from './models'; import { CollectionModel, FieldModel } from './models';
@ -106,6 +106,13 @@ export class CollectionManagerPlugin extends Plugin {
if (prevDefaultValue != currentDefaultValue) { if (prevDefaultValue != currentDefaultValue) {
await model.syncDefaultValue({ transaction, defaultValue: currentDefaultValue }); await model.syncDefaultValue({ transaction, defaultValue: currentDefaultValue });
} }
const prevOnDelete = prevOptions['onDelete'];
const currentOnDelete = currentOptions['onDelete'];
if (prevOnDelete != currentOnDelete) {
await model.syncReferenceCheckOption({ transaction });
}
}); });
this.app.db.on('fields.afterSaveWithAssociations', async (model: FieldModel, { context, transaction }) => { this.app.db.on('fields.afterSaveWithAssociations', async (model: FieldModel, { context, transaction }) => {