fix(core): Allow deleting and fix bulk inserting of empty rows (no-changelog) (#19779)

This commit is contained in:
Daria 2025-09-19 17:49:01 +03:00 committed by GitHub
parent e76202dffc
commit 084ea5a00b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 11 deletions

View File

@ -1341,6 +1341,33 @@ describe('dataStore', () => {
expect(data).toEqual([{ id: 1, createdAt: expect.any(Date), updatedAt: expect.any(Date) }]);
});
it('bulk insert should work with multiple empty rows', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
name: 'dataStore',
columns: [],
});
// ACT
const result = await dataStoreService.insertRows(dataStoreId, project1.id, [{}, {}]);
// ASSERT
expect(result).toEqual({ success: true, insertedRows: 2 });
const { count, data } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(count).toEqual(2);
expect(data).toEqual([
{ id: expect.any(Number), createdAt: expect.any(Date), updatedAt: expect.any(Date) },
{ id: expect.any(Number), createdAt: expect.any(Date), updatedAt: expect.any(Date) },
]);
});
it('handles multi-batch bulk correctly in bulk mode', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
@ -1865,6 +1892,51 @@ describe('dataStore', () => {
),
);
});
it('should delete empty rows containing only system columns', async () => {
// ARRANGE
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
name: 'dataStore',
columns: [],
});
// Insert empty rows
await dataStoreService.insertRows(dataStoreId, project1.id, [{}, {}]);
// Verify rows exist with only system columns
const { count: initialCount, data: initialData } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(initialCount).toEqual(2);
expect(initialData).toEqual([
{ id: 1, createdAt: expect.any(Date), updatedAt: expect.any(Date) },
{ id: 2, createdAt: expect.any(Date), updatedAt: expect.any(Date) },
]);
// ACT
const result = await dataStoreService.deleteRows(dataStoreId, project1.id, {
filter: {
type: 'and',
filters: [{ columnName: 'id', condition: 'eq', value: 1 }],
},
});
// ASSERT
expect(result).toEqual(true);
// Verify only one row remains
const { count: finalCount, data: finalData } = await dataStoreService.getManyRowsAndCount(
dataStoreId,
project1.id,
{},
);
expect(finalCount).toEqual(1);
expect(finalData).toEqual([
{ id: 2, createdAt: expect.any(Date), updatedAt: expect.any(Date) },
]);
});
});
describe('updateRow', () => {

View File

@ -162,6 +162,18 @@ export class DataStoreRowsRepository {
columns: DataTableColumn[],
em: EntityManager,
) {
let insertedRows = 0;
// Special case: no columns, insert each row individually
if (columns.length === 0) {
for (const row of rows) {
const query = em.createQueryBuilder().insert().into(table).values(row);
await query.execute();
insertedRows++;
}
return { success: true, insertedRows } as const;
}
// DB systems have different maximum parameters per query
// with old sqlite versions having the lowest in 999 parameters
// In practice 20000 works here, but performance didn't meaningfully change
@ -173,7 +185,6 @@ export class DataStoreRowsRepository {
const columnNames = columns.map((x) => x.name);
const dbType = this.dataSource.options.type;
let insertedRows = 0;
for (let i = 0; i < batches; ++i) {
const start = i * rowsPerBatch;
const endExclusive = Math.min(rows.length, (i + 1) * rowsPerBatch);

View File

@ -20,8 +20,9 @@ import type {
DataStoreRows,
DataTableInsertRowsReturnType,
DataTableInsertRowsResult,
DataStoreColumnType,
} from 'n8n-workflow';
import { validateFieldType } from 'n8n-workflow';
import { DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP, validateFieldType } from 'n8n-workflow';
import { DataStoreColumnRepository } from './data-store-column.repository';
import { DataStoreRowsRepository } from './data-store-rows.repository';
@ -286,11 +287,6 @@ export class DataStoreService {
await this.validateDataStoreExists(dataStoreId, projectId);
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId);
if (columns.length === 0) {
throw new DataStoreValidationError(
'No columns found for this data table or data table not found',
);
}
if (!dto.filter?.filters || dto.filter.filters.length === 0) {
throw new DataStoreValidationError(
@ -311,15 +307,16 @@ export class DataStoreService {
private validateRowsWithColumns(
rows: DataStoreRows,
columns: Array<{ name: string; type: string }>,
columns: Array<{ name: string; type: DataStoreColumnType }>,
includeSystemColumns = false,
): void {
// Include system columns like 'id' if requested
const allColumns = includeSystemColumns
? [
{ name: 'id', type: 'number' },
{ name: 'createdAt', type: 'date' },
{ name: 'updatedAt', type: 'date' },
...Object.entries(DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP).map(([name, type]) => ({
name,
type,
})),
...columns,
]
: columns;