From 084ea5a00b3de4c7cd96e0eb6979aa1729a5fa76 Mon Sep 17 00:00:00 2001 From: Daria Date: Fri, 19 Sep 2025 17:49:01 +0300 Subject: [PATCH] fix(core): Allow deleting and fix bulk inserting of empty rows (no-changelog) (#19779) --- .../data-store.service.integration.test.ts | 72 +++++++++++++++++++ .../data-table/data-store-rows.repository.ts | 13 +++- .../modules/data-table/data-store.service.ts | 17 ++--- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/modules/data-table/__tests__/data-store.service.integration.test.ts b/packages/cli/src/modules/data-table/__tests__/data-store.service.integration.test.ts index 3589af6a58f..987e62d3eb4 100644 --- a/packages/cli/src/modules/data-table/__tests__/data-store.service.integration.test.ts +++ b/packages/cli/src/modules/data-table/__tests__/data-store.service.integration.test.ts @@ -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', () => { diff --git a/packages/cli/src/modules/data-table/data-store-rows.repository.ts b/packages/cli/src/modules/data-table/data-store-rows.repository.ts index e9f7832bdbd..d4c18164cb8 100644 --- a/packages/cli/src/modules/data-table/data-store-rows.repository.ts +++ b/packages/cli/src/modules/data-table/data-store-rows.repository.ts @@ -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); diff --git a/packages/cli/src/modules/data-table/data-store.service.ts b/packages/cli/src/modules/data-table/data-store.service.ts index 461e1ac1081..42ddf3dd2b6 100644 --- a/packages/cli/src/modules/data-table/data-store.service.ts +++ b/packages/cli/src/modules/data-table/data-store.service.ts @@ -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;