222 lines
5.9 KiB
TypeScript
222 lines
5.9 KiB
TypeScript
import Database, { Collection, MagicAttributeModel, SyncOptions, Transactionable } from '@nocobase/database';
|
|
|
|
interface LoadOptions extends Transactionable {
|
|
// TODO
|
|
skipExist?: boolean;
|
|
}
|
|
|
|
interface MigrateOptions extends SyncOptions, Transactionable {
|
|
isNew?: boolean;
|
|
}
|
|
|
|
export class FieldModel extends MagicAttributeModel {
|
|
get db(): Database {
|
|
return (<any>this.constructor).database;
|
|
}
|
|
|
|
isAssociationField() {
|
|
return ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany'].includes(this.get('type'));
|
|
}
|
|
|
|
async load(loadOptions?: LoadOptions) {
|
|
const { skipExist = false, transaction } = loadOptions || {};
|
|
const collectionName = this.get('collectionName');
|
|
|
|
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();
|
|
|
|
const field = await (async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
return collection.setField(name, options);
|
|
})();
|
|
|
|
await this.db.emitAsync('field:loaded', {
|
|
fieldKey: this.get('key'),
|
|
transaction,
|
|
});
|
|
|
|
return field;
|
|
}
|
|
|
|
async syncSortByField(options: Transactionable) {
|
|
const collectionName = this.get('collectionName');
|
|
const collection = this.db.getCollection(collectionName);
|
|
await this.load(options);
|
|
await collection.sync({
|
|
force: false,
|
|
alter: {
|
|
drop: false,
|
|
},
|
|
// @ts-ignore
|
|
transaction: options.transaction,
|
|
});
|
|
}
|
|
|
|
async migrate({ isNew, ...options }: MigrateOptions = {}) {
|
|
let field;
|
|
try {
|
|
field = await this.load({
|
|
transaction: options.transaction,
|
|
});
|
|
|
|
if (!field) {
|
|
return;
|
|
}
|
|
const collection = this.getFieldCollection();
|
|
|
|
if (isNew && collection.model.rawAttributes[this.get('name')] && this.get('unique')) {
|
|
// trick: set unique to false to avoid auto sync unique index
|
|
collection.model.rawAttributes[this.get('name')].unique = false;
|
|
}
|
|
|
|
await field.sync(options);
|
|
|
|
if (isNew && this.get('unique')) {
|
|
await this.syncUniqueIndex({
|
|
transaction: options.transaction,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
// field sync failed, delete from memory
|
|
if (isNew && field) {
|
|
// update field should not remove field from memory
|
|
field.remove();
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async remove(options?: any) {
|
|
const collection = this.getFieldCollection();
|
|
|
|
if (!collection) {
|
|
return;
|
|
}
|
|
|
|
return collection.removeFieldFromDb(this.get('name'), {
|
|
transaction: options.transaction,
|
|
});
|
|
}
|
|
|
|
async syncUniqueIndex(options: Transactionable) {
|
|
const unique = this.get('unique');
|
|
const collection = this.getFieldCollection();
|
|
const field = collection.getField(this.get('name'));
|
|
const columnName = collection.model.rawAttributes[this.get('name')].field;
|
|
const tableName = collection.model.tableName;
|
|
|
|
const queryInterface = this.db.sequelize.getQueryInterface() as any;
|
|
|
|
const existsIndexes = await queryInterface.showIndex(collection.getTableNameWithSchema(), {
|
|
transaction: options.transaction,
|
|
});
|
|
|
|
const existUniqueIndex = existsIndexes.find((item) => {
|
|
return item.unique && item.fields[0].attribute === columnName && item.fields.length === 1;
|
|
});
|
|
|
|
let existsUniqueConstraint;
|
|
|
|
const constraintName = `${tableName}_${field.name}_uk`;
|
|
|
|
if (existUniqueIndex) {
|
|
const existsUniqueConstraints = await queryInterface.showConstraint(
|
|
collection.getTableNameWithSchema(),
|
|
constraintName,
|
|
{},
|
|
);
|
|
|
|
existsUniqueConstraint = existsUniqueConstraints[0];
|
|
}
|
|
|
|
if (unique && !existsUniqueConstraint) {
|
|
// @ts-ignore
|
|
await collection.sync({ ...options, force: false, alter: { drop: false } });
|
|
|
|
await queryInterface.addConstraint(collection.getTableNameWithSchema(), {
|
|
type: 'unique',
|
|
fields: [columnName],
|
|
name: constraintName,
|
|
transaction: options.transaction,
|
|
});
|
|
|
|
this.db.logger.info(`add unique index ${constraintName}`);
|
|
}
|
|
|
|
if (!unique && existsUniqueConstraint) {
|
|
await queryInterface.removeConstraint(collection.getTableNameWithSchema(), constraintName, {
|
|
transaction: options.transaction,
|
|
});
|
|
|
|
this.db.logger.info(`remove unique index ${constraintName}`);
|
|
}
|
|
}
|
|
|
|
async syncDefaultValue(
|
|
options: Transactionable & {
|
|
defaultValue: any;
|
|
},
|
|
) {
|
|
const collection = this.getFieldCollection();
|
|
|
|
if (!collection) {
|
|
return;
|
|
}
|
|
|
|
const field = collection.getField(this.get('name'));
|
|
|
|
// overriding field should not sync default value
|
|
if (field.get('overriding')) {
|
|
return;
|
|
}
|
|
|
|
const queryInterface = collection.db.sequelize.getQueryInterface();
|
|
|
|
await queryInterface.changeColumn(
|
|
collection.getTableNameWithSchema(),
|
|
collection.model.rawAttributes[this.get('name')].field,
|
|
{
|
|
type: field.dataType,
|
|
defaultValue: options.defaultValue,
|
|
},
|
|
{
|
|
transaction: options.transaction,
|
|
},
|
|
);
|
|
}
|
|
|
|
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 {
|
|
const collectionName = this.get('collectionName');
|
|
|
|
if (!this.db.hasCollection(collectionName)) {
|
|
return;
|
|
}
|
|
|
|
return this.db.getCollection(collectionName);
|
|
}
|
|
}
|