fix: sync collection field default value (#907)
This commit is contained in:
parent
3e6d1a3427
commit
76f5754e20
@ -5,7 +5,7 @@ import {
|
||||
ModelIndexesOptions,
|
||||
QueryInterfaceOptions,
|
||||
SyncOptions,
|
||||
Transactionable
|
||||
Transactionable,
|
||||
} from 'sequelize';
|
||||
import { Collection } from '../collection';
|
||||
import { Database } from '../database';
|
||||
@ -53,7 +53,6 @@ export abstract class Field {
|
||||
this.init();
|
||||
}
|
||||
|
||||
// TODO
|
||||
async sync(syncOptions: SyncOptions) {
|
||||
await this.collection.sync({
|
||||
...syncOptions,
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { MockServer } from '@nocobase/test';
|
||||
import { createApp } from '..';
|
||||
import { Collection } from '@nocobase/database';
|
||||
|
||||
describe('field defaultValue', () => {
|
||||
let app: MockServer;
|
||||
|
||||
let TestCollection: Collection;
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await createApp();
|
||||
await app
|
||||
@ -14,13 +17,9 @@ describe('field defaultValue', () => {
|
||||
name: 'test1',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
TestCollection = app.db.getCollection('test1');
|
||||
|
||||
it('should be updated', async () => {
|
||||
await app
|
||||
.agent()
|
||||
.resource('collections.fields', 'test1')
|
||||
@ -31,6 +30,19 @@ describe('field defaultValue', () => {
|
||||
defaultValue: 'abc',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.destroy();
|
||||
});
|
||||
|
||||
it('should be updated', async () => {
|
||||
const dialect = app.db.sequelize.getDialect();
|
||||
|
||||
if (dialect != 'postgres') {
|
||||
return;
|
||||
}
|
||||
|
||||
const response1 = await app.agent().resource('test1').create();
|
||||
expect(response1.body.data.field1).toBe('abc');
|
||||
await app
|
||||
@ -43,7 +55,28 @@ describe('field defaultValue', () => {
|
||||
defaultValue: 'cba',
|
||||
},
|
||||
});
|
||||
const response2 = await app.agent().resource('test1').create();
|
||||
expect(response2.body.data.field1).toBe('cba');
|
||||
|
||||
const response2 = await app.agent().resource('test1').create();
|
||||
expect(response2.body.data.field1).toBe('cba');
|
||||
|
||||
const results = await app.db.sequelize.getQueryInterface().describeTable(TestCollection.model.tableName);
|
||||
|
||||
expect(results.field1.defaultValue).toBe('cba');
|
||||
});
|
||||
|
||||
it('should be cleaned', async () => {
|
||||
await app
|
||||
.agent()
|
||||
.resource('collections.fields', 'test1')
|
||||
.update({
|
||||
filterByTk: 'field1',
|
||||
values: {
|
||||
type: 'string',
|
||||
defaultValue: null,
|
||||
},
|
||||
});
|
||||
|
||||
const response2 = await app.agent().resource('test1').create();
|
||||
expect(response2.body.data.field1).toBeNull();
|
||||
});
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import Database, { Field, MagicAttributeModel } from '@nocobase/database';
|
||||
import { SyncOptions, Transactionable, UniqueConstraintError, Utils } from 'sequelize';
|
||||
import Database, { Collection, Field, MagicAttributeModel } from '@nocobase/database';
|
||||
import { SyncOptions, Transactionable, UniqueConstraintError } from 'sequelize';
|
||||
|
||||
interface LoadOptions extends Transactionable {
|
||||
// TODO
|
||||
@ -13,16 +13,23 @@ interface MigrateOptions extends SyncOptions, Transactionable {
|
||||
async function migrate(field: Field, options: MigrateOptions): Promise<void> {
|
||||
const { unique } = field.options;
|
||||
const { model } = field.collection;
|
||||
|
||||
const ukName = `${model.tableName}_${field.name}_uk`;
|
||||
const queryInterface = model.sequelize.getQueryInterface();
|
||||
const fieldAttribute = model.rawAttributes[field.name];
|
||||
|
||||
// @ts-ignore
|
||||
const existedConstraints = await queryInterface.showConstraint(model.tableName, ukName, { transaction: options.transaction }) as any[];
|
||||
const constraintBefore = existedConstraints.find(item => item.constraintName === ukName);
|
||||
const existedConstraints = (await queryInterface.showConstraint(model.tableName, ukName, {
|
||||
transaction: options.transaction,
|
||||
})) as any[];
|
||||
|
||||
const constraintBefore = existedConstraints.find((item) => item.constraintName === ukName);
|
||||
|
||||
if (typeof fieldAttribute?.unique !== 'undefined') {
|
||||
if (constraintBefore && !unique) {
|
||||
await queryInterface.removeConstraint(model.tableName, ukName, { transaction: options.transaction });
|
||||
}
|
||||
|
||||
fieldAttribute.unique = Boolean(constraintBefore);
|
||||
}
|
||||
|
||||
@ -33,19 +40,24 @@ async function migrate(field: Field, options: MigrateOptions): Promise<void> {
|
||||
type: 'unique',
|
||||
fields: [field.name],
|
||||
name: ukName,
|
||||
transaction: options.transaction
|
||||
transaction: options.transaction,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof fieldAttribute?.unique !== 'undefined') {
|
||||
fieldAttribute.unique = unique;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const updatedConstraints = await queryInterface.showConstraint(model.tableName, ukName, { transaction: options.transaction }) as any[];
|
||||
const indexAfter = updatedConstraints.find(item => item.constraintName === ukName);
|
||||
const updatedConstraints = (await queryInterface.showConstraint(model.tableName, ukName, {
|
||||
transaction: options.transaction,
|
||||
})) as any[];
|
||||
|
||||
const indexAfter = updatedConstraints.find((item) => item.constraintName === ukName);
|
||||
|
||||
if (unique && !indexAfter) {
|
||||
throw new UniqueConstraintError({
|
||||
fields: { [field.name]: undefined }
|
||||
fields: { [field.name]: undefined },
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -61,12 +73,15 @@ export class FieldModel extends MagicAttributeModel {
|
||||
if (!this.db.hasCollection(collectionName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collection = this.db.getCollection(collectionName);
|
||||
const name = this.get('name');
|
||||
if (skipExist && collection.hasField(name)) {
|
||||
return collection.getField(name);
|
||||
}
|
||||
|
||||
const options = this.get();
|
||||
|
||||
if (options.uiSchemaUid) {
|
||||
const UISchema = this.db.getModel('uiSchemas');
|
||||
const uiSchema = await UISchema.findByPk(options.uiSchemaUid, {
|
||||
@ -74,6 +89,7 @@ export class FieldModel extends MagicAttributeModel {
|
||||
});
|
||||
Object.assign(options, { uiSchema: uiSchema.get() });
|
||||
}
|
||||
|
||||
return collection.setField(name, options);
|
||||
}
|
||||
|
||||
@ -81,6 +97,7 @@ export class FieldModel extends MagicAttributeModel {
|
||||
const field = await this.load({
|
||||
transaction: options.transaction,
|
||||
});
|
||||
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
@ -98,11 +115,11 @@ export class FieldModel extends MagicAttributeModel {
|
||||
}
|
||||
|
||||
async remove(options?: any) {
|
||||
const collectionName = this.get('collectionName');
|
||||
if (!this.db.hasCollection(collectionName)) {
|
||||
const collection = this.getFieldCollection();
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
const collection = this.db.getCollection(collectionName);
|
||||
|
||||
const field = collection.getField(this.get('name'));
|
||||
if (!field) {
|
||||
return;
|
||||
@ -111,4 +128,42 @@ export class FieldModel extends MagicAttributeModel {
|
||||
transaction: options.transaction,
|
||||
});
|
||||
}
|
||||
|
||||
async syncDefaultValue(
|
||||
options: Transactionable & {
|
||||
defaultValue: any;
|
||||
},
|
||||
) {
|
||||
const collection = this.getFieldCollection();
|
||||
|
||||
if (!collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const field = collection.getField(this.get('name'));
|
||||
|
||||
const queryInterface = collection.db.sequelize.getQueryInterface();
|
||||
|
||||
await queryInterface.changeColumn(
|
||||
collection.model.tableName,
|
||||
this.get('name'),
|
||||
{
|
||||
type: field.dataType,
|
||||
defaultValue: options.defaultValue,
|
||||
},
|
||||
{
|
||||
transaction: options.transaction,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
protected getFieldCollection(): Collection | null {
|
||||
const collectionName = this.get('collectionName');
|
||||
|
||||
if (!this.db.hasCollection(collectionName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.db.getCollection(collectionName);
|
||||
}
|
||||
}
|
||||
|
@ -60,12 +60,14 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
database: this.app.db,
|
||||
});
|
||||
});
|
||||
|
||||
for (const key in beforeInitOptions) {
|
||||
if (Object.prototype.hasOwnProperty.call(beforeInitOptions, key)) {
|
||||
const fn = beforeInitOptions[key];
|
||||
this.app.db.on(`fields.${key}.beforeInitOptions`, fn);
|
||||
}
|
||||
}
|
||||
|
||||
this.app.db.on('fields.afterCreate', afterCreateForReverseField(this.app.db));
|
||||
|
||||
this.app.db.on('collections.afterCreateWithAssociations', async (model, { context, transaction }) => {
|
||||
@ -87,13 +89,24 @@ export class CollectionManagerPlugin extends Plugin {
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterUpdate', async (model: FieldModel, { context, transaction }) => {
|
||||
const prevOptions = model.previous('options');
|
||||
const currentOptions = model.get('options');
|
||||
|
||||
if (context) {
|
||||
const { unique: prev } = model.previous('options');
|
||||
const { unique: next } = model.get('options');
|
||||
const prev = prevOptions['unique'];
|
||||
const next = currentOptions['unique'];
|
||||
|
||||
if (Boolean(prev) !== Boolean(next)) {
|
||||
await model.migrate({ transaction });
|
||||
}
|
||||
}
|
||||
|
||||
const prevDefaultValue = prevOptions['defaultValue'];
|
||||
const currentDefaultValue = currentOptions['defaultValue'];
|
||||
|
||||
if (prevDefaultValue != currentDefaultValue) {
|
||||
await model.syncDefaultValue({ transaction, defaultValue: currentDefaultValue });
|
||||
}
|
||||
});
|
||||
|
||||
this.app.db.on('fields.afterSaveWithAssociations', async (model, { context, transaction }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user