From 55c7bb5cf58ddf90fd3351cc6a7574c24152eca2 Mon Sep 17 00:00:00 2001 From: ChengLei Shao Date: Sat, 8 Apr 2023 22:08:32 +0800 Subject: [PATCH] fix: sort field init performance (#1675) * fix: sort field init performance * chore: test table prefix * fix: mysql compability --- .../src/__tests__/fields/sort-field.test.ts | 44 +++++++++ .../core/database/src/fields/sort-field.ts | 98 ++++++++++++------- 2 files changed, 107 insertions(+), 35 deletions(-) diff --git a/packages/core/database/src/__tests__/fields/sort-field.test.ts b/packages/core/database/src/__tests__/fields/sort-field.test.ts index a30c4e1d0..90851b405 100644 --- a/packages/core/database/src/__tests__/fields/sort-field.test.ts +++ b/packages/core/database/src/__tests__/fields/sort-field.test.ts @@ -17,6 +17,50 @@ describe('string field', () => { await db.close(); }); + it('should init sorted value with thousand records', async () => { + const Test = db.collection({ + name: 'tests', + fields: [ + { + type: 'string', + name: 'name', + }, + { + type: 'string', + name: 'group', + }, + ], + }); + + await db.sync(); + + await Test.model.bulkCreate( + (() => { + const values = []; + for (let i = 0; i < 100000; i++) { + values.push({ + group: 'a', + name: `r${i}`, + }); + + values.push({ + group: 'b', + name: `r${i}`, + }); + } + return values; + })(), + ); + + Test.setField('sort', { type: 'sort', scopeKey: 'group' }); + + const begin = Date.now(); + await db.sync(); + const end = Date.now(); + // log time cost as milliseconds + console.log(end - begin); + }); + it('should init sorted value with scopeKey', async () => { const Test = db.collection({ name: 'tests', diff --git a/packages/core/database/src/fields/sort-field.ts b/packages/core/database/src/fields/sort-field.ts index c59fa8d52..3dadbddc0 100644 --- a/packages/core/database/src/fields/sort-field.ts +++ b/packages/core/database/src/fields/sort-field.ts @@ -42,7 +42,19 @@ export class SortField extends Field { }; initRecordsSortValue = async ({ transaction }) => { - const doInit = async (scopeKey = null, scopeValue = null) => { + const orderField = (() => { + const model = this.collection.model; + if (model.primaryKeyAttribute) { + return model.primaryKeyAttribute; + } + if (model.rawAttributes['createdAt']) { + return model.rawAttributes['createdAt'].field; + } + + throw new Error(`can not find order key for collection ${this.collection.name}`); + })(); + + const needInit = async (scopeKey = null, scopeValue = null) => { const filter = {}; if (scopeKey && scopeValue) { filter[scopeKey] = scopeValue; @@ -61,44 +73,53 @@ export class SortField extends Field { transaction, }); - const orderKey = (() => { - const model = this.collection.model; - if (model.primaryKeyAttribute) { - return model.primaryKeyAttribute; - } - if (model.rawAttributes['createdAt']) { - return 'createdAt'; + return emptyCount === totalCount && emptyCount > 0; + }; + + const doInit = async (scopeKey = null, scopeValue = null) => { + const queryInterface = this.collection.db.sequelize.getQueryInterface(); + + const quotedOrderField = queryInterface.quoteIdentifier(orderField); + + const sql = ` + WITH ordered_table AS ( + SELECT *, ROW_NUMBER() OVER (${ + scopeKey ? `PARTITION BY ${queryInterface.quoteIdentifier(scopeKey)}` : '' + } ORDER BY ${quotedOrderField}) AS new_sequence_number + FROM ${this.collection.quotedTableName()} + ${ + scopeKey + ? `WHERE ${queryInterface.quoteIdentifier(scopeKey)} IN (${scopeValue.map((v) => `'${v}'`).join(',')})` + : '' + } + ) + ${ + this.collection.db.inDialect('mysql') + ? ` + UPDATE ${this.collection.quotedTableName()}, ordered_table + SET ${this.collection.quotedTableName()}.${this.name} = ordered_table.new_sequence_number + WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField} + ${scopeKey ? `AND ${this.collection.quotedTableName()}.${scopeKey} = ordered_table.${scopeKey}` : ''} + ` + : ` + UPDATE ${this.collection.quotedTableName()} + SET ${queryInterface.quoteIdentifier(this.name)} = ordered_table.new_sequence_number + FROM ordered_table + WHERE ${this.collection.quotedTableName()}.${quotedOrderField} = ${queryInterface.quoteIdentifier( + 'ordered_table', + )}.${quotedOrderField}; + ` } - throw new Error(`can not find order key for collection ${this.collection.name}`); - })(); + `; - if (emptyCount === totalCount && emptyCount > 0) { - const records = await this.collection.repository.find({ - order: [orderKey], - filter, - transaction, - }); - - let start = 1; - for (const record of records) { - await record.update( - { - sort: start, - }, - { - hooks: false, - transaction, - silent: true, - }, - ); - - start += 1; - } - } + await this.collection.db.sequelize.query(sql, { + transaction, + }); }; const scopeKey = this.options.scopeKey; + if (scopeKey) { const groups = await this.collection.repository.find({ attributes: [scopeKey], @@ -106,10 +127,17 @@ export class SortField extends Field { raw: true, }); + const needInitGroups = []; for (const group of groups) { - await doInit(scopeKey, group[scopeKey]); + if (await needInit(scopeKey, group[scopeKey])) { + needInitGroups.push(group['group']); + } } - } else { + + if (needInitGroups.length > 0) { + await doInit(scopeKey, needInitGroups); + } + } else if (await needInit()) { await doInit(); } };