mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(editor): Use DB_TABLE_PREFIX on data store tables and refactor data store user table repo (no-changelog) (#18604)
This commit is contained in:
@@ -53,6 +53,5 @@ export {
|
||||
type DataStoreCreateColumnSchema,
|
||||
type DataStoreListFilter,
|
||||
type DataStoreListOptions,
|
||||
type DataStoreUserTableName,
|
||||
dateTimeSchema,
|
||||
} from './schemas/data-store.schema';
|
||||
|
||||
@@ -39,8 +39,6 @@ export const dataStoreSchema = z.object({
|
||||
export type DataStore = z.infer<typeof dataStoreSchema>;
|
||||
export type DataStoreColumn = z.infer<typeof dataStoreColumnSchema>;
|
||||
|
||||
export type DataStoreUserTableName = `data_store_user_${string}`;
|
||||
|
||||
export type DataStoreListFilter = {
|
||||
id?: string | string[];
|
||||
projectId?: string | string[];
|
||||
|
||||
@@ -18,7 +18,6 @@ import * as utils from '@test-integration/utils';
|
||||
import { DataStoreColumnRepository } from '../data-store-column.repository';
|
||||
import { DataStoreRowsRepository } from '../data-store-rows.repository';
|
||||
import { DataStoreRepository } from '../data-store.repository';
|
||||
import { toTableName } from '../utils/sql-utils';
|
||||
|
||||
let owner: User;
|
||||
let member: User;
|
||||
@@ -781,9 +780,9 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId', () => {
|
||||
});
|
||||
expect(dataStoreColumnInDb).toBeNull();
|
||||
|
||||
await expect(
|
||||
dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {}),
|
||||
).rejects.toThrow(QueryFailedError);
|
||||
await expect(dataStoreRowsRepository.getManyAndCount(dataStore.id, {})).rejects.toThrow(
|
||||
QueryFailedError,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1839,7 +1838,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/insert', () => {
|
||||
data: [1],
|
||||
});
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.data[0]);
|
||||
});
|
||||
@@ -1879,7 +1878,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/insert', () => {
|
||||
data: [1],
|
||||
});
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.data[0]);
|
||||
});
|
||||
@@ -1916,7 +1915,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/insert', () => {
|
||||
data: [1],
|
||||
});
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.data[0]);
|
||||
});
|
||||
@@ -1950,7 +1949,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/insert', () => {
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.message).toContain('unknown column');
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(0);
|
||||
});
|
||||
|
||||
@@ -2287,7 +2286,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '1' })
|
||||
.expect(403);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
});
|
||||
|
||||
@@ -2318,7 +2317,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '1' })
|
||||
.expect(403);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
});
|
||||
|
||||
@@ -2358,7 +2357,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '1,3' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject({
|
||||
first: 'test value 2',
|
||||
@@ -2398,7 +2397,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '2' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject({
|
||||
first: 'test value 1',
|
||||
@@ -2437,7 +2436,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '1,2' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(0);
|
||||
});
|
||||
|
||||
@@ -2474,7 +2473,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '2' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(2);
|
||||
expect(rowsInDb.data.map((r) => r.first).sort()).toEqual(['test value 1', 'test value 3']);
|
||||
});
|
||||
@@ -2501,7 +2500,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
|
||||
expect(response.body.data).toBe(true);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
});
|
||||
|
||||
@@ -2525,7 +2524,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '999,1000' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
});
|
||||
|
||||
@@ -2552,7 +2551,7 @@ describe('DELETE /projects/:projectId/data-stores/:dataStoreId/rows', () => {
|
||||
.query({ ids: '1,999,2,1000' })
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -2687,7 +2686,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => {
|
||||
.send(payload)
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]);
|
||||
});
|
||||
@@ -2724,7 +2723,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => {
|
||||
.send(payload)
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]);
|
||||
});
|
||||
@@ -2758,7 +2757,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => {
|
||||
.send(payload)
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(1);
|
||||
expect(rowsInDb.data[0]).toMatchObject(payload.rows[0]);
|
||||
});
|
||||
@@ -2793,7 +2792,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => {
|
||||
.expect(400);
|
||||
|
||||
expect(response.body.message).toContain('unknown column');
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {});
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {});
|
||||
expect(rowsInDb.count).toBe(0);
|
||||
});
|
||||
|
||||
@@ -2840,7 +2839,7 @@ describe('POST /projects/:projectId/data-stores/:dataStoreId/upsert', () => {
|
||||
.send(payload)
|
||||
.expect(200);
|
||||
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(toTableName(dataStore.id), {
|
||||
const rowsInDb = await dataStoreRowsRepository.getManyAndCount(dataStore.id, {
|
||||
sortBy: ['id', 'ASC'],
|
||||
});
|
||||
expect(rowsInDb.count).toBe(3);
|
||||
|
||||
@@ -11,7 +11,6 @@ import { DataStoreColumnNotFoundError } from '../errors/data-store-column-not-fo
|
||||
import { DataStoreNameConflictError } from '../errors/data-store-name-conflict.error';
|
||||
import { DataStoreNotFoundError } from '../errors/data-store-not-found.error';
|
||||
import { DataStoreValidationError } from '../errors/data-store-validation.error';
|
||||
import { toTableName } from '../utils/sql-utils';
|
||||
|
||||
beforeAll(async () => {
|
||||
await testModules.loadModules(['data-store']);
|
||||
@@ -73,7 +72,7 @@ describe('dataStore', () => {
|
||||
]);
|
||||
|
||||
// Select the column from user table to check for its existence
|
||||
const userTableName = toTableName(dataStoreId);
|
||||
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
|
||||
const rows = await dataStoreRepository.manager
|
||||
.createQueryBuilder()
|
||||
.select('foo')
|
||||
@@ -96,7 +95,7 @@ describe('dataStore', () => {
|
||||
|
||||
await expect(dataStoreService.getColumns(dataStoreId, project1.id)).resolves.toEqual([]);
|
||||
|
||||
const userTableName = toTableName(dataStoreId);
|
||||
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
|
||||
const queryRunner = dataStoreRepository.manager.connection.createQueryRunner();
|
||||
try {
|
||||
const table = await queryRunner.getTable(userTableName);
|
||||
@@ -225,7 +224,7 @@ describe('dataStore', () => {
|
||||
|
||||
// ACT
|
||||
const result = await dataStoreService.deleteDataStore(dataStoreId, project1.id);
|
||||
const userTableName = toTableName(dataStoreId);
|
||||
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
|
||||
|
||||
// ASSERT
|
||||
expect(result).toEqual(true);
|
||||
@@ -335,7 +334,7 @@ describe('dataStore', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const userTableName = toTableName(dataStoreId);
|
||||
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
|
||||
const queryRunner = dataStoreRepository.manager.connection.createQueryRunner();
|
||||
try {
|
||||
const table = await queryRunner.getTable(userTableName);
|
||||
@@ -402,7 +401,7 @@ describe('dataStore', () => {
|
||||
},
|
||||
]);
|
||||
|
||||
const userTableName = toTableName(dataStoreId);
|
||||
const userTableName = dataStoreRowsRepository.toTableName(dataStoreId);
|
||||
const queryRunner = dataStoreRepository.manager.connection.createQueryRunner();
|
||||
try {
|
||||
const table = await queryRunner.getTable(userTableName);
|
||||
@@ -1020,10 +1019,7 @@ describe('dataStore', () => {
|
||||
// ASSERT
|
||||
expect(result).toEqual([2]);
|
||||
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(
|
||||
toTableName(dataStoreId),
|
||||
{},
|
||||
);
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
|
||||
|
||||
expect(count).toEqual(2);
|
||||
expect(data).toEqual([
|
||||
@@ -1060,10 +1056,7 @@ describe('dataStore', () => {
|
||||
// ASSERT
|
||||
expect(result).toEqual([3, 4]);
|
||||
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(
|
||||
toTableName(dataStoreId),
|
||||
{},
|
||||
);
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
|
||||
|
||||
expect(count).toEqual(3);
|
||||
expect(data).toEqual([
|
||||
@@ -1246,10 +1239,7 @@ describe('dataStore', () => {
|
||||
// ASSERT
|
||||
expect(result).toBe(true);
|
||||
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(
|
||||
toTableName(dataStoreId),
|
||||
{},
|
||||
);
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
|
||||
|
||||
expect(count).toEqual(1);
|
||||
expect(data).toEqual([{ fullName: 'Alicia', age: 31, id: 1, pid: '1995-111a' }]);
|
||||
@@ -1281,10 +1271,7 @@ describe('dataStore', () => {
|
||||
// ASSERT
|
||||
expect(result).toBe(true);
|
||||
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(
|
||||
toTableName(dataStoreId),
|
||||
{},
|
||||
);
|
||||
const { count, data } = await dataStoreRowsRepository.getManyAndCount(dataStoreId, {});
|
||||
|
||||
expect(count).toEqual(2);
|
||||
expect(data).toEqual([
|
||||
|
||||
@@ -76,10 +76,16 @@ export class DataStoreColumnRepository extends Repository<DataStoreColumn> {
|
||||
async deleteColumn(dataStoreId: string, column: DataStoreColumn) {
|
||||
await this.manager.transaction(async (em) => {
|
||||
await em.remove(DataStoreColumn, column);
|
||||
|
||||
const queryRunner = em.queryRunner;
|
||||
if (!queryRunner) {
|
||||
throw new UnexpectedError('QueryRunner is not available');
|
||||
}
|
||||
|
||||
await this.dataStoreRowsRepository.dropColumnFromTable(
|
||||
dataStoreId,
|
||||
column.name,
|
||||
em,
|
||||
queryRunner,
|
||||
em.connection.options.type,
|
||||
);
|
||||
await this.shiftColumns(dataStoreId, column.index, -1, em);
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
import type {
|
||||
ListDataStoreContentQueryDto,
|
||||
ListDataStoreContentFilter,
|
||||
DataStoreUserTableName,
|
||||
UpsertDataStoreRowsDto,
|
||||
} from '@n8n/api-types';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { CreateTable, DslColumn } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
import {
|
||||
DataSource,
|
||||
DataSourceOptions,
|
||||
EntityManager,
|
||||
QueryRunner,
|
||||
SelectQueryBuilder,
|
||||
} from '@n8n/typeorm';
|
||||
import { DataSource, DataSourceOptions, QueryRunner, SelectQueryBuilder } from '@n8n/typeorm';
|
||||
import { DataStoreColumnJsType, DataStoreRows } from 'n8n-workflow';
|
||||
|
||||
import { DataStoreColumn } from './data-store-column.entity';
|
||||
import { DataStoreUserTableName } from './data-store.types';
|
||||
import {
|
||||
addColumnQuery,
|
||||
buildColumnTypeMap,
|
||||
@@ -26,7 +21,6 @@ import {
|
||||
quoteIdentifier,
|
||||
splitRowsByExistence,
|
||||
toDslColumns,
|
||||
toTableName,
|
||||
} from './utils/sql-utils';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -50,14 +44,17 @@ function getConditionAndParams(
|
||||
|
||||
@Service()
|
||||
export class DataStoreRowsRepository {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
constructor(
|
||||
private dataSource: DataSource,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
) {}
|
||||
|
||||
// TypeORM cannot infer the columns for a dynamic table name, so we use a raw query
|
||||
async insertRows(
|
||||
tableName: DataStoreUserTableName,
|
||||
rows: DataStoreRows,
|
||||
columns: DataStoreColumn[],
|
||||
) {
|
||||
toTableName(dataStoreId: string): DataStoreUserTableName {
|
||||
const { tablePrefix } = this.globalConfig.database;
|
||||
return `${tablePrefix}data_store_user_${dataStoreId}`;
|
||||
}
|
||||
|
||||
async insertRows(dataStoreId: string, rows: DataStoreRows, columns: DataStoreColumn[]) {
|
||||
const insertedIds: number[] = [];
|
||||
|
||||
// We insert one by one as the default behavior of returning the last inserted ID
|
||||
@@ -75,7 +72,7 @@ export class DataStoreRowsRepository {
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(
|
||||
tableName,
|
||||
this.toTableName(dataStoreId),
|
||||
columns.map((c) => c.name),
|
||||
)
|
||||
.values(row);
|
||||
@@ -92,21 +89,18 @@ export class DataStoreRowsRepository {
|
||||
return insertedIds;
|
||||
}
|
||||
|
||||
async upsertRows(
|
||||
tableName: DataStoreUserTableName,
|
||||
dto: UpsertDataStoreRowsDto,
|
||||
columns: DataStoreColumn[],
|
||||
) {
|
||||
// TypeORM cannot infer the columns for a dynamic table name, so we use a raw query
|
||||
async upsertRows(dataStoreId: string, dto: UpsertDataStoreRowsDto, columns: DataStoreColumn[]) {
|
||||
const { rows, matchFields } = dto;
|
||||
|
||||
const { rowsToInsert, rowsToUpdate } = await this.fetchAndSplitRowsByExistence(
|
||||
tableName,
|
||||
dataStoreId,
|
||||
matchFields,
|
||||
rows,
|
||||
);
|
||||
|
||||
if (rowsToInsert.length > 0) {
|
||||
await this.insertRows(tableName, rowsToInsert, columns);
|
||||
await this.insertRows(dataStoreId, rowsToInsert, columns);
|
||||
}
|
||||
|
||||
if (rowsToUpdate.length > 0) {
|
||||
@@ -119,7 +113,7 @@ export class DataStoreRowsRepository {
|
||||
const setData = Object.fromEntries(updateKeys.map((key) => [key, row[key]]));
|
||||
const whereData = Object.fromEntries(matchFields.map((key) => [key, row[key]]));
|
||||
|
||||
await this.updateRow(tableName, setData, whereData, columns);
|
||||
await this.updateRow(dataStoreId, setData, whereData, columns);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +121,7 @@ export class DataStoreRowsRepository {
|
||||
}
|
||||
|
||||
async updateRow(
|
||||
tableName: DataStoreUserTableName,
|
||||
dataStoreId: string,
|
||||
setData: Record<string, DataStoreColumnJsType | null>,
|
||||
whereData: Record<string, DataStoreColumnJsType | null>,
|
||||
columns: DataStoreColumn[],
|
||||
@@ -135,7 +129,7 @@ export class DataStoreRowsRepository {
|
||||
const dbType = this.dataSource.options.type;
|
||||
const columnTypeMap = buildColumnTypeMap(columns);
|
||||
|
||||
const queryBuilder = this.dataSource.createQueryBuilder().update(tableName);
|
||||
const queryBuilder = this.dataSource.createQueryBuilder().update(this.toTableName(dataStoreId));
|
||||
|
||||
const setValues: Record<string, DataStoreColumnJsType | null> = {};
|
||||
for (const [key, value] of Object.entries(setData)) {
|
||||
@@ -153,13 +147,13 @@ export class DataStoreRowsRepository {
|
||||
await queryBuilder.execute();
|
||||
}
|
||||
|
||||
async deleteRows(tableName: DataStoreUserTableName, ids: number[]) {
|
||||
async deleteRows(dataStoreId: string, ids: number[]) {
|
||||
if (ids.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const dbType = this.dataSource.options.type;
|
||||
const quotedTableName = quoteIdentifier(tableName, dbType);
|
||||
const quotedTableName = quoteIdentifier(this.toTableName(dataStoreId), dbType);
|
||||
const placeholders = ids.map((_, index) => getPlaceholder(index + 1, dbType)).join(', ');
|
||||
const query = `DELETE FROM ${quotedTableName} WHERE id IN (${placeholders})`;
|
||||
|
||||
@@ -168,36 +162,40 @@ export class DataStoreRowsRepository {
|
||||
}
|
||||
|
||||
async createTableWithColumns(
|
||||
tableName: string,
|
||||
dataStoreId: string,
|
||||
columns: DataStoreColumn[],
|
||||
queryRunner: QueryRunner,
|
||||
) {
|
||||
const dslColumns = [new DslColumn('id').int.autoGenerate2.primary, ...toDslColumns(columns)];
|
||||
const createTable = new CreateTable(tableName, '', queryRunner);
|
||||
const createTable = new CreateTable(this.toTableName(dataStoreId), '', queryRunner);
|
||||
createTable.withColumns.apply(createTable, dslColumns);
|
||||
|
||||
await createTable.execute(queryRunner);
|
||||
}
|
||||
|
||||
async dropTable(dataStoreId: string, queryRunner: QueryRunner) {
|
||||
await queryRunner.dropTable(this.toTableName(dataStoreId), true);
|
||||
}
|
||||
|
||||
async addColumn(
|
||||
dataStoreId: string,
|
||||
column: DataStoreColumn,
|
||||
queryRunner: QueryRunner,
|
||||
dbType: DataSourceOptions['type'],
|
||||
) {
|
||||
const tableName = toTableName(dataStoreId);
|
||||
await queryRunner.manager.query(addColumnQuery(tableName, column, dbType));
|
||||
await queryRunner.query(addColumnQuery(this.toTableName(dataStoreId), column, dbType));
|
||||
}
|
||||
|
||||
async dropColumnFromTable(
|
||||
dataStoreId: string,
|
||||
columnName: string,
|
||||
em: EntityManager,
|
||||
queryRunner: QueryRunner,
|
||||
dbType: DataSourceOptions['type'],
|
||||
) {
|
||||
await em.query(deleteColumnQuery(toTableName(dataStoreId), columnName, dbType));
|
||||
await queryRunner.query(deleteColumnQuery(this.toTableName(dataStoreId), columnName, dbType));
|
||||
}
|
||||
|
||||
async getManyAndCount(dataStoreId: DataStoreUserTableName, dto: ListDataStoreContentQueryDto) {
|
||||
async getManyAndCount(dataStoreId: string, dto: ListDataStoreContentQueryDto) {
|
||||
const [countQuery, query] = this.getManyQuery(dataStoreId, dto);
|
||||
const data: DataStoreRows = await query.select('*').getRawMany();
|
||||
const countResult = await countQuery.select('COUNT(*) as count').getRawOne<{
|
||||
@@ -208,19 +206,19 @@ export class DataStoreRowsRepository {
|
||||
return { count: count ?? -1, data };
|
||||
}
|
||||
|
||||
async getRowIds(dataStoreId: DataStoreUserTableName, dto: ListDataStoreContentQueryDto) {
|
||||
async getRowIds(dataStoreId: string, dto: ListDataStoreContentQueryDto) {
|
||||
const [_, query] = this.getManyQuery(dataStoreId, dto);
|
||||
const result = await query.select('dataStore.id').getRawMany<number>();
|
||||
return result;
|
||||
}
|
||||
|
||||
private getManyQuery(
|
||||
dataStoreTableName: DataStoreUserTableName,
|
||||
dataStoreId: string,
|
||||
dto: ListDataStoreContentQueryDto,
|
||||
): [QueryBuilder, QueryBuilder] {
|
||||
const query = this.dataSource.createQueryBuilder();
|
||||
|
||||
query.from(dataStoreTableName, 'dataStore');
|
||||
query.from(this.toTableName(dataStoreId), 'dataStore');
|
||||
this.applyFilters(query, dto);
|
||||
const countQuery = query.clone().select('COUNT(*)');
|
||||
this.applySorting(query, dto);
|
||||
@@ -268,7 +266,7 @@ export class DataStoreRowsRepository {
|
||||
}
|
||||
|
||||
private async fetchAndSplitRowsByExistence(
|
||||
tableName: string,
|
||||
dataStoreId: string,
|
||||
matchFields: string[],
|
||||
rows: DataStoreRows,
|
||||
): Promise<{ rowsToInsert: DataStoreRows; rowsToUpdate: DataStoreRows }> {
|
||||
@@ -287,7 +285,7 @@ export class DataStoreRowsRepository {
|
||||
}
|
||||
|
||||
const quotedFields = matchFields.map((field) => quoteIdentifier(field, dbType)).join(', ');
|
||||
const quotedTableName = quoteIdentifier(tableName, dbType);
|
||||
const quotedTableName = quoteIdentifier(this.toTableName(dataStoreId), dbType);
|
||||
|
||||
const query = `
|
||||
SELECT ${quotedFields}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { UnexpectedError } from 'n8n-workflow';
|
||||
import { DataStoreColumn } from './data-store-column.entity';
|
||||
import { DataStoreRowsRepository } from './data-store-rows.repository';
|
||||
import { DataStore } from './data-store.entity';
|
||||
import { toTableName } from './utils/sql-utils';
|
||||
|
||||
@Service()
|
||||
export class DataStoreRepository extends Repository<DataStore> {
|
||||
@@ -32,7 +31,6 @@ export class DataStoreRepository extends Repository<DataStore> {
|
||||
await em.insert(DataStore, dataStore);
|
||||
dataStoreId = dataStore.id;
|
||||
|
||||
const tableName = toTableName(dataStore.id);
|
||||
const queryRunner = em.queryRunner;
|
||||
if (!queryRunner) {
|
||||
throw new UnexpectedError('QueryRunner is not available');
|
||||
@@ -41,9 +39,9 @@ export class DataStoreRepository extends Repository<DataStore> {
|
||||
// insert columns
|
||||
const columnEntities = columns.map((col, index) =>
|
||||
em.create(DataStoreColumn, {
|
||||
dataStoreId,
|
||||
name: col.name,
|
||||
type: col.type,
|
||||
dataStoreId: dataStore.id,
|
||||
index: col.index ?? index,
|
||||
}),
|
||||
);
|
||||
@@ -54,7 +52,7 @@ export class DataStoreRepository extends Repository<DataStore> {
|
||||
|
||||
// create user table (will create empty table with just id column if no columns)
|
||||
await this.dataStoreRowsRepository.createTableWithColumns(
|
||||
tableName,
|
||||
dataStoreId,
|
||||
columnEntities,
|
||||
queryRunner,
|
||||
);
|
||||
@@ -81,7 +79,7 @@ export class DataStoreRepository extends Repository<DataStore> {
|
||||
}
|
||||
|
||||
await em.delete(DataStore, { id: dataStoreId });
|
||||
await queryRunner.dropTable(toTableName(dataStoreId), true);
|
||||
await this.dataStoreRowsRepository.dropTable(dataStoreId, queryRunner);
|
||||
|
||||
return true;
|
||||
});
|
||||
@@ -113,7 +111,7 @@ export class DataStoreRepository extends Repository<DataStore> {
|
||||
let changed = false;
|
||||
for (const match of existingTables) {
|
||||
const result = await em.delete(DataStore, { id: match.id });
|
||||
await queryRunner.dropTable(toTableName(match.id), true);
|
||||
await this.dataStoreRowsRepository.dropTable(match.id, queryRunner);
|
||||
changed = changed || (result.affected ?? 0) > 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import { DataStoreColumnNotFoundError } from './errors/data-store-column-not-fou
|
||||
import { DataStoreNameConflictError } from './errors/data-store-name-conflict.error';
|
||||
import { DataStoreNotFoundError } from './errors/data-store-not-found.error';
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { toTableName, normalizeRows } from './utils/sql-utils';
|
||||
import { normalizeRows } from './utils/sql-utils';
|
||||
|
||||
@Service()
|
||||
export class DataStoreService {
|
||||
@@ -112,10 +112,7 @@ export class DataStoreService {
|
||||
// a renamed/removed column appearing here (or added column missing) if the store was
|
||||
// modified between when the frontend sent the request and we received it
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId);
|
||||
const result = await this.dataStoreRowsRepository.getManyAndCount(
|
||||
toTableName(dataStoreId),
|
||||
dto,
|
||||
);
|
||||
const result = await this.dataStoreRowsRepository.getManyAndCount(dataStoreId, dto);
|
||||
return {
|
||||
count: result.count,
|
||||
data: normalizeRows(result.data, columns),
|
||||
@@ -133,7 +130,7 @@ export class DataStoreService {
|
||||
await this.validateRows(dataStoreId, rows);
|
||||
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId);
|
||||
return await this.dataStoreRowsRepository.insertRows(toTableName(dataStoreId), rows, columns);
|
||||
return await this.dataStoreRowsRepository.insertRows(dataStoreId, rows, columns);
|
||||
}
|
||||
|
||||
async upsertRows(dataStoreId: string, projectId: string, dto: UpsertDataStoreRowsDto) {
|
||||
@@ -146,7 +143,7 @@ export class DataStoreService {
|
||||
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId);
|
||||
|
||||
return await this.dataStoreRowsRepository.upsertRows(toTableName(dataStoreId), dto, columns);
|
||||
return await this.dataStoreRowsRepository.upsertRows(dataStoreId, dto, columns);
|
||||
}
|
||||
|
||||
async updateRow(dataStoreId: string, projectId: string, dto: UpdateDataStoreRowDto) {
|
||||
@@ -170,14 +167,14 @@ export class DataStoreService {
|
||||
this.validateRowsWithColumns([filter], columns, true, true);
|
||||
this.validateRowsWithColumns([data], columns, true, false);
|
||||
|
||||
await this.dataStoreRowsRepository.updateRow(toTableName(dataStoreId), data, filter, columns);
|
||||
await this.dataStoreRowsRepository.updateRow(dataStoreId, data, filter, columns);
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteRows(dataStoreId: string, projectId: string, ids: number[]) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
|
||||
return await this.dataStoreRowsRepository.deleteRows(toTableName(dataStoreId), ids);
|
||||
return await this.dataStoreRowsRepository.deleteRows(dataStoreId, ids);
|
||||
}
|
||||
|
||||
private validateRowsWithColumns(
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type DataStoreUserTableName = `data_store_user_${string}`;
|
||||
export type DataStoreUserTableName = `${string}data_store_user_${string}`;
|
||||
|
||||
@@ -8,10 +8,10 @@ import type { DataSourceOptions } from '@n8n/typeorm';
|
||||
import type { DataStoreColumnJsType, DataStoreRows } from 'n8n-workflow';
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
||||
import type { DataStoreUserTableName } from '../data-store.types';
|
||||
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
||||
export function toDslColumns(columns: DataStoreCreateColumnSchema[]): DslColumn[] {
|
||||
return columns.map((col) => {
|
||||
const name = new DslColumn(col.name.trim());
|
||||
@@ -137,10 +137,6 @@ export function quoteIdentifier(name: string, dbType: DataSourceOptions['type'])
|
||||
}
|
||||
}
|
||||
|
||||
export function toTableName(dataStoreId: string): DataStoreUserTableName {
|
||||
return `data_store_user_${dataStoreId}`;
|
||||
}
|
||||
|
||||
type WithInsertId = { insertId: number };
|
||||
type WithRowId = { id: number };
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import type { DataStoreRows } from 'n8n-workflow';
|
||||
import { DataStoreColumnRepository } from '@/modules/data-store/data-store-column.repository';
|
||||
import { DataStoreRowsRepository } from '@/modules/data-store/data-store-rows.repository';
|
||||
import { DataStoreRepository } from '@/modules/data-store/data-store.repository';
|
||||
import { toTableName } from '@/modules/data-store/utils/sql-utils';
|
||||
|
||||
export const createDataStore = async (
|
||||
project: Project,
|
||||
@@ -37,7 +36,7 @@ export const createDataStore = async (
|
||||
const columns = await dataStoreColumnRepository.getColumns(dataStore.id);
|
||||
|
||||
const dataStoreRowsRepository = Container.get(DataStoreRowsRepository);
|
||||
await dataStoreRowsRepository.insertRows(toTableName(dataStore.id), options.data, columns);
|
||||
await dataStoreRowsRepository.insertRows(dataStore.id, options.data, columns);
|
||||
}
|
||||
|
||||
return dataStore;
|
||||
|
||||
Reference in New Issue
Block a user