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:
@ -16,74 +16,66 @@ describe('RecordPositionQueryFactory', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('create', () => {
|
describe('create', () => {
|
||||||
describe('createForGet', () => {
|
it('should return query and params for FIND_BY_POSITION', async () => {
|
||||||
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 () => {
|
|
||||||
const positionValue = 1;
|
const positionValue = 1;
|
||||||
|
const queryType = RecordPositionQueryType.FIND_BY_POSITION;
|
||||||
try {
|
const [query, params] = await factory.create(
|
||||||
await factory.create(
|
{ positionValue, recordPositionQueryType: queryType },
|
||||||
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,
|
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
dataSourceSchema,
|
dataSourceSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual(
|
expect(query).toEqual(
|
||||||
`UPDATE workspace_test."company"
|
`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
|
SET "position" = $1
|
||||||
WHERE "id" = $2`,
|
WHERE "id" = $2`,
|
||||||
);
|
);
|
||||||
|
expect(params).toEqual([positionValue, recordId]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,54 +1,119 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
export enum RecordPositionQueryType {
|
export enum RecordPositionQueryType {
|
||||||
GET = 'GET',
|
FIND_MIN_POSITION = 'FIND_MIN_POSITION',
|
||||||
UPDATE = 'UPDATE',
|
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()
|
@Injectable()
|
||||||
export class RecordPositionQueryFactory {
|
export class RecordPositionQueryFactory {
|
||||||
async create(
|
create(
|
||||||
recordPositionQueryType: RecordPositionQueryType,
|
recordPositionQueryArgs: RecordPositionQueryArgs,
|
||||||
positionValue: 'first' | 'last' | number,
|
|
||||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||||
dataSourceSchema: string,
|
dataSourceSchema: string,
|
||||||
): Promise<string> {
|
): [RecordPositionQuery, RecordPositionQueryParams] {
|
||||||
const name =
|
const name = computeTableName(
|
||||||
(objectMetadata.isCustom ? '_' : '') + objectMetadata.nameSingular;
|
objectMetadata.nameSingular,
|
||||||
|
objectMetadata.isCustom,
|
||||||
|
);
|
||||||
|
|
||||||
switch (recordPositionQueryType) {
|
switch (recordPositionQueryArgs.recordPositionQueryType) {
|
||||||
case RecordPositionQueryType.GET:
|
case RecordPositionQueryType.FIND_BY_POSITION:
|
||||||
if (typeof positionValue === 'number') {
|
return this.buildFindByPositionQuery(
|
||||||
throw new Error(
|
recordPositionQueryArgs satisfies FindByPositionQueryArgs,
|
||||||
'RecordPositionQueryType.GET requires positionValue to be a number',
|
name,
|
||||||
);
|
dataSourceSchema,
|
||||||
}
|
);
|
||||||
|
case RecordPositionQueryType.FIND_MIN_POSITION:
|
||||||
return this.createForGet(positionValue, name, dataSourceSchema);
|
return this.buildFindMinPositionQuery(name, dataSourceSchema);
|
||||||
case RecordPositionQueryType.UPDATE:
|
case RecordPositionQueryType.FIND_MAX_POSITION:
|
||||||
return this.createForUpdate(name, dataSourceSchema);
|
return this.buildFindMaxPositionQuery(name, dataSourceSchema);
|
||||||
|
case RecordPositionQueryType.UPDATE_POSITION:
|
||||||
|
return this.buildUpdatePositionQuery(
|
||||||
|
recordPositionQueryArgs satisfies UpdatePositionQueryArgs,
|
||||||
|
name,
|
||||||
|
dataSourceSchema,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid RecordPositionQueryType');
|
throw new Error('Invalid RecordPositionQueryType');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createForGet(
|
private buildFindByPositionQuery(
|
||||||
positionValue: 'first' | 'last',
|
{ positionValue }: FindByPositionQueryArgs,
|
||||||
name: string,
|
name: string,
|
||||||
dataSourceSchema: string,
|
dataSourceSchema: string,
|
||||||
): Promise<string> {
|
): [RecordPositionQuery, RecordPositionQueryParams] {
|
||||||
const orderByDirection = positionValue === 'first' ? 'ASC' : 'DESC';
|
return [
|
||||||
|
`SELECT position FROM ${dataSourceSchema}."${name}"
|
||||||
return `SELECT position FROM ${dataSourceSchema}."${name}"
|
WHERE "position" = $1`,
|
||||||
WHERE "position" IS NOT NULL ORDER BY "position" ${orderByDirection} LIMIT 1`;
|
[positionValue],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createForUpdate(
|
private buildFindMaxPositionQuery(
|
||||||
name: string,
|
name: string,
|
||||||
dataSourceSchema: string,
|
dataSourceSchema: string,
|
||||||
): Promise<string> {
|
): [RecordPositionQuery, RecordPositionQueryParams] {
|
||||||
return `UPDATE ${dataSourceSchema}."${name}"
|
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
|
SET "position" = $1
|
||||||
WHERE "id" = $2`;
|
WHERE "id" = $2`,
|
||||||
|
[positionValue, recordId],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,12 +12,14 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
const recordPositionFactory = {
|
const recordPositionFactory = {
|
||||||
create: jest.fn().mockResolvedValue(2),
|
create: jest.fn().mockResolvedValue(2),
|
||||||
};
|
};
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
const options = {
|
const options = {
|
||||||
fieldMetadataCollection: [
|
fieldMetadataCollection: [
|
||||||
{ name: 'position', type: FieldMetadataType.POSITION },
|
{ name: 'position', type: FieldMetadataType.POSITION },
|
||||||
{ name: 'testNumber', type: FieldMetadataType.NUMBER },
|
{ name: 'testNumber', type: FieldMetadataType.NUMBER },
|
||||||
] as FieldMetadataInterface[],
|
] as FieldMetadataInterface[],
|
||||||
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
objectMetadataItem: { isCustom: true, nameSingular: 'test' },
|
||||||
|
workspaceId,
|
||||||
} as WorkspaceQueryRunnerOptions;
|
} as WorkspaceQueryRunnerOptions;
|
||||||
|
|
||||||
let factory: QueryRunnerArgsFactory;
|
let factory: QueryRunnerArgsFactory;
|
||||||
@ -68,6 +70,36 @@ describe('QueryRunnerArgsFactory', () => {
|
|||||||
ResolverArgsType.CreateMany,
|
ResolverArgsType.CreateMany,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(recordPositionFactory.create).toHaveBeenCalledWith(
|
||||||
|
'last',
|
||||||
|
{ isCustom: true, nameSingular: 'test' },
|
||||||
|
workspaceId,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({
|
||||||
|
id: 'uuid',
|
||||||
|
data: [{ position: 2, testNumber: 1 }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('createMany type should override position if not present', async () => {
|
||||||
|
const args = {
|
||||||
|
id: 'uuid',
|
||||||
|
data: [{ testNumber: '1' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await factory.create(
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
ResolverArgsType.CreateMany,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(recordPositionFactory.create).toHaveBeenCalledWith(
|
||||||
|
'first',
|
||||||
|
{ isCustom: true, nameSingular: 'test' },
|
||||||
|
workspaceId,
|
||||||
|
0,
|
||||||
|
);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
id: 'uuid',
|
id: 'uuid',
|
||||||
data: [{ position: 2, testNumber: 1 }],
|
data: [{ position: 2, testNumber: 1 }],
|
||||||
|
|||||||
@ -9,14 +9,15 @@ describe('RecordPositionFactory', () => {
|
|||||||
create: jest.fn().mockResolvedValue('query'),
|
create: jest.fn().mockResolvedValue('query'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const workspaceDataSourceService = {
|
let workspaceDataSourceService;
|
||||||
getSchemaName: jest.fn().mockReturnValue('schemaName'),
|
|
||||||
executeRawQuery: jest.fn().mockResolvedValue([{ position: 1 }]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let factory: RecordPositionFactory;
|
let factory: RecordPositionFactory;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
workspaceDataSourceService = {
|
||||||
|
getSchemaName: jest.fn().mockReturnValue('schemaName'),
|
||||||
|
executeRawQuery: jest.fn().mockResolvedValue([{ position: 1 }]),
|
||||||
|
};
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
RecordPositionFactory,
|
RecordPositionFactory,
|
||||||
@ -44,10 +45,20 @@ describe('RecordPositionFactory', () => {
|
|||||||
|
|
||||||
it('should return the value when value is a number', async () => {
|
it('should return the value when value is a number', async () => {
|
||||||
const value = 1;
|
const value = 1;
|
||||||
|
|
||||||
|
workspaceDataSourceService.executeRawQuery.mockResolvedValue([]);
|
||||||
|
|
||||||
const result = await factory.create(value, objectMetadata, workspaceId);
|
const result = await factory.create(value, objectMetadata, workspaceId);
|
||||||
|
|
||||||
expect(result).toEqual(value);
|
expect(result).toEqual(value);
|
||||||
});
|
});
|
||||||
|
it('should throw an error when position is not unique', async () => {
|
||||||
|
const value = 1;
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
factory.create(value, objectMetadata, workspaceId),
|
||||||
|
).rejects.toThrow('Position is not unique');
|
||||||
|
});
|
||||||
it('should return the existing position -1 when value is first', async () => {
|
it('should return the existing position -1 when value is first', async () => {
|
||||||
const value = 'first';
|
const value = 'first';
|
||||||
const result = await factory.create(value, objectMetadata, workspaceId);
|
const result = await factory.create(value, objectMetadata, workspaceId);
|
||||||
|
|||||||
@ -16,6 +16,11 @@ import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/fi
|
|||||||
|
|
||||||
import { RecordPositionFactory } from './record-position.factory';
|
import { RecordPositionFactory } from './record-position.factory';
|
||||||
|
|
||||||
|
type ArgPositionBackfillInput = {
|
||||||
|
argIndex?: number;
|
||||||
|
shouldBackfillPosition: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class QueryRunnerArgsFactory {
|
export class QueryRunnerArgsFactory {
|
||||||
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
constructor(private readonly recordPositionFactory: RecordPositionFactory) {}
|
||||||
@ -39,8 +44,11 @@ export class QueryRunnerArgsFactory {
|
|||||||
return {
|
return {
|
||||||
...args,
|
...args,
|
||||||
data: await Promise.all(
|
data: await Promise.all(
|
||||||
(args as CreateManyResolverArgs).data.map((arg) =>
|
(args as CreateManyResolverArgs).data.map((arg, index) =>
|
||||||
this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap),
|
this.overrideDataByFieldMetadata(arg, options, fieldMetadataMap, {
|
||||||
|
argIndex: index,
|
||||||
|
shouldBackfillPosition: true,
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
} satisfies CreateManyResolverArgs;
|
} satisfies CreateManyResolverArgs;
|
||||||
@ -73,6 +81,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
(args as FindDuplicatesResolverArgs).data,
|
(args as FindDuplicatesResolverArgs).data,
|
||||||
options,
|
options,
|
||||||
fieldMetadataMap,
|
fieldMetadataMap,
|
||||||
|
{ shouldBackfillPosition: false },
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
@ -84,11 +93,14 @@ export class QueryRunnerArgsFactory {
|
|||||||
data: Record<string, any> | undefined,
|
data: Record<string, any> | undefined,
|
||||||
options: WorkspaceQueryRunnerOptions,
|
options: WorkspaceQueryRunnerOptions,
|
||||||
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
fieldMetadataMap: Map<string, FieldMetadataInterface>,
|
||||||
|
argPositionBackfillInput: ArgPositionBackfillInput,
|
||||||
) {
|
) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isFieldPositionPresent = false;
|
||||||
|
|
||||||
const createArgPromiseByArgKey = Object.entries(data).map(
|
const createArgPromiseByArgKey = Object.entries(data).map(
|
||||||
async ([key, value]) => {
|
async ([key, value]) => {
|
||||||
const fieldMetadata = fieldMetadataMap.get(key);
|
const fieldMetadata = fieldMetadataMap.get(key);
|
||||||
@ -99,6 +111,8 @@ export class QueryRunnerArgsFactory {
|
|||||||
|
|
||||||
switch (fieldMetadata.type) {
|
switch (fieldMetadata.type) {
|
||||||
case FieldMetadataType.POSITION:
|
case FieldMetadataType.POSITION:
|
||||||
|
isFieldPositionPresent = true;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
key,
|
key,
|
||||||
await this.recordPositionFactory.create(
|
await this.recordPositionFactory.create(
|
||||||
@ -108,6 +122,7 @@ export class QueryRunnerArgsFactory {
|
|||||||
nameSingular: options.objectMetadataItem.nameSingular,
|
nameSingular: options.objectMetadataItem.nameSingular,
|
||||||
},
|
},
|
||||||
options.workspaceId,
|
options.workspaceId,
|
||||||
|
argPositionBackfillInput.argIndex,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
case FieldMetadataType.NUMBER:
|
case FieldMetadataType.NUMBER:
|
||||||
@ -120,6 +135,27 @@ export class QueryRunnerArgsFactory {
|
|||||||
|
|
||||||
const newArgEntries = await Promise.all(createArgPromiseByArgKey);
|
const newArgEntries = await Promise.all(createArgPromiseByArgKey);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isFieldPositionPresent &&
|
||||||
|
argPositionBackfillInput.shouldBackfillPosition
|
||||||
|
) {
|
||||||
|
return Object.fromEntries([
|
||||||
|
...newArgEntries,
|
||||||
|
[
|
||||||
|
'position',
|
||||||
|
await this.recordPositionFactory.create(
|
||||||
|
'first',
|
||||||
|
{
|
||||||
|
isCustom: options.objectMetadataItem.isCustom,
|
||||||
|
nameSingular: options.objectMetadataItem.nameSingular,
|
||||||
|
},
|
||||||
|
options.workspaceId,
|
||||||
|
argPositionBackfillInput.argIndex,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return Object.fromEntries(newArgEntries);
|
return Object.fromEntries(newArgEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { isDefined } from 'class-validator';
|
|||||||
|
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||||
import {
|
import {
|
||||||
|
RecordPositionQueryArgs,
|
||||||
RecordPositionQueryFactory,
|
RecordPositionQueryFactory,
|
||||||
RecordPositionQueryType,
|
RecordPositionQueryType,
|
||||||
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
} from 'src/engine/api/graphql/workspace-query-builder/factories/record-position-query.factory';
|
||||||
@ -19,40 +20,76 @@ export class RecordPositionFactory {
|
|||||||
value: number | 'first' | 'last',
|
value: number | 'first' | 'last',
|
||||||
objectMetadata: { isCustom: boolean; nameSingular: string },
|
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
index = 0,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
if (typeof value === 'number') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataSourceSchema =
|
const dataSourceSchema =
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
const query = await this.recordPositionQueryFactory.create(
|
if (typeof value === 'number') {
|
||||||
RecordPositionQueryType.GET,
|
const recordWithSamePosition = await this.findRecordPosition(
|
||||||
value,
|
{
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_BY_POSITION,
|
||||||
|
positionValue: value,
|
||||||
|
},
|
||||||
|
objectMetadata,
|
||||||
|
dataSourceSchema,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (recordWithSamePosition) {
|
||||||
|
throw new Error('Position is not unique');
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === 'first') {
|
||||||
|
const recordWithMinPosition = await this.findRecordPosition(
|
||||||
|
{
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_MIN_POSITION,
|
||||||
|
},
|
||||||
|
objectMetadata,
|
||||||
|
dataSourceSchema,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return isDefined(recordWithMinPosition?.position)
|
||||||
|
? recordWithMinPosition.position - index - 1
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordWithMaxPosition = await this.findRecordPosition(
|
||||||
|
{
|
||||||
|
recordPositionQueryType: RecordPositionQueryType.FIND_MAX_POSITION,
|
||||||
|
},
|
||||||
|
objectMetadata,
|
||||||
|
dataSourceSchema,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return isDefined(recordWithMaxPosition?.position)
|
||||||
|
? recordWithMaxPosition.position + index + 1
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findRecordPosition(
|
||||||
|
recordPositionQueryArgs: RecordPositionQueryArgs,
|
||||||
|
objectMetadata: { isCustom: boolean; nameSingular: string },
|
||||||
|
dataSourceSchema: string,
|
||||||
|
workspaceId: string,
|
||||||
|
) {
|
||||||
|
const [query, params] = await this.recordPositionQueryFactory.create(
|
||||||
|
recordPositionQueryArgs,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
dataSourceSchema,
|
dataSourceSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If the value was 'first', the first record will be the one with the lowest position
|
|
||||||
// If the value was 'last', the first record will be the one with the highest position
|
|
||||||
const records = await this.workspaceDataSourceService.executeRawQuery(
|
const records = await this.workspaceDataSourceService.executeRawQuery(
|
||||||
query,
|
query,
|
||||||
[],
|
params,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
undefined,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
return records?.[0];
|
||||||
!isDefined(records) ||
|
|
||||||
records.length === 0 ||
|
|
||||||
!isDefined(records[0]?.position)
|
|
||||||
) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value === 'first'
|
|
||||||
? records[0].position - 1
|
|
||||||
: records[0].position + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,16 +31,19 @@ export class RecordPositionBackfillService {
|
|||||||
const dataSourceSchema =
|
const dataSourceSchema =
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
const query = await this.recordPositionQueryFactory.create(
|
const [query, params] = await this.recordPositionQueryFactory.create(
|
||||||
RecordPositionQueryType.UPDATE,
|
{
|
||||||
position,
|
recordPositionQueryType: RecordPositionQueryType.UPDATE_POSITION,
|
||||||
|
recordId,
|
||||||
|
positionValue: position,
|
||||||
|
},
|
||||||
objectMetadata as ObjectMetadataInterface,
|
objectMetadata as ObjectMetadataInterface,
|
||||||
dataSourceSchema,
|
dataSourceSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.workspaceDataSourceService.executeRawQuery(
|
this.workspaceDataSourceService.executeRawQuery(
|
||||||
query,
|
query,
|
||||||
[position, recordId],
|
params,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user