feat(database): append default sort options into find (#4231)

* chore: upgrade vitest

* feat(database): append default sort options into find

* chore: test

* chore: test

* fix: test

* fix: test

* fix: test

* fix: test

* fix: test
This commit is contained in:
ChengLei Shao 2024-05-06 21:03:30 +08:00 committed by GitHub
parent 3651c6ccca
commit 7545d05a3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 70 additions and 9 deletions

View File

@ -80,7 +80,7 @@ describe('option parser', () => {
const params = parser.toSequelizeParams(); const params = parser.toSequelizeParams();
expect(params).toEqual({ expect(params).toMatchObject({
attributes: ['id', 'name'], attributes: ['id', 'name'],
include: [ include: [
{ {

View File

@ -24,7 +24,46 @@ describe('find with associations', () => {
await db.close(); await db.close();
}); });
// 关系数据分页测试 it('should append primary key to sort option', async () => {
const User = db.collection({
name: 'users',
fields: [
{ name: 'name', type: 'string' },
{
name: 'age',
type: 'integer',
},
],
});
await db.sync();
await User.repository.create({
values: [
{
name: 'user1',
age: '10',
},
{
name: 'user2',
age: '10',
},
{
name: 'user3',
age: '10',
},
],
});
const records = await User.repository.find({
sort: ['age'],
});
expect(records[0].get('name')).toBe('user1');
expect(records[1].get('name')).toBe('user2');
expect(records[2].get('name')).toBe('user3');
});
it('should filter with associations by pagination', async () => { it('should filter with associations by pagination', async () => {
const Org = db.collection({ const Org = db.collection({
name: 'organizations', name: 'organizations',

View File

@ -108,18 +108,29 @@ export class OptionsParser {
*/ */
protected parseSort(filterParams) { protected parseSort(filterParams) {
let sort = this.options?.sort || []; let sort = this.options?.sort || [];
if (typeof sort === 'string') { if (typeof sort === 'string') {
sort = sort.split(','); sort = sort.split(',');
} }
const primaryKeyField = this.model.primaryKeyAttribute;
if (primaryKeyField && !this.options?.group) {
if (!sort.includes(primaryKeyField)) {
sort.push(primaryKeyField);
}
}
const orderParams = []; const orderParams = [];
for (const sortKey of sort) { for (const sortKey of sort) {
let direction = sortKey.startsWith('-') ? 'DESC' : 'ASC'; let direction = sortKey.startsWith('-') ? 'DESC' : 'ASC';
const sortField: Array<any> = sortKey.replace('-', '').split('.'); const sortField: Array<any> = sortKey.startsWith('-') ? sortKey.replace('-', '').split('.') : sortKey.split('.');
if (this.database.inDialect('postgres', 'sqlite')) { if (this.database.inDialect('postgres', 'sqlite')) {
direction = `${direction} NULLS LAST`; direction = `${direction} NULLS LAST`;
} }
// handle sort by association // handle sort by association
if (sortField.length > 1) { if (sortField.length > 1) {
let associationModel = this.model; let associationModel = this.model;

View File

@ -339,7 +339,10 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
}); });
options.optionsTransformer?.(queryOptions); options.optionsTransformer?.(queryOptions);
const hasAssociationFilter = () => {
delete queryOptions.order;
const hasAssociationFilter = (() => {
if (queryOptions.include && queryOptions.include.length > 0) { if (queryOptions.include && queryOptions.include.length > 0) {
const filterInclude = queryOptions.include.filter((include) => { const filterInclude = queryOptions.include.filter((include) => {
return ( return (
@ -350,9 +353,9 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
return filterInclude.length > 0; return filterInclude.length > 0;
} }
return false; return false;
}; })();
if (hasAssociationFilter()) { if (hasAssociationFilter) {
const primaryKeyField = this.model.primaryKeyAttribute; const primaryKeyField = this.model.primaryKeyAttribute;
const queryInterface = this.database.sequelize.getQueryInterface(); const queryInterface = this.database.sequelize.getQueryInterface();

View File

@ -7,17 +7,18 @@
* For more information, please refer to: https://www.nocobase.com/agreement. * For more information, please refer to: https://www.nocobase.com/agreement.
*/ */
import { MockServer, createMockServer } from '@nocobase/test'; import { createMockServer, MockServer } from '@nocobase/test';
import compose from 'koa-compose'; import compose from 'koa-compose';
import { vi } from 'vitest'; import { vi } from 'vitest';
import { import {
cacheMiddleware, cacheMiddleware,
checkPermission,
parseBuilder, parseBuilder,
parseFieldAndAssociations, parseFieldAndAssociations,
checkPermission,
postProcess,
parseVariables, parseVariables,
postProcess,
} from '../actions/query'; } from '../actions/query';
const formatter = await import('../actions/formatter'); const formatter = await import('../actions/formatter');
describe('query', () => { describe('query', () => {
@ -25,6 +26,9 @@ describe('query', () => {
const sequelize = { const sequelize = {
fn: vi.fn().mockImplementation((fn: string, field: string) => [fn, field]), fn: vi.fn().mockImplementation((fn: string, field: string) => [fn, field]),
col: vi.fn().mockImplementation((field: string) => field), col: vi.fn().mockImplementation((field: string) => field),
getDialect() {
return false;
},
}; };
let ctx: any; let ctx: any;
let app: MockServer; let app: MockServer;
@ -307,15 +311,19 @@ describe('query', () => {
ctx.body = value; ctx.body = value;
await next(); await next();
}); });
class MockCache { class MockCache {
map: Map<string, any> = new Map(); map: Map<string, any> = new Map();
get(key: string) { get(key: string) {
return this.map.get(key); return this.map.get(key);
} }
set(key: string, value: any) { set(key: string, value: any) {
this.map.set(key, value); this.map.set(key, value);
} }
} }
let ctx: any; let ctx: any;
beforeEach(() => { beforeEach(() => {
const cache = new MockCache(); const cache = new MockCache();