fix: reference options sync (#1061)
* fix: sync onDelete options between field and reverseField * feat: reference compatibility
This commit is contained in:
		
							parent
							
								
									8a8478cf4c
								
							
						
					
					
						commit
						83cc19a013
					
				@ -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]);
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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: {
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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'));
 | 
				
			||||||
 | 
				
			|||||||
@ -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');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 }) => {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user