feat(core): Use query builder for upsert fetch and split rows (no-changelog) (#18659)

This commit is contained in:
Daria
2025-08-22 16:45:00 +03:00
committed by GitHub
parent 4a58b4218b
commit 350f84c49f
2 changed files with 92 additions and 37 deletions

View File

@@ -1218,36 +1218,94 @@ describe('dataStore', () => {
});
describe('upsertRows', () => {
it('updates a row if filter matches', async () => {
it('updates rows if filter matches', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
name: 'dataStore',
columns: [
{ name: 'pid', type: 'string' },
{ name: 'fullName', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'age', type: 'number' },
],
});
// Insert initial row
const ids = await dataStoreService.insertRows(dataStoreId, project1.id, [
{ pid: '1995-111a', fullName: 'Alice', age: 30 },
await dataStoreService.insertRows(dataStoreId, project1.id, [
{ pid: '1995-111a', name: 'Alice', age: 30 },
{ pid: '1994-222a', name: 'John', age: 31 },
{ pid: '1993-333a', name: 'Paul', age: 32 },
]);
expect(ids).toEqual([1]);
// ACT
const result = await dataStoreService.upsertRows(dataStoreId, project1.id, {
rows: [{ pid: '1995-111a', fullName: 'Alicia', age: 31 }],
rows: [
{ pid: '1995-111a', name: 'Alicia', age: 31 },
{ pid: '1994-222a', name: 'John', age: 32 },
],
matchFields: ['pid'],
});
// ASSERT
expect(result).toBe(true);
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
const { count, data } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(count).toEqual(1);
expect(data).toEqual([{ fullName: 'Alicia', age: 31, id: 1, pid: '1995-111a' }]);
expect(count).toEqual(3);
expect(data).toEqual(
expect.arrayContaining([
{ pid: '1995-111a', name: 'Alicia', age: 31, id: 1 },
{ pid: '1994-222a', name: 'John', age: 32, id: 2 },
{ pid: '1993-333a', name: 'Paul', age: 32, id: 3 }, // unchanged
]),
);
});
it('works correctly with multiple filters', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
name: 'dataStore',
columns: [
{ name: 'city', type: 'string' },
{ name: 'age', type: 'number' },
{ name: 'isEligible', type: 'boolean' },
],
});
await dataStoreService.insertRows(dataStoreId, project1.id, [
{ city: 'Berlin', age: 30, isEligible: false },
{ city: 'Amsterdam', age: 32, isEligible: false },
{ city: 'Oslo', age: 28, isEligible: false },
]);
// ACT
const result = await dataStoreService.upsertRows(dataStoreId, project1.id, {
rows: [
{ city: 'Berlin', age: 30, isEligible: true },
{ city: 'Amsterdam', age: 32, isEligible: true },
],
matchFields: ['age', 'city'],
});
// ASSERT
expect(result).toBe(true);
const { count, data } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(count).toEqual(3);
expect(data).toEqual(
expect.arrayContaining([
{ city: 'Berlin', age: 30, isEligible: true, id: 1 },
{ city: 'Amsterdam', age: 32, isEligible: true, id: 2 },
{ city: 'Oslo', age: 28, isEligible: false, id: 3 },
]),
);
});
it('inserts a row if filter does not match', async () => {
@@ -1256,32 +1314,36 @@ describe('dataStore', () => {
name: 'dataStore',
columns: [
{ name: 'pid', type: 'string' },
{ name: 'fullName', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'age', type: 'number' },
],
});
// Insert initial row
const ids = await dataStoreService.insertRows(dataStoreId, project1.id, [
{ pid: '1995-111a', fullName: 'Alice', age: 30 },
{ pid: '1995-111a', name: 'Alice', age: 30 },
]);
expect(ids).toEqual([1]);
// ACT
const result = await dataStoreService.upsertRows(dataStoreId, project1.id, {
rows: [{ pid: '1992-222b', fullName: 'Alice', age: 30 }],
rows: [{ pid: '1992-222b', name: 'Alice', age: 30 }],
matchFields: ['pid'],
});
// ASSERT
expect(result).toBe(true);
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
const { count, data } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(count).toEqual(2);
expect(data).toEqual([
{ fullName: 'Alice', age: 30, id: 1, pid: '1995-111a' },
{ fullName: 'Alice', age: 30, id: 2, pid: '1992-222b' },
{ name: 'Alice', age: 30, id: 1, pid: '1995-111a' },
{ name: 'Alice', age: 30, id: 2, pid: '1992-222b' },
]);
});
});

View File

@@ -270,29 +270,22 @@ export class DataStoreRowsRepository {
matchFields: string[],
rows: DataStoreRows,
): Promise<{ rowsToInsert: DataStoreRows; rowsToUpdate: DataStoreRows }> {
const dbType = this.dataSource.options.type;
const whereClauses: string[] = [];
const params: unknown[] = [];
const queryBuilder = this.dataSource
.createQueryBuilder()
.select(matchFields)
.from(this.toTableName(dataStoreId), 'datastore');
for (const row of rows) {
const clause = matchFields
.map((field) => {
params.push(row[field]);
return `${quoteIdentifier(field, dbType)} = ${getPlaceholder(params.length, dbType)}`;
})
.join(' AND ');
whereClauses.push(`(${clause})`);
}
rows.forEach((row, index) => {
const matchData = Object.fromEntries(matchFields.map((field) => [field, row[field]]));
if (index === 0) {
queryBuilder.where(matchData);
} else {
queryBuilder.orWhere(matchData);
}
});
const quotedFields = matchFields.map((field) => quoteIdentifier(field, dbType)).join(', ');
const quotedTableName = quoteIdentifier(this.toTableName(dataStoreId), dbType);
const query = `
SELECT ${quotedFields}
FROM ${quotedTableName}
WHERE ${whereClauses.join(' OR ')}
`;
const existing: Array<Record<string, unknown>> = await this.dataSource.query(query, params);
const existing: Array<Record<string, DataStoreColumnJsType | null>> =
await queryBuilder.getRawMany();
return splitRowsByExistence(existing, matchFields, rows);
}