From 70ab4dcf1f81ad26a0f94e6f00b6a10f5fdede9f Mon Sep 17 00:00:00 2001 From: chenos Date: Fri, 1 Jul 2022 09:33:05 +0800 Subject: [PATCH] fix(database): index invalid (#564) * fix(database): index invalid * fix: test error --- .../database/src/__tests__/collection.test.ts | 16 +++++ .../src/__tests__/update-associations.test.ts | 22 +++--- packages/core/database/src/collection.ts | 70 ++++++++++++++++++- .../database/src/fields/belongs-to-field.ts | 3 +- .../src/fields/belongs-to-many-field.ts | 2 + packages/core/database/src/fields/field.ts | 6 ++ .../database/src/fields/has-many-field.ts | 14 +++- .../core/database/src/fields/has-one-field.ts | 10 +++ .../src/server/collections/auditLogs.ts | 1 + .../src/collections/collections.ts | 1 + .../src/collections/uiRoutes.ts | 4 +- packages/plugins/users/src/server.ts | 6 +- 12 files changed, 136 insertions(+), 19 deletions(-) diff --git a/packages/core/database/src/__tests__/collection.test.ts b/packages/core/database/src/__tests__/collection.test.ts index f83ccf6ee..29ad3859a 100644 --- a/packages/core/database/src/__tests__/collection.test.ts +++ b/packages/core/database/src/__tests__/collection.test.ts @@ -13,6 +13,22 @@ describe('collection', () => { await db.close(); }); + test.skip('indexes', async () => { + await db.clean({ drop: true }); + const collection = db.collection({ + name: 'test', + fields: [ + { + type: 'string', + name: 'name', + index: true, + }, + ], + }); + collection.removeField('name'); + await db.sync(); + }); + test('removeFromDb', async () => { await db.clean({ drop: true }); const collection = db.collection({ diff --git a/packages/core/database/src/__tests__/update-associations.test.ts b/packages/core/database/src/__tests__/update-associations.test.ts index c4c6c3491..70d75c128 100644 --- a/packages/core/database/src/__tests__/update-associations.test.ts +++ b/packages/core/database/src/__tests__/update-associations.test.ts @@ -1,6 +1,6 @@ import { Collection } from '../collection'; import { Database } from '../database'; -import { updateAssociation, updateAssociations } from '../update-associations'; +import { updateAssociations } from '../update-associations'; import { mockDatabase } from './'; describe('update associations', () => { @@ -356,13 +356,14 @@ describe('update associations', () => { }); describe('belongsToMany', () => { - let db; - let Post; - let Tag; - let PostTag; + let db: Database; + let Post: Collection; + let Tag: Collection; + let PostTag: Collection; beforeEach(async () => { db = mockDatabase(); + await db.clean({ drop: true }); PostTag = db.collection({ name: 'posts_tags', fields: [{ type: 'string', name: 'tagged_at' }], @@ -389,7 +390,7 @@ describe('update associations', () => { afterEach(async () => { await db.close(); }); - test('set through value', async () => { + test.only('set through value', async () => { const p1 = await Post.repository.create({ values: { title: 'hello', @@ -404,9 +405,12 @@ describe('update associations', () => { ], }, }); - - const t1 = (await p1.getTags())[0]; - expect(t1.posts_tags.tagged_at).toEqual('123'); + const count = await PostTag.repository.count({ + filter: { + tagged_at: '123', + }, + }); + expect(count).toEqual(1); }); }); }); diff --git a/packages/core/database/src/collection.ts b/packages/core/database/src/collection.ts index 6cc8ecaac..08ca7a397 100644 --- a/packages/core/database/src/collection.ts +++ b/packages/core/database/src/collection.ts @@ -1,7 +1,14 @@ import merge from 'deepmerge'; import { EventEmitter } from 'events'; import { default as lodash, default as _ } from 'lodash'; -import { ModelCtor, ModelOptions, QueryInterfaceDropTableOptions, SyncOptions, Transactionable } from 'sequelize'; +import { + ModelCtor, + ModelOptions, + QueryInterfaceDropTableOptions, + SyncOptions, + Transactionable, + Utils +} from 'sequelize'; import { Database } from './database'; import { Field, FieldOptions } from './fields'; import { Model } from './model'; @@ -278,6 +285,67 @@ export class Collection< this.setField(options.name || name, options); } + addIndex(index: any) { + if (!index) { + return; + } + let indexes: any = this.model.options.indexes || []; + let indexName = []; + let indexItem; + if (typeof index === 'string') { + indexItem = { + fields: [index], + }; + indexName = [index]; + } else if (Array.isArray(index)) { + indexItem = { + fields: index, + }; + indexName = index; + } else if (index?.fields) { + indexItem = index; + indexName = index.fields; + } + if (lodash.isEqual(this.model.primaryKeyAttributes, indexName)) { + return; + } + const name: string = this.model.primaryKeyAttributes.join(','); + if (name.startsWith(`${indexName.join(',')},`)) { + return; + } + for (const item of indexes) { + if (lodash.isEqual(item.fields, indexName)) { + return; + } + const name: string = item.fields.join(','); + if (name.startsWith(`${indexName.join(',')},`)) { + return; + } + } + if (!indexItem) { + return; + } + indexes.push(indexItem); + this.model.options.indexes = indexes; + const tableName = this.model.getTableName(); + // @ts-ignore + this.model._indexes = this.model.options.indexes + // @ts-ignore + .map((index) => Utils.nameIndex(this.model._conformIndex(index), tableName)); + } + + removeIndex(fields: any) { + if (!fields) { + return; + } + // @ts-ignore + const indexes: any[] = this.model._indexes; + // @ts-ignore + this.model._indexes = indexes.filter((item) => { + return !lodash.isEqual(item.fields, fields); + }); + } + async sync(syncOptions?: SyncOptions) { const modelNames = [this.model.name]; diff --git a/packages/core/database/src/fields/belongs-to-field.ts b/packages/core/database/src/fields/belongs-to-field.ts index bf3432125..cb248b66b 100644 --- a/packages/core/database/src/fields/belongs-to-field.ts +++ b/packages/core/database/src/fields/belongs-to-field.ts @@ -46,7 +46,7 @@ export class BelongsToField extends RelationField { // @ts-ignore this.options.sourceKey = association.sourceKey; } - + this.collection.addIndex([this.options.foreignKey]); return true; } @@ -68,6 +68,7 @@ export class BelongsToField extends RelationField { delete collection.model.associations[this.name]; // @ts-ignore collection.model.refreshAttributes(); + // this.collection.removeIndex([this.options.foreignKey]); } } diff --git a/packages/core/database/src/fields/belongs-to-many-field.ts b/packages/core/database/src/fields/belongs-to-many-field.ts index c2315217a..4ef70a5d7 100644 --- a/packages/core/database/src/fields/belongs-to-many-field.ts +++ b/packages/core/database/src/fields/belongs-to-many-field.ts @@ -58,6 +58,8 @@ export class BelongsToManyField extends RelationField { if (!this.options.through) { this.options.through = this.through; } + Through.addIndex([this.options.foreignKey]); + Through.addIndex([this.options.otherKey]); return true; } diff --git a/packages/core/database/src/fields/field.ts b/packages/core/database/src/fields/field.ts index de4fca9fd..32d23ec99 100644 --- a/packages/core/database/src/fields/field.ts +++ b/packages/core/database/src/fields/field.ts @@ -171,11 +171,17 @@ export abstract class Field { model.rawAttributes[this.name] = this.toSequelize(); // @ts-ignore model.refreshAttributes(); + if (this.options.index) { + this.context.collection.addIndex([this.name]); + } } unbind() { const { model } = this.context.collection; model.removeAttribute(this.name); + if (this.options.index) { + this.context.collection.removeIndex([this.name]); + } } toSequelize(): any { diff --git a/packages/core/database/src/fields/has-many-field.ts b/packages/core/database/src/fields/has-many-field.ts index d3f87ad13..3f14b4093 100644 --- a/packages/core/database/src/fields/has-many-field.ts +++ b/packages/core/database/src/fields/has-many-field.ts @@ -7,7 +7,7 @@ import { HasManyOptions as SequelizeHasManyOptions, Utils } from 'sequelize'; - +import { Collection } from '../collection'; import { MultipleRelationFieldOptions, RelationField } from './relation-field'; export interface HasManyFieldOptions extends HasManyOptions { @@ -98,7 +98,6 @@ export class HasManyField extends RelationField { as: this.name, foreignKey: this.foreignKey, }); - // inverse relation // this.TargetModel.belongsTo(collection.model); @@ -112,6 +111,15 @@ export class HasManyField extends RelationField { // @ts-ignore this.options.sourceKey = association.sourceKey; } + let tcoll: Collection; + if (this.target === collection.name) { + tcoll = collection; + } else { + tcoll = database.getCollection(this.target); + } + if (tcoll) { + tcoll.addIndex([this.options.foreignKey]); + } return true; } @@ -120,7 +128,7 @@ export class HasManyField extends RelationField { // 如果关系字段还没建立就删除了,也同步删除待建立关联的关系字段 database.removePendingField(this); // 如果关系表内没有显式的创建外键字段,删除关系时,外键也删除掉 - const tcoll = database.collections.get(this.target); + const tcoll = database.getCollection(this.target); const foreignKey = this.options.foreignKey; const field = tcoll.findField((field) => { if (field.name === foreignKey) { diff --git a/packages/core/database/src/fields/has-one-field.ts b/packages/core/database/src/fields/has-one-field.ts index ab3dbb958..436fc7cf7 100644 --- a/packages/core/database/src/fields/has-one-field.ts +++ b/packages/core/database/src/fields/has-one-field.ts @@ -7,6 +7,7 @@ import { HasOneOptions as SequelizeHasOneOptions, Utils } from 'sequelize'; +import { Collection } from '../collection'; import { BaseRelationFieldOptions, RelationField } from './relation-field'; export interface HasOneFieldOptions extends HasOneOptions { @@ -106,6 +107,15 @@ export class HasOneField extends RelationField { // @ts-ignore this.options.sourceKey = association.sourceKey; } + let tcoll: Collection; + if (this.target === collection.name) { + tcoll = collection; + } else { + tcoll = database.getCollection(this.target); + } + if (tcoll) { + tcoll.addIndex([this.options.foreignKey]); + } return true; } diff --git a/packages/plugins/audit-logs/src/server/collections/auditLogs.ts b/packages/plugins/audit-logs/src/server/collections/auditLogs.ts index 7c72c5fc1..df90dd076 100644 --- a/packages/plugins/audit-logs/src/server/collections/auditLogs.ts +++ b/packages/plugins/audit-logs/src/server/collections/auditLogs.ts @@ -17,6 +17,7 @@ export default defineCollection({ { type: 'string', name: 'recordId', + index: true, }, { type: 'string', diff --git a/packages/plugins/collection-manager/src/collections/collections.ts b/packages/plugins/collection-manager/src/collections/collections.ts index 1c4ae1007..6d6faa87f 100644 --- a/packages/plugins/collection-manager/src/collections/collections.ts +++ b/packages/plugins/collection-manager/src/collections/collections.ts @@ -44,6 +44,7 @@ export default { targetKey: 'name', foreignKey: 'collectionName', sortBy: 'sort', + constraints: true, }, ], } as CollectionOptions; diff --git a/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts b/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts index 06846ea94..b17bbdc34 100644 --- a/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts +++ b/packages/plugins/ui-routes-storage/src/collections/uiRoutes.ts @@ -26,12 +26,14 @@ export default defineCollection({ target: 'uiRoutes', sourceKey: 'key', foreignKey: 'parentKey', + constraints: true, }, { type: 'belongsTo', name: 'uiSchema', target: 'uiSchemas', - foreignKey: 'uiSchemaUid' + foreignKey: 'uiSchemaUid', + constraints: true, }, { type: 'json', diff --git a/packages/plugins/users/src/server.ts b/packages/plugins/users/src/server.ts index 863669c13..53c02caa0 100644 --- a/packages/plugins/users/src/server.ts +++ b/packages/plugins/users/src/server.ts @@ -63,8 +63,7 @@ export default class UsersPlugin extends Plugin { dataIndex: 'state.currentUser.id', createOnly: true, visible: true, - onDelete: 'SET NULL', - onUpdate: 'CASCADE', + index: true, }); collection.setField('createdBy', { type: 'belongsTo', @@ -79,8 +78,7 @@ export default class UsersPlugin extends Plugin { dataType: 'integer', dataIndex: 'state.currentUser.id', visible: true, - onDelete: 'SET NULL', - onUpdate: 'CASCADE', + index: true, }); collection.setField('updatedBy', { type: 'belongsTo',