n8n/packages/nodes-base/nodes/MySql/v2/actions/database/insert.operation.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

236 lines
6.2 KiB
TypeScript
Raw Normal View History

import type {
IDataObject,
IExecuteFunctions,
INodeExecutionData,
INodeProperties,
} from 'n8n-workflow';
2023-04-12 07:24:17 -07:00
import type {
QueryMode,
QueryRunner,
QueryValues,
QueryWithValues,
} from '../../helpers/interfaces';
import { AUTO_MAP, BATCH_MODE, DATA_MODE } from '../../helpers/interfaces';
import { escapeSqlIdentifier, replaceEmptyStringsByNulls } from '../../helpers/utils';
2023-04-12 07:24:17 -07:00
import { optionsCollection } from '../common.descriptions';
feat(editor): SQL editor overhaul (#6282) * Draft setup * ⚡ Implemented expression evaluation in Postgres node, minor SQL editor UI improvements, minor refacring * ⚡ Added initial version of expression preview for SQL editor * ⚡ Linking npm package for codemirror sql grammar instead of a local file * ⚡ Moving expression editor wrapper elements to the component * ⚡ Using expression preview in SQL editor * Use SQL parser skipping whitespace * ✨ Added support for custom skipped segments specification * ✨ Fixing highlight problems with dots and expressions that resolve to zero * 👕 Fixing linting error * ✨ Added current item support * ⚡ Added expression support to more nodes with sql editor * ✨ Added expression support for other nodes * ✨ Implemented different SQL dialect support * 🐛 Fixing hard-coded parameter names for editors * ✨ Fixing preview for nested queries, updating query when input data changes, adding keyboard shortcut to toggle comments * ✨ Adding a custom automcomplete notice for different editors * ⚡ Updating SQL autocomplete notice * ✅ Added unit tests for SQL editor * ⚡ Using latest grammar * 🐛 Fixing code node editor rendering * 💄 SQL preview dropdown matches editor width. Removing unnecessary css * ⚡ Addressing PR review feedback * 👌 Addressing PR review feedback pt2 * 👌 Added path alias for utils in nodes-base package * 👌 Addressing more PR review feedback * ✅ Adding tests for `getResolvables` utility function * ⚡Fixing lodash imports * 👌 Better focus handling, adding more plugins to the editor, other minor imrovements * ⚡ Not showing SQL autocomplete suggestions inside expressions * ⚡ Using npm package for sql grammar * ⚡ Removing autocomplete notice, adding line highlight on syntax error * 👌 Addressing code review feedback --------- Co-authored-by: Milorad Filipovic <milorad@n8n.io>
2023-06-22 07:47:28 -07:00
import { updateDisplayOptions } from '@utils/utilities';
2023-04-12 07:24:17 -07:00
const properties: INodeProperties[] = [
{
displayName: 'Data Mode',
name: 'dataMode',
type: 'options',
options: [
{
name: 'Auto-Map Input Data to Columns',
value: DATA_MODE.AUTO_MAP,
description: 'Use when node input properties names exactly match the table column names',
},
{
name: 'Map Each Column Manually',
value: DATA_MODE.MANUAL,
description: 'Set the value for each destination column manually',
},
],
default: AUTO_MAP,
description:
'Whether to map node input properties and the table data automatically or manually',
},
{
displayName: `
In this mode, make sure incoming data fields are named the same as the columns in your table. If needed, use an 'Edit Fields' node before this node to change the field names.
2023-04-12 07:24:17 -07:00
`,
name: 'notice',
type: 'notice',
default: '',
displayOptions: {
show: {
dataMode: [DATA_MODE.AUTO_MAP],
},
},
},
{
displayName: 'Values to Send',
name: 'valuesToSend',
placeholder: 'Add Value',
type: 'fixedCollection',
typeOptions: {
multipleValueButtonText: 'Add Value',
multipleValues: true,
},
displayOptions: {
show: {
dataMode: [DATA_MODE.MANUAL],
},
},
default: {},
options: [
{
displayName: 'Values',
name: 'values',
values: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
displayName: 'Column',
name: 'column',
type: 'options',
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-options
2023-04-12 07:24:17 -07:00
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>',
2023-04-12 07:24:17 -07:00
typeOptions: {
loadOptionsMethod: 'getColumns',
loadOptionsDependsOn: ['table.value'],
},
default: [],
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
},
],
},
],
},
optionsCollection,
];
const displayOptions = {
show: {
resource: ['database'],
operation: ['insert'],
},
hide: {
table: [''],
},
};
export const description = updateDisplayOptions(displayOptions, properties);
export async function execute(
this: IExecuteFunctions,
inputItems: INodeExecutionData[],
runQueries: QueryRunner,
nodeOptions: IDataObject,
): Promise<INodeExecutionData[]> {
let returnData: INodeExecutionData[] = [];
const items = replaceEmptyStringsByNulls(inputItems, nodeOptions.replaceEmptyStrings as boolean);
const table = this.getNodeParameter('table', 0, '', { extractValue: true }) as string;
const dataMode = this.getNodeParameter('dataMode', 0) as string;
const queryBatching = (nodeOptions.queryBatching as QueryMode) || BATCH_MODE.SINGLE;
const queries: QueryWithValues[] = [];
if (queryBatching === BATCH_MODE.SINGLE) {
let columns: string[] = [];
let insertItems: IDataObject[] = [];
const priority = (nodeOptions.priority as string) || '';
const ignore = (nodeOptions.skipOnConflict as boolean) ? 'IGNORE' : '';
if (dataMode === DATA_MODE.AUTO_MAP) {
columns = [
...new Set(
items.reduce((acc, item) => {
const itemColumns = Object.keys(item.json);
return acc.concat(itemColumns);
}, [] as string[]),
),
];
insertItems = this.helpers.copyInputItems(items, columns);
2023-04-12 07:24:17 -07:00
}
if (dataMode === DATA_MODE.MANUAL) {
for (let i = 0; i < items.length; i++) {
const valuesToSend = (this.getNodeParameter('valuesToSend', i, []) as IDataObject)
.values as IDataObject[];
const item = valuesToSend.reduce((acc, { column, value }) => {
acc[column as string] = value;
return acc;
}, {} as IDataObject);
insertItems.push(item);
}
columns = [
...new Set(
insertItems.reduce((acc, item) => {
const itemColumns = Object.keys(item);
return acc.concat(itemColumns);
}, [] as string[]),
),
];
}
const escapedColumns = columns.map(escapeSqlIdentifier).join(', ');
2023-04-12 07:24:17 -07:00
const placeholder = `(${columns.map(() => '?').join(',')})`;
const replacements = items.map(() => placeholder).join(',');
const query = `INSERT ${priority} ${ignore} INTO ${escapeSqlIdentifier(
table,
)} (${escapedColumns}) VALUES ${replacements}`;
2023-04-12 07:24:17 -07:00
const values = insertItems.reduce(
(acc: IDataObject[], item) => acc.concat(Object.values(item) as IDataObject[]),
[],
);
queries.push({ query, values });
} else {
for (let i = 0; i < items.length; i++) {
let columns: string[] = [];
let insertItem: IDataObject = {};
const options = this.getNodeParameter('options', i);
const priority = (options.priority as string) || '';
const ignore = (options.skipOnConflict as boolean) ? 'IGNORE' : '';
if (dataMode === DATA_MODE.AUTO_MAP) {
columns = Object.keys(items[i].json);
insertItem = columns.reduce((acc, key) => {
if (columns.includes(key)) {
acc[key] = items[i].json[key];
}
return acc;
}, {} as IDataObject);
}
if (dataMode === DATA_MODE.MANUAL) {
const valuesToSend = (this.getNodeParameter('valuesToSend', i, []) as IDataObject)
.values as IDataObject[];
insertItem = valuesToSend.reduce((acc, { column, value }) => {
acc[column as string] = value;
return acc;
}, {} as IDataObject);
columns = Object.keys(insertItem);
}
const escapedColumns = columns.map(escapeSqlIdentifier).join(', ');
2023-04-12 07:24:17 -07:00
const placeholder = `(${columns.map(() => '?').join(',')})`;
const query = `INSERT ${priority} ${ignore} INTO ${escapeSqlIdentifier(
table,
)} (${escapedColumns}) VALUES ${placeholder};`;
2023-04-12 07:24:17 -07:00
const values = Object.values(insertItem) as QueryValues;
queries.push({ query, values });
}
}
returnData = await runQueries(queries);
return returnData;
}