mirror of
https://github.com/n8n-io/n8n.git
synced 2026-06-02 01:37:07 +02:00
fix(core): Allow deleting and fix bulk inserting of empty rows (no-changelog) (#19779)
This commit is contained in:
parent
e76202dffc
commit
084ea5a00b
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user