feat: field sort plugin in mssql

This commit is contained in:
aaaaaajie 2025-03-30 21:58:42 +08:00
parent 09eda5ffdc
commit d2ff34e17e
5 changed files with 140 additions and 107 deletions

View File

@ -27,6 +27,10 @@ describe('sort action', () => {
return api.destroy();
});
function parseSortValue(value) {
return api.db.options.dialect === 'mssql' ? value.toString() : value;
}
describe('associations', () => {
let UserCollection: Collection;
@ -204,19 +208,19 @@ describe('sort action', () => {
data: [
{
title: 't2',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't3',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't1',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't4',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -239,19 +243,19 @@ describe('sort action', () => {
data: [
{
title: 't3',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't1',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't2',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't4',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -274,19 +278,19 @@ describe('sort action', () => {
data: [
{
title: 't2',
sort2: 1,
sort2: parseSortValue(1),
},
{
title: 't3',
sort2: 2,
sort2: parseSortValue(2),
},
{
title: 't1',
sort2: 3,
sort2: parseSortValue(3),
},
{
title: 't4',
sort2: 4,
sort2: parseSortValue(4),
},
],
});
@ -308,19 +312,19 @@ describe('sort action', () => {
data: [
{
title: 't3',
sort: 0,
sort: parseSortValue(0),
},
{
title: 't1',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't2',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't4',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -492,15 +496,15 @@ describe('sort action', () => {
data: [
{
title: 't12',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't13',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't14',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -517,23 +521,23 @@ describe('sort action', () => {
data: [
{
title: 't21',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't11',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't22',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't23',
sort: 4,
sort: parseSortValue(4),
},
{
title: 't24',
sort: 5,
sort: parseSortValue(5),
},
],
});
@ -558,15 +562,15 @@ describe('sort action', () => {
data: [
{
title: 't12',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't13',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't14',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -581,23 +585,23 @@ describe('sort action', () => {
data: [
{
title: 't21',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't22',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't11',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't23',
sort: 4,
sort: parseSortValue(4),
},
{
title: 't24',
sort: 5,
sort: parseSortValue(5),
},
],
});
@ -619,23 +623,23 @@ describe('sort action', () => {
data: [
{
title: 't11',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't22',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't12',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't13',
sort: 4,
sort: parseSortValue(4),
},
{
title: 't14',
sort: 5,
sort: parseSortValue(5),
},
],
});
@ -650,15 +654,15 @@ describe('sort action', () => {
data: [
{
title: 't21',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't23',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't24',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -681,23 +685,23 @@ describe('sort action', () => {
data: [
{
title: 't11',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't12',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't22',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't13',
sort: 4,
sort: parseSortValue(4),
},
{
title: 't14',
sort: 5,
sort: parseSortValue(5),
},
],
});
@ -712,15 +716,15 @@ describe('sort action', () => {
data: [
{
title: 't21',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't23',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't24',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -747,15 +751,15 @@ describe('sort action', () => {
data: [
{
title: 't12',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't13',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't14',
sort: 4,
sort: parseSortValue(4),
},
],
});
@ -770,23 +774,23 @@ describe('sort action', () => {
data: [
{
title: 't21',
sort: 1,
sort: parseSortValue(1),
},
{
title: 't22',
sort: 2,
sort: parseSortValue(2),
},
{
title: 't23',
sort: 3,
sort: parseSortValue(3),
},
{
title: 't24',
sort: 4,
sort: parseSortValue(4),
},
{
title: 't11',
sort: 5,
sort: parseSortValue(5),
},
],
});

View File

@ -30,6 +30,10 @@ describe('sort collections', () => {
await app.destroy();
});
function parseSortValue(value) {
return db.options.dialect === 'mssql' ? value.toString() : value;
}
describe('sort collection', () => {
beforeEach(async () => {
Post = app.db.collection({
@ -68,7 +72,7 @@ describe('sort collections', () => {
await db.sync();
const instance = await model.create();
expect(model.rawAttributes['sort']).toBeDefined();
expect(instance.get('sort')).toBe(1);
expect(instance.get('sort')).toBe(parseSortValue(1));
});
test('sortable=string', async () => {
@ -82,7 +86,7 @@ describe('sort collections', () => {
await db.sync();
const instance = await model.create();
expect(model.rawAttributes['order']).toBeDefined();
expect(instance.get('order')).toBe(1);
expect(instance.get('order')).toBe(parseSortValue(1));
});
test('sortable=object', async () => {
@ -102,10 +106,10 @@ describe('sort collections', () => {
const t3 = await Test.model.create({ status: 'draft' });
const t4 = await Test.model.create({ status: 'draft' });
expect(t1.get('sort')).toBe(1);
expect(t2.get('sort')).toBe(2);
expect(t3.get('sort')).toBe(1);
expect(t4.get('sort')).toBe(2);
expect(t1.get('sort')).toBe(parseSortValue(1));
expect(t2.get('sort')).toBe(parseSortValue(2));
expect(t3.get('sort')).toBe(parseSortValue(1));
expect(t4.get('sort')).toBe(parseSortValue(2));
});
test('forward insert', async () => {
@ -133,11 +137,11 @@ describe('sort collections', () => {
});
expect(results).toEqual([
{ title: 't1', sort: 1 },
{ title: 't3', sort: 2 },
{ title: 't4', sort: 3 },
{ title: 't2', sort: 4 },
{ title: 't5', sort: 5 },
{ title: 't1', sort: parseSortValue(1) },
{ title: 't3', sort: parseSortValue(2) },
{ title: 't4', sort: parseSortValue(3) },
{ title: 't2', sort: parseSortValue(4) },
{ title: 't5', sort: parseSortValue(5) },
]);
});
@ -166,11 +170,11 @@ describe('sort collections', () => {
});
expect(results).toEqual([
{ title: 't1', sort: 1 },
{ title: 't4', sort: 2 },
{ title: 't2', sort: 3 },
{ title: 't3', sort: 4 },
{ title: 't5', sort: 5 },
{ title: 't1', sort: parseSortValue(1) },
{ title: 't4', sort: parseSortValue(2) },
{ title: 't2', sort: parseSortValue(3) },
{ title: 't3', sort: parseSortValue(4) },
{ title: 't5', sort: parseSortValue(5) },
]);
});
});
@ -245,11 +249,11 @@ describe('sort collections', () => {
});
expect(results).toEqual([
{ title: 's1:t1', sort: 1 },
{ title: 's1:t3', sort: 2 },
{ title: 's1:t4', sort: 3 },
{ title: 's1:t2', sort: 4 },
{ title: 's1:t5', sort: 5 },
{ title: 's1:t1', sort: parseSortValue(1) },
{ title: 's1:t3', sort: parseSortValue(2) },
{ title: 's1:t4', sort: parseSortValue(3) },
{ title: 's1:t2', sort: parseSortValue(4) },
{ title: 's1:t5', sort: parseSortValue(5) },
]);
const s2results = (
@ -264,11 +268,11 @@ describe('sort collections', () => {
});
expect(s2results).toEqual([
{ title: 's2:t1', sort: 1 },
{ title: 's2:t2', sort: 2 },
{ title: 's2:t3', sort: 3 },
{ title: 's2:t4', sort: 4 },
{ title: 's2:t5', sort: 5 },
{ title: 's2:t1', sort: parseSortValue(1) },
{ title: 's2:t2', sort: parseSortValue(2) },
{ title: 's2:t3', sort: parseSortValue(3) },
{ title: 's2:t4', sort: parseSortValue(4) },
{ title: 's2:t5', sort: parseSortValue(5) },
]);
});
@ -319,12 +323,12 @@ describe('sort collections', () => {
});
expect(results).toEqual([
{ title: 's2:t1', sort: 1 },
{ title: 's2:t2', sort: 2 },
{ title: 's1:t1', sort: 3 },
{ title: 's2:t3', sort: 4 },
{ title: 's2:t4', sort: 5 },
{ title: 's2:t5', sort: 6 },
{ title: 's2:t1', sort: parseSortValue(1) },
{ title: 's2:t2', sort: parseSortValue(2) },
{ title: 's1:t1', sort: parseSortValue(3) },
{ title: 's2:t3', sort: parseSortValue(4) },
{ title: 's2:t4', sort: parseSortValue(5) },
{ title: 's2:t5', sort: parseSortValue(6) },
]);
});
});

View File

@ -48,6 +48,10 @@ describe('sort field', () => {
await app.destroy();
});
function parseSortValue(value) {
return db.options.dialect === 'mssql' ? value.toString() : value;
}
describe('main data source', () => {
it('should init with camelCase scope key', async () => {
const Test = db.collection({
@ -213,9 +217,9 @@ describe('sort field', () => {
const records = await Test.repository.find({});
const r3 = records.find((r) => r.get('name') === 'r3');
expect(r3.get('sort')).toBe(2);
expect(r3.get('sort')).toBe(parseSortValue(2));
const r5 = records.find((r) => r.get('name') === 'r5');
expect(r5.get('sort')).toBe(1);
expect(r5.get('sort')).toBe(parseSortValue(1));
});
it('should init sorted value by createdAt when primaryKey not exists', async () => {
@ -249,11 +253,11 @@ describe('sort field', () => {
await db.sync();
const test1 = await Test.model.create<any>();
expect(test1.sort).toBe(1);
expect(test1.sort).toBe(parseSortValue(1));
const test2 = await Test.model.create<any>();
expect(test2.sort).toBe(2);
expect(test2.sort).toBe(parseSortValue(2));
const test3 = await Test.model.create<any>();
expect(test3.sort).toBe(3);
expect(test3.sort).toBe(parseSortValue(3));
});
it('should init sort value on data already exits', async () => {
@ -292,7 +296,9 @@ describe('sort field', () => {
const items = await db.getRepository('tests').find({
order: ['id'],
});
expect(items.map((item) => item.get('sort'))).toEqual([1, 2, 3]);
expect(items[0].get('sort')).toBe(parseSortValue(1));
expect(items[1].get('sort')).toBe(parseSortValue(2));
expect(items[2].get('sort')).toBe(parseSortValue(3));
});
test.skip('simultaneously create ', async () => {
@ -311,7 +317,7 @@ describe('sort field', () => {
await Promise.all(promise);
const tests = await Test.model.findAll();
const sortValues = tests.map((t) => t.get('sort')).sort();
expect(sortValues).toEqual([1, 2, 3]);
expect(sortValues).toEqual(db.options.dialect === 'mssql' ? ['1', '2', '3'] : [1, 2, 3]);
});
it('skip if sort value not empty', async () => {
@ -321,11 +327,11 @@ describe('sort field', () => {
});
await db.sync();
const test1 = await Test.model.create<any>({ sort: 3 });
expect(test1.sort).toBe(3);
expect(test1.sort).toBe(parseSortValue(3));
const test2 = await Test.model.create<any>();
expect(test2.sort).toBe(4);
expect(test2.sort).toBe(parseSortValue(4));
const test3 = await Test.model.create<any>();
expect(test3.sort).toBe(5);
expect(test3.sort).toBe(parseSortValue(5));
});
it('scopeKey', async () => {
@ -343,16 +349,16 @@ describe('sort field', () => {
const t3 = await Test.model.create({ status: 'draft' });
const t4 = await Test.model.create({ status: 'draft' });
expect(t1.get('sort')).toBe(1);
expect(t2.get('sort')).toBe(2);
expect(t3.get('sort')).toBe(1);
expect(t4.get('sort')).toBe(2);
expect(t1.get('sort')).toBe(parseSortValue(1));
expect(t2.get('sort')).toBe(parseSortValue(2));
expect(t3.get('sort')).toBe(parseSortValue(1));
expect(t4.get('sort')).toBe(parseSortValue(2));
t1.set('status', 'draft');
await t1.save();
await t1.reload();
expect(t1.get('sort')).toBe(3);
expect(t1.get('sort')).toBe(parseSortValue(3));
});
});
@ -374,8 +380,8 @@ describe('sort field', () => {
const p2 = await anotherDB.getRepository('posts').create({
values: { title: 'p2' },
});
expect(p1.sort).toBe(1);
expect(p2.sort).toBe(2);
expect(p1.sort).toBe(parseSortValue(1));
expect(p2.sort).toBe(parseSortValue(2));
});
});
});

View File

@ -132,8 +132,8 @@ export class SortableCollection {
async sameScopeMove(sourceInstance: Model, targetInstance: Model, options: MoveOptions) {
const fieldName = this.field.get('name');
const sourceSort = sourceInstance.get(fieldName);
let targetSort = targetInstance.get(fieldName);
const sourceSort = Number(sourceInstance.get(fieldName));
let targetSort = Number(targetInstance.get(fieldName));
if (options.insertAfter) {
targetSort = targetSort + 1;

View File

@ -171,6 +171,25 @@ export class SortField extends Field {
${whereClause}
) AS ordered_table ON ${this.collection.quotedTableName()}.${quotedOrderField} = ordered_table.${quotedOrderField}
SET ${this.collection.quotedTableName()}.${sortColumnName} = ordered_table.new_sequence_number;
`;
} else if (this.collection.db.inDialect('mssql')) {
// TODO: This MSSQL support is intended for external data sources
// Since the core database doesn't support MSSQL, this logic needs to be implemented through an extension mechanism
// Potential solutions:
// 1. Abstract database dialect-specific logic into separate modules
// 2. Consider implementing a dialect adapter pattern for better extensibility
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()}
${whereClause}
)
UPDATE t
SET ${sortColumnName} = ot.new_sequence_number
FROM ${this.collection.quotedTableName()} t
INNER JOIN ordered_table ot ON t.${quotedOrderField} = ot.${quotedOrderField};
`;
}
await this.collection.db.sequelize.query(sql, {