UNPKG

n8n

Version:

n8n Workflow Automation Tool

442 lines 21.4 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DataTableRowsRepository = void 0; const db_1 = require("@n8n/db"); const di_1 = require("@n8n/di"); const typeorm_1 = require("@n8n/typeorm"); const n8n_workflow_1 = require("n8n-workflow"); const sql_utils_1 = require("./utils/sql-utils"); function getConditionAndParams(filter, index, dbType, tableReference) { const paramName = `filter_${index}`; const columnRef = tableReference ? `${(0, sql_utils_1.quoteIdentifier)(tableReference, dbType)}.${(0, sql_utils_1.quoteIdentifier)(filter.columnName, dbType)}` : (0, sql_utils_1.quoteIdentifier)(filter.columnName, dbType); if (filter.value === null) { switch (filter.condition) { case 'eq': return [`${columnRef} IS NULL`, {}]; case 'neq': return [`${columnRef} IS NOT NULL`, {}]; } } const value = filter.value; const operators = { eq: '=', gt: '>', gte: '>=', lt: '<', lte: '<=', }; if (operators[filter.condition]) { return [`${columnRef} ${operators[filter.condition]} :${paramName}`, { [paramName]: value }]; } if (filter.condition === 'neq') { return [`(${columnRef} != :${paramName} OR ${columnRef} IS NULL)`, { [paramName]: value }]; } switch (filter.condition) { case 'like': if (['sqlite', 'sqlite-pooled'].includes(dbType)) { const globValue = (0, sql_utils_1.toSqliteGlobFromPercent)(value); return [`${columnRef} GLOB :${paramName}`, { [paramName]: globValue }]; } if (['mysql', 'mariadb'].includes(dbType)) { const escapedValue = (0, sql_utils_1.escapeLikeSpecials)(value); return [ `${columnRef} LIKE BINARY :${paramName} ESCAPE '\\\\'`, { [paramName]: escapedValue }, ]; } if (dbType === 'postgres') { const escapedValue = (0, sql_utils_1.escapeLikeSpecials)(value); return [`${columnRef} LIKE :${paramName} ESCAPE '\\'`, { [paramName]: escapedValue }]; } return [`${columnRef} LIKE :${paramName}`, { [paramName]: value }]; case 'ilike': if (['sqlite', 'sqlite-pooled'].includes(dbType)) { const escapedValue = (0, sql_utils_1.escapeLikeSpecials)(value); return [ `UPPER(${columnRef}) LIKE UPPER(:${paramName}) ESCAPE '\\'`, { [paramName]: escapedValue }, ]; } if (['mysql', 'mariadb'].includes(dbType)) { const escapedValue = (0, sql_utils_1.escapeLikeSpecials)(value); return [ `UPPER(${columnRef}) LIKE UPPER(:${paramName}) ESCAPE '\\\\'`, { [paramName]: escapedValue }, ]; } if (dbType === 'postgres') { const escapedValue = (0, sql_utils_1.escapeLikeSpecials)(value); return [`${columnRef} ILIKE :${paramName} ESCAPE '\\'`, { [paramName]: escapedValue }]; } return [`UPPER(${columnRef}) LIKE UPPER(:${paramName})`, { [paramName]: value }]; } throw new Error(`Unsupported filter condition: ${filter.condition}`); } let DataTableRowsRepository = class DataTableRowsRepository { constructor(dataSource) { this.dataSource = dataSource; } async insertRowsBulk(table, rows, columns, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { let insertedRows = 0; 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 }; } const batchSize = 800; const batches = Math.max(1, Math.ceil((columns.length * rows.length) / batchSize)); const rowsPerBatch = Math.ceil(rows.length / batches); const columnNames = columns.map((x) => x.name); const dbType = this.dataSource.options.type; for (let i = 0; i < batches; ++i) { const start = i * rowsPerBatch; const endExclusive = Math.min(rows.length, (i + 1) * rowsPerBatch); if (endExclusive <= start) break; const completeRows = new Array(endExclusive - start); for (let j = start; j < endExclusive; ++j) { const insertArray = []; for (let h = 0; h < columnNames.length; ++h) { const column = columns[h]; const value = rows[j][column.name] ?? null; insertArray[h] = (0, sql_utils_1.normalizeValueForDatabase)(value, column.type, dbType); } completeRows[j - start] = insertArray; } const query = em .createQueryBuilder() .insert() .into(table, columnNames) .values(completeRows); await query.execute(); insertedRows += completeRows.length; } return { success: true, insertedRows }; }); } async insertRows(dataTableId, rows, columns, returnType, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const inserted = []; const dbType = this.dataSource.options.type; const useReturning = dbType === 'postgres' || dbType === 'mariadb'; const table = (0, sql_utils_1.toTableName)(dataTableId); const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name)); const escapedSystemColumns = n8n_workflow_1.DATA_TABLE_SYSTEM_COLUMNS.map((x) => this.dataSource.driver.escape(x)); const selectColumns = [...escapedSystemColumns, ...escapedColumns]; if (returnType === 'count') { return await this.insertRowsBulk(table, rows, columns, em); } for (const row of rows) { const completeRow = { ...row }; for (const column of columns) { if (!(column.name in completeRow)) { completeRow[column.name] = null; } completeRow[column.name] = (0, sql_utils_1.normalizeValueForDatabase)(completeRow[column.name], column.type, dbType); } const query = em.createQueryBuilder().insert().into(table).values(completeRow); if (useReturning) { query.returning(returnType === 'all' ? selectColumns.join(',') : 'id'); } const result = await query.execute(); if (useReturning) { const returned = returnType === 'all' ? (0, sql_utils_1.normalizeRows)((0, sql_utils_1.extractReturningData)(result.raw), columns) : (0, sql_utils_1.extractInsertedIds)(result.raw, dbType).map((id) => ({ id })); inserted.push.apply(inserted, returned); continue; } const ids = (0, sql_utils_1.extractInsertedIds)(result.raw, dbType); if (ids.length === 0) { throw new n8n_workflow_1.UnexpectedError("Couldn't find the inserted row ID"); } if (returnType === 'id') { inserted.push(...ids.map((id) => ({ id }))); continue; } const insertedRows = await this.getManyByIds(dataTableId, ids, columns, em); inserted.push(...insertedRows); } return inserted; }); } async updateRows(dataTableId, data, filter, columns, returnData = false, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const dbType = this.dataSource.options.type; const useReturning = dbType === 'postgres'; const table = (0, sql_utils_1.toTableName)(dataTableId); const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name)); const escapedSystemColumns = n8n_workflow_1.DATA_TABLE_SYSTEM_COLUMNS.map((x) => this.dataSource.driver.escape(x)); const selectColumns = [...escapedSystemColumns, ...escapedColumns]; const setData = this.prepareUpdateData(data, columns, dbType); let affectedRows = []; if (!useReturning && returnData) { affectedRows = await this.getAffectedRowsForUpdate(dataTableId, filter, columns, true, em); } setData.updatedAt = (0, sql_utils_1.normalizeValueForDatabase)(new Date(), 'date', dbType); const query = em.createQueryBuilder().update(table); this.applyFilters(query, filter, undefined); query.set(setData); if (useReturning && returnData) { query.returning(selectColumns.join(',')); } const result = await query.execute(); if (!returnData) { return true; } if (useReturning) { return (0, sql_utils_1.normalizeRows)((0, sql_utils_1.extractReturningData)(result.raw), columns); } const ids = affectedRows.map((row) => row.id); return await this.getManyByIds(dataTableId, ids, columns, em); }); } async dryRunUpdateRows(dataTableId, data, filter, columns, trx) { const dbType = this.dataSource.options.type; const beforeRows = await this.getAffectedRowsForUpdate(dataTableId, filter, columns, false, trx); const columnUpdates = this.prepareUpdateData(data, columns, dbType); return beforeRows.flatMap((original) => { const updated = { ...original, ...columnUpdates, updatedAt: new Date() }; return this.toDryRunRows(original, updated); }); } async dryRunUpsertRow(dataTableId, data, filter, columns, trx) { const updateResult = await this.dryRunUpdateRows(dataTableId, data, filter, columns, trx); if (updateResult.length > 0) { return updateResult; } const dbType = this.dataSource.options.type; const now = new Date(); const preparedData = this.prepareUpdateData(data, columns, dbType); const insertedRow = { id: 0, createdAt: now, updatedAt: now, ...preparedData, }; return this.toDryRunRows(null, insertedRow); } async deleteRows(dataTableId, columns, filter, returnData = false, dryRun = false, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const dbType = this.dataSource.options.type; const useReturning = !dryRun && dbType === 'postgres'; const shouldReturnData = returnData || dryRun; const table = (0, sql_utils_1.toTableName)(dataTableId); if (!shouldReturnData) { const query = em.createQueryBuilder().delete().from(table, 'dataTable'); if (filter) { this.applyFilters(query, filter, undefined); } await query.execute(); return true; } let affectedRows = []; if (!useReturning) { const selectQuery = em.createQueryBuilder().select('*').from(table, 'dataTable'); if (filter) { this.applyFilters(selectQuery, filter, 'dataTable'); } const rawRows = await selectQuery.getRawMany(); affectedRows = (0, sql_utils_1.normalizeRows)(rawRows, columns); } if (dryRun) { return affectedRows.flatMap((row) => this.toDryRunRows(row, null)); } const deleteQuery = em.createQueryBuilder().delete().from(table, 'dataTable'); if (useReturning) { const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name)); const escapedSystemColumns = n8n_workflow_1.DATA_TABLE_SYSTEM_COLUMNS.map((x) => this.dataSource.driver.escape(x)); const selectColumns = [...escapedSystemColumns, ...escapedColumns]; deleteQuery.returning(selectColumns.join(',')); } if (filter) { this.applyFilters(deleteQuery, filter, undefined); } const result = await deleteQuery.execute(); if (useReturning) { affectedRows = (0, sql_utils_1.normalizeRows)((0, sql_utils_1.extractReturningData)(result.raw), columns); } return affectedRows; }); } async getAffectedRowsForUpdate(dataTableId, filter, columns, idsOnly, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const table = (0, sql_utils_1.toTableName)(dataTableId); const selectColumns = idsOnly ? 'id' : '*'; const selectQuery = em.createQueryBuilder().select(selectColumns).from(table, 'dataTable'); this.applyFilters(selectQuery, filter, 'dataTable'); const rawRows = await selectQuery.getRawMany(); if (idsOnly) { return rawRows; } return (0, sql_utils_1.normalizeRows)(rawRows, columns); }); } prepareUpdateData(data, columns, dbType) { const setData = { ...data }; for (const column of columns) { if (column.name in setData) { setData[column.name] = (0, sql_utils_1.normalizeValueForDatabase)(setData[column.name], column.type, dbType); } } return setData; } toDryRunRows(beforeState, afterState) { if (beforeState === null && afterState === null) { throw new Error('Both before and after rows cannot be null'); } if (beforeState && afterState) { return [ { ...beforeState, dryRunState: 'before' }, { ...afterState, dryRunState: 'after' }, ]; } const template = (beforeState ?? afterState); const nullRow = { id: null, createdAt: null, updatedAt: null, ...Object.fromEntries(Object.keys(template).map((key) => [key, null])), }; const before = beforeState ?? nullRow; const after = afterState ?? nullRow; return [ { ...before, dryRunState: 'before' }, { ...after, dryRunState: 'after' }, ]; } async getManyAndCount(dataTableId, dto, columns, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const [countQuery, query] = this.getManyQuery(dataTableId, dto, columns, em); const data = await query.select('*').getRawMany(); const countResult = await countQuery.select('COUNT(*) as count').getRawOne(); const count = typeof countResult?.count === 'number' ? countResult.count : Number(countResult?.count) || 0; return { count: count ?? -1, data }; }, false); } async getManyByIds(dataTableId, ids, columns, trx) { return await (0, db_1.withTransaction)(this.dataSource.manager, trx, async (em) => { const table = (0, sql_utils_1.toTableName)(dataTableId); const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name)); const escapedSystemColumns = n8n_workflow_1.DATA_TABLE_SYSTEM_COLUMNS.map((x) => this.dataSource.driver.escape(x)); const selectColumns = [...escapedSystemColumns, ...escapedColumns]; if (ids.length === 0) { return []; } const rows = await em .createQueryBuilder() .select(selectColumns) .from(table, 'dataTable') .where({ id: (0, typeorm_1.In)(ids) }) .getRawMany(); return (0, sql_utils_1.normalizeRows)(rows, columns); }, false); } getManyQuery(dataTableId, dto, columns, em) { const query = em.createQueryBuilder(); const tableReference = 'dataTable'; query.from((0, sql_utils_1.toTableName)(dataTableId), tableReference); if (dto.filter) { this.applyFilters(query, dto.filter, tableReference); } if (dto.search && dto.search.trim().length > 0) { this.applySearch(query, dto.search, tableReference, columns); } const countQuery = query.clone().select('COUNT(*)'); this.applySorting(query, dto); this.applyPagination(query, dto); return [countQuery, query]; } applySearch(query, rawSearch, tableReference, columns) { const dbType = this.dataSource.options.type; const searchTerm = rawSearch.includes('%') ? rawSearch : `%${rawSearch}%`; const isSqlite = ['sqlite', 'sqlite-pooled'].includes(dbType); const isMy = ['mysql', 'mariadb'].includes(dbType); const isPg = dbType === 'postgres'; const allColumnNames = columns.map((c) => c.name); if (allColumnNames.length === 0) return; const tableRefQuoted = (0, sql_utils_1.quoteIdentifier)(tableReference, dbType); const conditions = []; for (const col of allColumnNames) { const colRef = `${tableRefQuoted}.${(0, sql_utils_1.quoteIdentifier)(col, dbType)}`; if (isSqlite) { conditions.push(`UPPER(CAST(${colRef} AS TEXT)) LIKE UPPER(:search) ESCAPE '\\'`); continue; } if (isMy) { conditions.push(`UPPER(CAST(${colRef} AS CHAR)) LIKE UPPER(:search) ESCAPE '\\\\'`); continue; } if (isPg) { conditions.push(`CAST(${colRef} AS TEXT) ILIKE :search ESCAPE '\\'`); continue; } conditions.push(`UPPER(CAST(${colRef} AS TEXT)) LIKE UPPER(:search)`); } if (conditions.length === 0) return; const whereClause = `(${conditions.join(' OR ')})`; query.andWhere(whereClause, { search: (0, sql_utils_1.escapeLikeSpecials)(searchTerm) }); } applyFilters(query, filter, tableReference) { const filters = filter.filters ?? []; const filterType = filter.type ?? 'and'; const dbType = this.dataSource.options.type; const conditionsAndParams = filters.map((filter, i) => getConditionAndParams(filter, i, dbType, tableReference)); if (conditionsAndParams.length === 1) { const [condition, params] = conditionsAndParams[0]; query.andWhere(condition, params); } else { for (const [condition, params] of conditionsAndParams) { if (filterType === 'or') { query.orWhere(condition, params); } else { query.andWhere(condition, params); } } } } applySorting(query, dto) { if (!dto.sortBy) { return; } const [field, order] = dto.sortBy; this.applySortingByField(query, field, order); } applySortingByField(query, field, direction) { const dbType = this.dataSource.options.type; const quotedField = `${(0, sql_utils_1.quoteIdentifier)('dataTable', dbType)}.${(0, sql_utils_1.quoteIdentifier)(field, dbType)}`; query.orderBy(quotedField, direction); } applyPagination(query, dto) { query.skip(dto.skip ?? 0); if (dto.take) query.take(dto.take); } }; exports.DataTableRowsRepository = DataTableRowsRepository; exports.DataTableRowsRepository = DataTableRowsRepository = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [typeorm_1.DataSource]) ], DataTableRowsRepository); //# sourceMappingURL=data-table-rows.repository.js.map