Backfill position when not input (#5696)

- refactor record position factory and record position query factory
- override position if not present during createMany

To avoid overriding the same positions for all data in createMany, the
logic is:
- if inserted last, use last position + arg index + 1
- if inserted first, use first position - arg index - 1
This commit is contained in:
Thomas Trompette
2024-06-03 15:18:01 +02:00
committed by GitHub
parent a6b8beed68
commit 2886664b62
7 changed files with 298 additions and 122 deletions

View File

@ -16,74 +16,66 @@ describe('RecordPositionQueryFactory', () => {
});
describe('create', () => {
describe('createForGet', () => {
it('should return a string with the position when positionValue is first', async () => {
const positionValue = 'first';
const result = await factory.create(
RecordPositionQueryType.GET,
positionValue,
objectMetadataItem,
dataSourceSchema,
);
expect(result).toEqual(
`SELECT position FROM workspace_test."company"
WHERE "position" IS NOT NULL ORDER BY "position" ASC LIMIT 1`,
);
});
it('should return a string with the position when positionValue is last', async () => {
const positionValue = 'last';
const result = await factory.create(
RecordPositionQueryType.GET,
positionValue,
objectMetadataItem,
dataSourceSchema,
);
expect(result).toEqual(
`SELECT position FROM workspace_test."company"
WHERE "position" IS NOT NULL ORDER BY "position" DESC LIMIT 1`,
);
});
});
it('should return a string with the position when positionValue is a number', async () => {
it('should return query and params for FIND_BY_POSITION', async () => {
const positionValue = 1;
try {
await factory.create(
RecordPositionQueryType.GET,
positionValue,
objectMetadataItem,
dataSourceSchema,
);
} catch (error) {
expect(error.message).toEqual(
'RecordPositionQueryType.GET requires positionValue to be a number',
);
}
});
});
describe('createForUpdate', () => {
it('should return a string when RecordPositionQueryType is UPDATE', async () => {
const positionValue = 1;
const result = await factory.create(
RecordPositionQueryType.UPDATE,
positionValue,
const queryType = RecordPositionQueryType.FIND_BY_POSITION;
const [query, params] = await factory.create(
{ positionValue, recordPositionQueryType: queryType },
objectMetadataItem,
dataSourceSchema,
);
expect(result).toEqual(
`UPDATE workspace_test."company"
expect(query).toEqual(
`SELECT position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
WHERE "position" = $1`,
);
expect(params).toEqual([positionValue]);
});
it('should return query and params for FIND_MIN_POSITION', async () => {
const queryType = RecordPositionQueryType.FIND_MIN_POSITION;
const [query, params] = await factory.create(
{ recordPositionQueryType: queryType },
objectMetadataItem,
dataSourceSchema,
);
expect(query).toEqual(
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
);
expect(params).toEqual([]);
});
it('should return query and params for FIND_MAX_POSITION', async () => {
const queryType = RecordPositionQueryType.FIND_MAX_POSITION;
const [query, params] = await factory.create(
{ recordPositionQueryType: queryType },
objectMetadataItem,
dataSourceSchema,
);
expect(query).toEqual(
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${objectMetadataItem.nameSingular}"`,
);
expect(params).toEqual([]);
});
it('should return query and params for UPDATE_POSITION', async () => {
const positionValue = 1;
const recordId = '1';
const queryType = RecordPositionQueryType.UPDATE_POSITION;
const [query, params] = await factory.create(
{ positionValue, recordId, recordPositionQueryType: queryType },
objectMetadataItem,
dataSourceSchema,
);
expect(query).toEqual(
`UPDATE ${dataSourceSchema}."${objectMetadataItem.nameSingular}"
SET "position" = $1
WHERE "id" = $2`,
);
expect(params).toEqual([positionValue, recordId]);
});
});
});

View File

@ -1,54 +1,119 @@
import { Injectable } from '@nestjs/common';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
export enum RecordPositionQueryType {
GET = 'GET',
UPDATE = 'UPDATE',
FIND_MIN_POSITION = 'FIND_MIN_POSITION',
FIND_MAX_POSITION = 'FIND_MAX_POSITION',
FIND_BY_POSITION = 'FIND_BY_POSITION',
UPDATE_POSITION = 'UPDATE_POSITION',
}
type FindByPositionQueryArgs = {
positionValue: number;
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION;
};
type FindMinPositionQueryArgs = {
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION;
};
type FindMaxPositionQueryArgs = {
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION;
};
type UpdatePositionQueryArgs = {
recordId: string;
positionValue: number;
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION;
};
type RecordPositionQuery = string;
type RecordPositionQueryParams = any[];
export type RecordPositionQueryArgs =
| FindByPositionQueryArgs
| FindMinPositionQueryArgs
| FindMaxPositionQueryArgs
| UpdatePositionQueryArgs;
@Injectable()
export class RecordPositionQueryFactory {
async create(
recordPositionQueryType: RecordPositionQueryType,
positionValue: 'first' | 'last' | number,
create(
recordPositionQueryArgs: RecordPositionQueryArgs,
objectMetadata: { isCustom: boolean; nameSingular: string },
dataSourceSchema: string,
): Promise<string> {
const name =
(objectMetadata.isCustom ? '_' : '') + objectMetadata.nameSingular;
): [RecordPositionQuery, RecordPositionQueryParams] {
const name = computeTableName(
objectMetadata.nameSingular,
objectMetadata.isCustom,
);
switch (recordPositionQueryType) {
case RecordPositionQueryType.GET:
if (typeof positionValue === 'number') {
throw new Error(
'RecordPositionQueryType.GET requires positionValue to be a number',
);
}
return this.createForGet(positionValue, name, dataSourceSchema);
case RecordPositionQueryType.UPDATE:
return this.createForUpdate(name, dataSourceSchema);
switch (recordPositionQueryArgs.recordPositionQueryType) {
case RecordPositionQueryType.FIND_BY_POSITION:
return this.buildFindByPositionQuery(
recordPositionQueryArgs satisfies FindByPositionQueryArgs,
name,
dataSourceSchema,
);
case RecordPositionQueryType.FIND_MIN_POSITION:
return this.buildFindMinPositionQuery(name, dataSourceSchema);
case RecordPositionQueryType.FIND_MAX_POSITION:
return this.buildFindMaxPositionQuery(name, dataSourceSchema);
case RecordPositionQueryType.UPDATE_POSITION:
return this.buildUpdatePositionQuery(
recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
name,
dataSourceSchema,
);
default:
throw new Error('Invalid RecordPositionQueryType');
}
}
private async createForGet(
positionValue: 'first' | 'last',
private buildFindByPositionQuery(
{ positionValue }: FindByPositionQueryArgs,
name: string,
dataSourceSchema: string,
): Promise<string> {
const orderByDirection = positionValue === 'first' ? 'ASC' : 'DESC';
return `SELECT position FROM ${dataSourceSchema}."${name}"
WHERE "position" IS NOT NULL ORDER BY "position" ${orderByDirection} LIMIT 1`;
): [RecordPositionQuery, RecordPositionQueryParams] {
return [
`SELECT position FROM ${dataSourceSchema}."${name}"
WHERE "position" = $1`,
[positionValue],
];
}
private async createForUpdate(
private buildFindMaxPositionQuery(
name: string,
dataSourceSchema: string,
): Promise<string> {
return `UPDATE ${dataSourceSchema}."${name}"
): [RecordPositionQuery, RecordPositionQueryParams] {
return [
`SELECT MAX(position) as position FROM ${dataSourceSchema}."${name}"`,
[],
];
}
private buildFindMinPositionQuery(
name: string,
dataSourceSchema: string,
): [RecordPositionQuery, RecordPositionQueryParams] {
return [
`SELECT MIN(position) as position FROM ${dataSourceSchema}."${name}"`,
[],
];
}
private buildUpdatePositionQuery(
{ recordId, positionValue }: UpdatePositionQueryArgs,
name: string,
dataSourceSchema: string,
): [RecordPositionQuery, RecordPositionQueryParams] {
return [
`UPDATE ${dataSourceSchema}."${name}"
SET "position" = $1
WHERE "id" = $2`;
WHERE "id" = $2`,
[positionValue, recordId],
];
}
}