first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as deleteTable from './deleteTable.operation';
|
||||
import * as execute from './executeQuery.operation';
|
||||
import * as insert from './insert.operation';
|
||||
import * as select from './select.operation';
|
||||
import * as update from './update.operation';
|
||||
import * as upsert from './upsert.operation';
|
||||
import { schemaRLC, tableRLC } from '../common.descriptions';
|
||||
|
||||
export { deleteTable, execute, insert, select, update, upsert };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteTable',
|
||||
description: 'Delete an entire table or rows in a table',
|
||||
action: 'Delete table or rows',
|
||||
},
|
||||
{
|
||||
name: 'Execute SQL',
|
||||
value: 'execute',
|
||||
description: 'Execute an SQL',
|
||||
action: 'Execute SQL',
|
||||
},
|
||||
{
|
||||
name: 'Insert',
|
||||
value: 'insert',
|
||||
description: 'Insert rows in a table',
|
||||
action: 'Insert rows in a table',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-option-name-wrong-for-upsert
|
||||
name: 'Insert or Update',
|
||||
value: 'upsert',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-upsert
|
||||
description: 'Insert or update rows in a table',
|
||||
action: 'Insert or update rows in a table',
|
||||
},
|
||||
{
|
||||
name: 'Select',
|
||||
value: 'select',
|
||||
description: 'Select rows from a table',
|
||||
action: 'Select rows from a table',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update rows in a table',
|
||||
action: 'Update rows in a table',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
},
|
||||
},
|
||||
default: 'insert',
|
||||
},
|
||||
{ ...schemaRLC, displayOptions: { hide: { operation: ['execute'] } } },
|
||||
{ ...tableRLC, displayOptions: { hide: { operation: ['execute'] } } },
|
||||
...deleteTable.description,
|
||||
...execute.description,
|
||||
...insert.description,
|
||||
...select.description,
|
||||
...update.description,
|
||||
...upsert.description,
|
||||
];
|
||||
@@ -0,0 +1,203 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import type * as oracleDBTypes from 'oracledb';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
OracleDBNodeOptions,
|
||||
QueriesRunner,
|
||||
QueryWithValues,
|
||||
WhereClause,
|
||||
} from '../../helpers/interfaces';
|
||||
import {
|
||||
quoteSqlIdentifier,
|
||||
addWhereClauses,
|
||||
getColumnMetaData,
|
||||
getColumnMap,
|
||||
getCompatibleValue,
|
||||
} from '../../helpers/utils';
|
||||
import {
|
||||
combineConditionsCollection,
|
||||
optionsCollection,
|
||||
whereFixedCollection,
|
||||
} from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Command',
|
||||
name: 'deleteCommand',
|
||||
type: 'options',
|
||||
default: 'truncate',
|
||||
options: [
|
||||
{
|
||||
name: 'Truncate',
|
||||
value: 'truncate',
|
||||
description: "Only removes the table's data and preserves the table's structure",
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description:
|
||||
"Delete the rows that match the 'Select Rows' conditions below. If no selection is made, all rows in the table are deleted.",
|
||||
},
|
||||
{
|
||||
name: 'Drop',
|
||||
value: 'drop',
|
||||
description: "Deletes the table's data and also the table's structure permanently",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...whereFixedCollection,
|
||||
displayOptions: {
|
||||
show: {
|
||||
deleteCommand: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...combineConditionsCollection,
|
||||
displayOptions: {
|
||||
show: {
|
||||
deleteCommand: ['delete'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['deleteTable'],
|
||||
},
|
||||
hide: {
|
||||
table: [''],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
pool: oracleDBTypes.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const queries: QueryWithValues[] = [];
|
||||
|
||||
const stmtBatching = nodeOptions.stmtBatching ?? 'independently';
|
||||
if (stmtBatching !== 'single') {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const schema = this.getNodeParameter('schema', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const table = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const deleteCommand = this.getNodeParameter('deleteCommand', i) as string;
|
||||
|
||||
let query = '';
|
||||
let values: any = [];
|
||||
|
||||
const quotedTableName = quoteSqlIdentifier(schema) + '.' + quoteSqlIdentifier(table);
|
||||
if (deleteCommand === 'drop') {
|
||||
query = `DECLARE
|
||||
e_table_missing EXCEPTION;
|
||||
PRAGMA EXCEPTION_INIT(e_table_missing, -942);
|
||||
BEGIN
|
||||
EXECUTE IMMEDIATE ('DROP TABLE ${quotedTableName} PURGE');
|
||||
EXCEPTION
|
||||
WHEN e_table_missing THEN NULL;
|
||||
END;`;
|
||||
} else if (deleteCommand === 'truncate') {
|
||||
query = `TRUNCATE TABLE ${quotedTableName}`;
|
||||
} else if (deleteCommand === 'delete') {
|
||||
const whereClauses =
|
||||
((this.getNodeParameter('where', i, []) as IDataObject).values as WhereClause[]) || [];
|
||||
const combineConditions = this.getNodeParameter('combineConditions', i, 'AND') as string;
|
||||
const tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table, i);
|
||||
const columnMetaDataObject = getColumnMap(tableSchema);
|
||||
|
||||
[query, values] = addWhereClauses(
|
||||
`DELETE FROM ${quotedTableName}`,
|
||||
whereClauses,
|
||||
combineConditions,
|
||||
columnMetaDataObject,
|
||||
);
|
||||
} else {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Invalid delete command, only drop, delete and truncate are supported ',
|
||||
{ itemIndex: i },
|
||||
);
|
||||
}
|
||||
|
||||
const queryWithValues = { query, values };
|
||||
queries.push(queryWithValues);
|
||||
}
|
||||
} else {
|
||||
const deleteCommand = this.getNodeParameter('deleteCommand', 0) as string;
|
||||
|
||||
if (deleteCommand !== 'delete') {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Invalid command for single-mode batching: only DELETE statements are supported.',
|
||||
{ itemIndex: 0 },
|
||||
);
|
||||
}
|
||||
|
||||
const schema = this.getNodeParameter('schema', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const table = this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let query: string = '';
|
||||
let bindDefs: any = [];
|
||||
const quotedTableName = quoteSqlIdentifier(schema) + '.' + quoteSqlIdentifier(table);
|
||||
const whereClauses =
|
||||
((this.getNodeParameter('where', 0, []) as IDataObject).values as WhereClause[]) || [];
|
||||
const combineConditions = this.getNodeParameter('combineConditions', 0, 'AND') as string;
|
||||
const tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table);
|
||||
const columnMetaDataObject = getColumnMap(tableSchema);
|
||||
|
||||
[query, bindDefs] = addWhereClauses(
|
||||
`DELETE FROM ${quotedTableName}`,
|
||||
whereClauses,
|
||||
combineConditions,
|
||||
columnMetaDataObject,
|
||||
true,
|
||||
);
|
||||
|
||||
const executeManyValues = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const result = [];
|
||||
const whereClauses =
|
||||
((this.getNodeParameter('where', i, []) as IDataObject).values as WhereClause[]) || [];
|
||||
|
||||
for (const clause of whereClauses) {
|
||||
const type: string = columnMetaDataObject[clause.column].type;
|
||||
const value = getCompatibleValue(type, clause.value);
|
||||
result.push(value);
|
||||
}
|
||||
executeManyValues.push(result);
|
||||
}
|
||||
|
||||
nodeOptions.bindDefs = bindDefs;
|
||||
queries.push({ query, executeManyValues });
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import type oracledb from 'oracledb';
|
||||
|
||||
import { getResolvables, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
ExecuteOpBindParam,
|
||||
OracleDBNodeOptions,
|
||||
QueriesRunner,
|
||||
QueryWithValues,
|
||||
} from '../../helpers/interfaces';
|
||||
import { getBindParameters } from '../../helpers/utils';
|
||||
import { optionsCollection } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Statement',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'e.g. SELECT id, name FROM product WHERE quantity > :1 AND price <= :2',
|
||||
noDataExpression: true,
|
||||
required: true,
|
||||
description:
|
||||
"The SQL statement to execute. You can use n8n expressions and positional parameters like :1, :2, :3, or named parameters like :name, :ID, etc to refer to the 'Bind Variable Placeholder Values' set in options below.",
|
||||
typeOptions: {
|
||||
editor: 'sqlEditor',
|
||||
sqlDialect: 'OracleDB',
|
||||
},
|
||||
hint: 'Consider using bind parameters to prevent SQL injection attacks. Add them in the options below',
|
||||
},
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['execute'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
/**
|
||||
* Query execution function for this node.
|
||||
*
|
||||
* This method is called once for every execution of the node during a workflow run.
|
||||
* It receives input data from the previous node(s) and returns output data to the next node(s).
|
||||
*
|
||||
*
|
||||
* Returns:
|
||||
* - An array of `INodeExecutionData` objects containing JSON data and optionally binary data, PairedItem,...
|
||||
*/
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
_pool?: oracledb.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const queries: QueryWithValues[] = [];
|
||||
|
||||
for (let index = 0; index < items.length; index++) {
|
||||
let query = this.getNodeParameter('query', index) as string;
|
||||
|
||||
// Dynamically replaces placeholders ({{...}}) in SQL queries.
|
||||
// Ex: SELECT * FROM users WHERE name = '{{ $json["name"] }}'
|
||||
// to SELECT * FROM users WHERE name = 'Alice'
|
||||
for (const resolvable of getResolvables(query)) {
|
||||
query = query.replace(resolvable, this.evaluateExpression(resolvable, index) as string);
|
||||
}
|
||||
|
||||
let values: any = [];
|
||||
|
||||
// get list of param objects entered by user
|
||||
const parameterIDataObjectList =
|
||||
((this.getNodeParameter('options.params', index, {}) as IDataObject)
|
||||
.values as ExecuteOpBindParam[]) || [];
|
||||
if (parameterIDataObjectList.length) {
|
||||
const { updatedQuery, bindParameters } = getBindParameters(query, parameterIDataObjectList);
|
||||
query = updatedQuery;
|
||||
values = bindParameters;
|
||||
}
|
||||
|
||||
queries.push({ query, values });
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
INode,
|
||||
} from 'n8n-workflow';
|
||||
import type * as oracleDBTypes from 'oracledb';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
ColumnMap,
|
||||
QueriesRunner,
|
||||
OracleDBNodeOptions,
|
||||
QueryWithValues,
|
||||
QueryMode,
|
||||
} from '../../helpers/interfaces';
|
||||
import {
|
||||
getInBindParametersForExecute,
|
||||
getColumnMap,
|
||||
getOutBindDefsForExecute,
|
||||
getBindDefsForExecuteMany,
|
||||
formatItemValues,
|
||||
quoteSqlIdentifier,
|
||||
configureTableSchemaUpdater,
|
||||
getColumnMetaData,
|
||||
checkItemAgainstSchema,
|
||||
} from '../../helpers/utils';
|
||||
import { optionsCollection } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
noDataExpression: true,
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'operation'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getMappingColumns',
|
||||
mode: 'add',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['insert'],
|
||||
},
|
||||
hide: {
|
||||
table: [''],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
function getQueryBindParameters(
|
||||
node: INode,
|
||||
query: string,
|
||||
values: oracleDBTypes.BindParameter[],
|
||||
columnMetaDataObject: ColumnMap,
|
||||
inputColumns: string[],
|
||||
outputColumns: string[],
|
||||
item: IDataObject,
|
||||
index: number,
|
||||
) {
|
||||
checkItemAgainstSchema(node, inputColumns, columnMetaDataObject, item, index);
|
||||
|
||||
const [quotedColsArray, replacements, posIndex] = getInBindParametersForExecute(
|
||||
inputColumns,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
'insert',
|
||||
values,
|
||||
);
|
||||
const quotedCols = quotedColsArray.join(',');
|
||||
|
||||
query = `${query} (${quotedCols}) VALUES (${replacements})`;
|
||||
|
||||
if (outputColumns.length > 0) {
|
||||
const updatedQuery = getOutBindDefsForExecute(
|
||||
query,
|
||||
columnMetaDataObject,
|
||||
outputColumns,
|
||||
values,
|
||||
posIndex,
|
||||
);
|
||||
query = updatedQuery;
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes the Node.
|
||||
*
|
||||
* @param this Function context (accesses params, helpers).
|
||||
* @param runQueries Helper function that executes an array of queries.
|
||||
* @param items Array of input data items.
|
||||
* @param nodeOptions Node configuration (version, execute options).
|
||||
* @param pool Database pool object to get Connections to execute.
|
||||
* @returns Promise which has INodeExecutionData array capturing results.
|
||||
*/
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
pool: oracleDBTypes.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let schema = this.getNodeParameter('schema', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let table = this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table);
|
||||
|
||||
const queries: QueryWithValues[] = [];
|
||||
const stmtBatching = (nodeOptions.stmtBatching as QueryMode) || 'single';
|
||||
|
||||
if (stmtBatching === 'single') {
|
||||
// We assume that the items passed have uniform keys.
|
||||
// Ex:
|
||||
// { "id": 1, "name": "Alice" }
|
||||
// { "id": 2, "name": "Bob" }
|
||||
// but not
|
||||
// { "id": 1, "name": "Alice" }
|
||||
// { "id": 2, "age": 25 }
|
||||
//
|
||||
// Also the schema and table are not changing in each item.
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
let item: IDataObject = {};
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[0].json;
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', 0) as IDataObject;
|
||||
}
|
||||
|
||||
const columnMetaDataObject: ColumnMap = getColumnMap(tableSchema);
|
||||
const inputColumns = Object.keys(item);
|
||||
let query = `INSERT INTO ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)}`;
|
||||
let outputColumns = this.getNodeParameter('options.outputColumns', 0, []) as string[];
|
||||
if (outputColumns.includes('*')) outputColumns = Object.keys(columnMetaDataObject);
|
||||
|
||||
query = getBindDefsForExecuteMany(
|
||||
this.getNode(),
|
||||
query,
|
||||
columnMetaDataObject,
|
||||
inputColumns,
|
||||
outputColumns,
|
||||
item,
|
||||
nodeOptions,
|
||||
);
|
||||
|
||||
const executeManyValues = [];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[i].json;
|
||||
}
|
||||
if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', i) as IDataObject;
|
||||
}
|
||||
const newItem = formatItemValues(item, columnMetaDataObject);
|
||||
executeManyValues.push(newItem);
|
||||
}
|
||||
|
||||
queries.push({ query, executeManyValues, outputColumns });
|
||||
} else {
|
||||
const updateTableSchema = configureTableSchemaUpdater(this.getNode(), schema, table);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
schema = this.getNodeParameter('schema', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
table = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', i) as string;
|
||||
let item: IDataObject = {};
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[i].json;
|
||||
|
||||
// Column refresh is needed only for 'autoMapInputData'
|
||||
tableSchema = await updateTableSchema(pool, tableSchema, schema, table, i);
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', i) as IDataObject;
|
||||
}
|
||||
|
||||
const columnMetaDataObject = getColumnMap(tableSchema);
|
||||
const inputColumns = Object.keys(item);
|
||||
let query = `INSERT INTO ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)}`;
|
||||
let outputColumns = this.getNodeParameter('options.outputColumns', i, []) as string[];
|
||||
const bindParams: oracleDBTypes.BindParameter[] = [];
|
||||
if (outputColumns.includes('*')) outputColumns = Object.keys(columnMetaDataObject);
|
||||
|
||||
query = getQueryBindParameters(
|
||||
this.getNode(),
|
||||
query,
|
||||
bindParams,
|
||||
columnMetaDataObject,
|
||||
inputColumns,
|
||||
outputColumns,
|
||||
item,
|
||||
i,
|
||||
);
|
||||
|
||||
queries.push({ query, values: bindParams, outputColumns });
|
||||
}
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import type * as oracleDBTypes from 'oracledb';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
OracleDBNodeOptions,
|
||||
QueriesRunner,
|
||||
QueryWithValues,
|
||||
SortRule,
|
||||
WhereClause,
|
||||
} from '../../helpers/interfaces';
|
||||
import {
|
||||
addSortRules,
|
||||
addWhereClauses,
|
||||
quoteSqlIdentifier,
|
||||
getColumnMap,
|
||||
getColumnMetaData,
|
||||
} from '../../helpers/utils';
|
||||
import {
|
||||
combineConditionsCollection,
|
||||
optionsCollection,
|
||||
sortFixedCollection,
|
||||
whereFixedCollection,
|
||||
} from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['select'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['select'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
whereFixedCollection,
|
||||
combineConditionsCollection,
|
||||
sortFixedCollection,
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['select'],
|
||||
},
|
||||
hide: {
|
||||
table: [''],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
pool: oracleDBTypes.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const queries: QueryWithValues[] = [];
|
||||
|
||||
const conn = await pool.getConnection();
|
||||
const isCDBSupported = conn.oracleServerVersion >= 1200000000;
|
||||
await conn.close();
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const schema = this.getNodeParameter('schema', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const table = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table, i);
|
||||
const columnMetaDataObject = getColumnMap(tableSchema);
|
||||
let values: any = [];
|
||||
const outputColumns = this.getNodeParameter('options.outputColumns', i, ['*']) as string[];
|
||||
|
||||
let query = '';
|
||||
let innerQuery = outputColumns.includes('*')
|
||||
? `SELECT * FROM ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)}`
|
||||
: `SELECT ${outputColumns.map(quoteSqlIdentifier).join(',')} FROM ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)}`;
|
||||
|
||||
// Add WHERE clause
|
||||
const whereClauses =
|
||||
((this.getNodeParameter('where', i, []) as IDataObject).values as WhereClause[]) || [];
|
||||
const combineConditions = this.getNodeParameter('combineConditions', i, 'AND') as string;
|
||||
[innerQuery, values] = addWhereClauses(
|
||||
innerQuery,
|
||||
whereClauses,
|
||||
combineConditions,
|
||||
columnMetaDataObject,
|
||||
);
|
||||
|
||||
// Add ORDER BY if needed
|
||||
const sortRules =
|
||||
((this.getNodeParameter('sort', i, []) as IDataObject).values as SortRule[]) || [];
|
||||
innerQuery = addSortRules(innerQuery, sortRules);
|
||||
|
||||
// Handle LIMIT / pagination
|
||||
const returnAll = this.getNodeParameter('returnAll', i, false);
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i, 50);
|
||||
|
||||
if (isCDBSupported) {
|
||||
// Oracle 12c+ (FETCH FIRST)
|
||||
query += `${innerQuery} FETCH FIRST ${limit} ROWS ONLY`;
|
||||
} else {
|
||||
if (sortRules.length > 0 || whereClauses.length > 0) {
|
||||
// Wrap inner query to preserve WHERE + ORDER BY
|
||||
query = `SELECT * FROM (${innerQuery}) WHERE ROWNUM <= ${limit}`;
|
||||
} else {
|
||||
// No ORDER BY or WHERE: safe to append ROWNUM inline
|
||||
query = `${innerQuery} WHERE ROWNUM <= ${limit}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// return all: no limit
|
||||
query = innerQuery;
|
||||
}
|
||||
|
||||
const queryWithValues = { query, values };
|
||||
queries.push(queryWithValues);
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import type * as oracleDBTypes from 'oracledb';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
ColumnMap,
|
||||
OracleDBNodeOptions,
|
||||
QueryMode,
|
||||
QueriesRunner,
|
||||
QueryWithValues,
|
||||
} from '../../helpers/interfaces';
|
||||
import {
|
||||
getCompatibleValue,
|
||||
getInBindParametersForExecute,
|
||||
getOutBindDefsForExecute,
|
||||
getBindDefsForExecuteMany,
|
||||
getColumnMap,
|
||||
configureTableSchemaUpdater,
|
||||
getColumnMetaData,
|
||||
quoteSqlIdentifier,
|
||||
} from '../../helpers/utils';
|
||||
import { optionsCollection } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
noDataExpression: true,
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'operation'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getMappingColumns',
|
||||
mode: 'update',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['update'],
|
||||
},
|
||||
hide: {
|
||||
table: [''],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
pool: oracleDBTypes.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
let schema = this.getNodeParameter('schema', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let table = this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table);
|
||||
|
||||
const queries: QueryWithValues[] = [];
|
||||
const stmtBatching = (nodeOptions.stmtBatching as QueryMode) || 'single';
|
||||
|
||||
if (stmtBatching === 'single') {
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
let item: IDataObject = {};
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[0].json;
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', 0) as IDataObject;
|
||||
}
|
||||
const columnMetaDataObject: ColumnMap = getColumnMap(tableSchema);
|
||||
// where clause column
|
||||
const columnsToMatchOn: string[] = this.getNodeParameter(
|
||||
'columns.matchingColumns',
|
||||
0,
|
||||
) as string[];
|
||||
|
||||
const updateColumns = Object.keys(item).filter((column) => !columnsToMatchOn.includes(column));
|
||||
if (!updateColumns.length) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Add values to update to the input item or set the 'Data Mode' to 'Define Below' to define the values to update.",
|
||||
);
|
||||
}
|
||||
|
||||
let query = `UPDATE ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)}`;
|
||||
let outputColumns = this.getNodeParameter('options.outputColumns', 0, []) as string[];
|
||||
if (outputColumns.includes('*')) outputColumns = Object.keys(columnMetaDataObject);
|
||||
|
||||
query = getBindDefsForExecuteMany(
|
||||
this.getNode(),
|
||||
query,
|
||||
columnMetaDataObject,
|
||||
updateColumns,
|
||||
outputColumns,
|
||||
item,
|
||||
nodeOptions,
|
||||
'update',
|
||||
columnsToMatchOn,
|
||||
);
|
||||
|
||||
const executeManyValues = [];
|
||||
const keysOrder = [...updateColumns, ...columnsToMatchOn];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[i].json;
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', i) as IDataObject;
|
||||
}
|
||||
const result = [];
|
||||
for (const key of keysOrder) {
|
||||
const type = columnMetaDataObject[key].type;
|
||||
let value: any = item[key];
|
||||
value = getCompatibleValue(type, value);
|
||||
result.push(value);
|
||||
}
|
||||
executeManyValues.push(result);
|
||||
}
|
||||
queries.push({ query, executeManyValues, outputColumns });
|
||||
} else {
|
||||
const updateTableSchema = configureTableSchemaUpdater(this.getNode(), schema, table);
|
||||
|
||||
for (let index = 0; index < items.length; index++) {
|
||||
schema = this.getNodeParameter('schema', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
table = this.getNodeParameter('table', index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const dataMode = this.getNodeParameter('columns.mappingMode', index) as string;
|
||||
let item: IDataObject = {};
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[index].json;
|
||||
|
||||
// Column refresh is needed only for 'autoMapInputData'
|
||||
tableSchema = await updateTableSchema(pool, tableSchema, schema, table, index);
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', index) as IDataObject;
|
||||
}
|
||||
|
||||
// where clause column
|
||||
const columnsToMatchOn: string[] = this.getNodeParameter(
|
||||
'columns.matchingColumns',
|
||||
index,
|
||||
) as string[];
|
||||
|
||||
const columnMetaDataObject = getColumnMap(tableSchema);
|
||||
const updateColumns = Object.keys(item).filter(
|
||||
(column) => !columnsToMatchOn.includes(column),
|
||||
);
|
||||
|
||||
if (!updateColumns.length) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Add values to update to the input item or set the 'Data Mode' to 'Define Below' to define the values to update.",
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(item).length === columnsToMatchOn.length) {
|
||||
// Only match column exists, nothing to update
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
"Add values to update to the input item or set the 'Data Mode' to 'Define Below' to define the values to update.",
|
||||
);
|
||||
}
|
||||
|
||||
const bindParams: oracleDBTypes.BindParameter[] = []; // bindParameters
|
||||
let [quotedColsArray, _replacements, posIndex] = getInBindParametersForExecute(
|
||||
updateColumns,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
'update',
|
||||
bindParams,
|
||||
);
|
||||
|
||||
let query = `UPDATE ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)} SET ${quotedColsArray.join(',')}`;
|
||||
if (columnsToMatchOn.length > 0) {
|
||||
[quotedColsArray, _replacements, posIndex] = getInBindParametersForExecute(
|
||||
columnsToMatchOn,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
'update',
|
||||
bindParams,
|
||||
posIndex,
|
||||
);
|
||||
|
||||
const condition = quotedColsArray.join(' AND ');
|
||||
query += ` WHERE ${condition}`;
|
||||
}
|
||||
|
||||
let outputColumns = this.getNodeParameter('options.outputColumns', index, []) as string[];
|
||||
if (outputColumns.includes('*')) outputColumns = Object.keys(columnMetaDataObject);
|
||||
|
||||
if (outputColumns.length > 0) {
|
||||
const updatedQuery = getOutBindDefsForExecute(
|
||||
query,
|
||||
columnMetaDataObject,
|
||||
outputColumns,
|
||||
bindParams,
|
||||
posIndex,
|
||||
);
|
||||
query = updatedQuery;
|
||||
}
|
||||
|
||||
queries.push({ query, values: bindParams, outputColumns });
|
||||
}
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
import type * as oracleDBTypes from 'oracledb';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type {
|
||||
ColumnInfo,
|
||||
ColumnMap,
|
||||
OracleDBNodeOptions,
|
||||
QueriesRunner,
|
||||
QueryWithValues,
|
||||
} from '../../helpers/interfaces';
|
||||
import {
|
||||
configureTableSchemaUpdater,
|
||||
getColumnMap,
|
||||
getCompatibleValue,
|
||||
getInBindParametersForSourceSelect,
|
||||
getOnClauseFromColumns,
|
||||
getInsertClauseAndBinds,
|
||||
getUpdateSetClause,
|
||||
getColumnMetaData,
|
||||
getOutBindDefsForExecute,
|
||||
quoteSqlIdentifier,
|
||||
} from '../../helpers/utils';
|
||||
import { optionsCollection } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Columns',
|
||||
name: 'columns',
|
||||
type: 'resourceMapper',
|
||||
noDataExpression: true,
|
||||
default: {
|
||||
mappingMode: 'defineBelow',
|
||||
value: null,
|
||||
},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'operation'],
|
||||
resourceMapper: {
|
||||
resourceMapperMethod: 'getMappingColumns',
|
||||
mode: 'upsert',
|
||||
fieldWords: {
|
||||
singular: 'column',
|
||||
plural: 'columns',
|
||||
},
|
||||
addAllFields: true,
|
||||
multiKeyMatch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
...optionsCollection,
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['database'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
hide: {
|
||||
table: [''],
|
||||
},
|
||||
};
|
||||
|
||||
function getQueryAndOutputColumns(
|
||||
ctx: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
item: IDataObject,
|
||||
schema: string,
|
||||
table: string,
|
||||
tableSchema: ColumnInfo[],
|
||||
bindParams: oracleDBTypes.BindParameter[],
|
||||
bindDefs: oracleDBTypes.BindDefinition[] | null,
|
||||
index: number,
|
||||
executeManyValues: any[] | null = null,
|
||||
): [string, string[]] {
|
||||
const columnMetaDataObject: ColumnMap = getColumnMap(tableSchema);
|
||||
const columnsToMatchOn: string[] = ctx.getNodeParameter(
|
||||
'columns.matchingColumns',
|
||||
index,
|
||||
) as string[];
|
||||
|
||||
if (columnsToMatchOn.length === 0) {
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
'No matching columns configured. Please define at least one column to match on.',
|
||||
);
|
||||
}
|
||||
|
||||
if (item[columnsToMatchOn[0]] === undefined) {
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
"Column to match on not found in input item. Add a column to match on or set the 'Data Mode' to 'Define Below' to define the value to match on.",
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(item).length === columnsToMatchOn.length) {
|
||||
// Only match column exists, nothing to update/insert
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
"Add values to update or insert to the input item or set the 'Data Mode' to 'Define Below' to define the values to insert or update.",
|
||||
);
|
||||
}
|
||||
const updateColumns = Object.keys(item).filter((column) => !columnsToMatchOn.includes(column));
|
||||
|
||||
const inputColumns = Object.keys(item);
|
||||
|
||||
const [sourceSelect, posIndex1] = getInBindParametersForSourceSelect(
|
||||
columnsToMatchOn,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
bindParams,
|
||||
bindDefs,
|
||||
0,
|
||||
);
|
||||
const onClause = getOnClauseFromColumns(columnsToMatchOn);
|
||||
const [updateSetClause, posIndex2] = getUpdateSetClause(
|
||||
updateColumns,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
bindParams,
|
||||
bindDefs,
|
||||
posIndex1,
|
||||
);
|
||||
const [insertColsStr, insertValsStr, posIndex3] = getInsertClauseAndBinds(
|
||||
inputColumns,
|
||||
columnMetaDataObject,
|
||||
item,
|
||||
bindParams,
|
||||
bindDefs,
|
||||
posIndex2,
|
||||
);
|
||||
|
||||
let query = `MERGE INTO ${quoteSqlIdentifier(schema)}.${quoteSqlIdentifier(table)} t
|
||||
USING (SELECT ${sourceSelect} FROM dual) s
|
||||
ON (${onClause})
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET ${updateSetClause}
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (${insertColsStr}) VALUES (${insertValsStr})
|
||||
`;
|
||||
|
||||
let outputColumns = ctx.getNodeParameter('options.outputColumns', 0, []) as string[];
|
||||
if (outputColumns.includes('*')) outputColumns = Object.keys(columnMetaDataObject);
|
||||
|
||||
if (outputColumns.length > 0) {
|
||||
query = getOutBindDefsForExecute(
|
||||
query,
|
||||
columnMetaDataObject,
|
||||
outputColumns,
|
||||
bindDefs ?? bindParams,
|
||||
posIndex3,
|
||||
);
|
||||
}
|
||||
if (executeManyValues) {
|
||||
const keysOrder = [...columnsToMatchOn, ...updateColumns, ...inputColumns];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const dataMode = ctx.getNodeParameter('columns.mappingMode', i) as string;
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[i].json;
|
||||
}
|
||||
if (dataMode === 'defineBelow') {
|
||||
item = ctx.getNodeParameter('columns.value', i) as IDataObject;
|
||||
}
|
||||
const result = [];
|
||||
for (const key of keysOrder) {
|
||||
const type = columnMetaDataObject[key].type;
|
||||
const value = getCompatibleValue(type, item[key]);
|
||||
result.push(value);
|
||||
}
|
||||
executeManyValues.push(result);
|
||||
}
|
||||
}
|
||||
return [query, outputColumns];
|
||||
}
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
runQueries: QueriesRunner,
|
||||
items: INodeExecutionData[],
|
||||
nodeOptions: OracleDBNodeOptions,
|
||||
pool: oracleDBTypes.Pool,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const stmtBatching = nodeOptions.stmtBatching ?? 'single';
|
||||
const queries: QueryWithValues[] = [];
|
||||
let item: IDataObject = {};
|
||||
|
||||
let schema = this.getNodeParameter('schema', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let table = this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let tableSchema = await getColumnMetaData(this.getNode(), pool, schema, table);
|
||||
|
||||
let dataMode = this.getNodeParameter('columns.mappingMode', 0) as string;
|
||||
|
||||
if (stmtBatching === 'single') {
|
||||
const executeManyValues: oracleDBTypes.BindParameters[] = [];
|
||||
const bindDefs: oracleDBTypes.BindDefinition[] = [];
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[0].json;
|
||||
} else if (dataMode === 'defineBelow') {
|
||||
item = this.getNodeParameter('columns.value', 0) as IDataObject;
|
||||
}
|
||||
|
||||
const [query, outputColumns] = getQueryAndOutputColumns(
|
||||
this,
|
||||
items,
|
||||
item,
|
||||
schema,
|
||||
table,
|
||||
tableSchema,
|
||||
[],
|
||||
bindDefs,
|
||||
0,
|
||||
executeManyValues,
|
||||
);
|
||||
|
||||
nodeOptions.bindDefs = bindDefs;
|
||||
queries.push({ query, executeManyValues, outputColumns });
|
||||
} else {
|
||||
const updateTableSchema = configureTableSchemaUpdater(this.getNode(), schema, table);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
dataMode = this.getNodeParameter('columns.mappingMode', i) as string;
|
||||
|
||||
schema = this.getNodeParameter('schema', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
table = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
if (dataMode === 'autoMapInputData') {
|
||||
item = items[i].json;
|
||||
|
||||
// Column refresh is needed only for 'autoMapInputData'
|
||||
tableSchema = await updateTableSchema(pool, tableSchema, schema, table, i);
|
||||
} else {
|
||||
item = this.getNodeParameter('columns.value', i) as IDataObject;
|
||||
}
|
||||
|
||||
const bindParams: oracleDBTypes.BindParameter[] = []; // bindParameters
|
||||
const [query, outputColumns] = getQueryAndOutputColumns(
|
||||
this,
|
||||
items,
|
||||
item,
|
||||
schema,
|
||||
table,
|
||||
tableSchema,
|
||||
bindParams,
|
||||
null,
|
||||
i,
|
||||
null,
|
||||
);
|
||||
|
||||
queries.push({ query, values: bindParams, outputColumns });
|
||||
}
|
||||
}
|
||||
|
||||
return await runQueries(queries, items, nodeOptions);
|
||||
}
|
||||
Reference in New Issue
Block a user