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,547 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
const stmtBatchOptions = [
|
||||
{
|
||||
name: 'Single Statement',
|
||||
value: 'single',
|
||||
description: 'A single Statement for all incoming items',
|
||||
},
|
||||
{
|
||||
name: 'Independently',
|
||||
value: 'independently',
|
||||
description: 'Execute one Statement per incoming item of the run',
|
||||
},
|
||||
{
|
||||
name: 'Transaction',
|
||||
value: 'transaction',
|
||||
description:
|
||||
'Execute all Statements in a transaction, if a failure occurs, all changes are rolled back',
|
||||
},
|
||||
];
|
||||
|
||||
export const optionsCollection: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Auto Commit',
|
||||
name: 'autoCommit',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether this property is true, then the transaction in the current connection is automatically committed at the end of statement execution',
|
||||
},
|
||||
{
|
||||
displayName: 'Bind Variable Placeholder Values',
|
||||
name: 'params',
|
||||
placeholder: 'Add Parameter',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValueButtonText: 'Add another Parameter',
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['execute'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
description: 'Enter the values for the bind parameters used in the statement',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Bind Name or Number',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A bind variable placeholder identifier or numeral',
|
||||
placeholder:
|
||||
'e.g. ``dept_id`` and ``dept_name`` are the two bind variables placeholders in this SQL statement',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Bind Direction',
|
||||
name: 'bindDirection',
|
||||
type: 'options',
|
||||
default: 'in',
|
||||
required: true,
|
||||
description:
|
||||
'Specify whether data values bound to SQL or PL/SQL bind parameters are passed into, or out from, the database',
|
||||
options: [
|
||||
{ name: 'IN', value: 'in' },
|
||||
{ name: 'OUT', value: 'out' },
|
||||
{ name: 'IN-OUT', value: 'inout' },
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data Type',
|
||||
name: 'datatype',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: 'string',
|
||||
options: [
|
||||
{ name: 'BLOB', value: 'blob' },
|
||||
{ name: 'Boolean', value: 'boolean' },
|
||||
{ name: 'Date', value: 'date' },
|
||||
{ name: 'JSON', value: 'json' },
|
||||
{ name: 'Number', value: 'number' },
|
||||
{ name: 'SparseVector', value: 'sparse' },
|
||||
{ name: 'String', value: 'string' },
|
||||
{ name: 'Vector', value: 'vector' },
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Value (String)',
|
||||
name: 'valueString',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['string'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value (Number)',
|
||||
name: 'valueNumber',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['number'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value (Date)',
|
||||
name: 'valueDate',
|
||||
type: 'dateTime',
|
||||
default: 0,
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['date'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value (Boolean)',
|
||||
name: 'valueBoolean',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['boolean'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value (JSON)',
|
||||
name: 'valueJson',
|
||||
type: 'json',
|
||||
default: '{}',
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value (VECTOR)',
|
||||
name: 'valueVector',
|
||||
type: 'json',
|
||||
default: '[]',
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['vector'],
|
||||
},
|
||||
},
|
||||
placeholder: '[1.2, 3.4, 5.6]',
|
||||
description: 'A JSON array of dimension values',
|
||||
},
|
||||
{
|
||||
displayName: 'Value (BLOB)',
|
||||
name: 'valueBlob',
|
||||
type: 'json',
|
||||
default: '[]',
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['blob'],
|
||||
},
|
||||
},
|
||||
placeholder: '{ "type": "Buffer", "data": [98,10] }',
|
||||
description: 'A Binary data',
|
||||
},
|
||||
{
|
||||
displayName: 'Value (Sparse Vector)',
|
||||
name: 'valueSparse',
|
||||
type: 'collection',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
datatype: ['sparse'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Dimensions',
|
||||
name: 'dimensions',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Total number of dimensions',
|
||||
},
|
||||
{
|
||||
displayName: 'Indices',
|
||||
name: 'indices',
|
||||
type: 'json',
|
||||
default: '[]',
|
||||
placeholder: '[0, 2, 5]',
|
||||
description: 'A JSON array of indices, e.g., [0, 2, 5]',
|
||||
},
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
type: 'json',
|
||||
default: '[]',
|
||||
placeholder: '[1.2, 3.4, 5.6]',
|
||||
description: 'A JSON array of values matching indices',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Parse for IN Statement',
|
||||
name: 'parseInStatement',
|
||||
type: 'options',
|
||||
required: true,
|
||||
default: false,
|
||||
hint: 'If "Yes" the "Value" field should be a string of comma-separated values. i.e: 1,2,3 or str1,str2,str3',
|
||||
options: [
|
||||
{ name: 'No', value: false },
|
||||
{ name: 'Yes', value: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Fetch Array Size',
|
||||
name: 'fetchArraySize',
|
||||
type: 'number',
|
||||
default: 100,
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['execute', 'select'],
|
||||
},
|
||||
},
|
||||
description:
|
||||
'This property is a number that sets the size of an internal buffer used for fetching query rows from Oracle Database. Changing it may affect query performance but does not affect how many rows are returned to the application.',
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Number of Rows to Prefetch',
|
||||
name: 'prefetchRows',
|
||||
type: 'number',
|
||||
default: 2,
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['execute', 'select'],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
description:
|
||||
'This property is a query tuning option to set the number of additional rows the underlying Oracle driver fetches during the internal initial statement execution phase of a query',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||
displayName: 'Output Columns',
|
||||
name: 'outputColumns',
|
||||
type: 'multiOptions',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-dynamic-multi-options
|
||||
description:
|
||||
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getColumnsMultiOptions',
|
||||
loadOptionsDependsOn: ['table.value'],
|
||||
},
|
||||
default: [],
|
||||
displayOptions: {
|
||||
show: { '/operation': ['insert', 'select', 'update', 'upsert'] },
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Output Numbers As String',
|
||||
name: 'largeNumbersOutputAsString',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the numbers should be retrieved as string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/operation': ['execute', 'select'],
|
||||
},
|
||||
},
|
||||
hint: 'Applies to NUMBER, FLOAT, LONG type columns only',
|
||||
},
|
||||
{
|
||||
displayName: 'Statement Batching',
|
||||
name: 'stmtBatching',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: stmtBatchOptions,
|
||||
default: 'single',
|
||||
displayOptions: {
|
||||
show: { '/operation': ['update', 'insert', 'upsert'] },
|
||||
},
|
||||
description: 'The way queries should be sent to the database',
|
||||
},
|
||||
{
|
||||
displayName: 'Statement Batching',
|
||||
name: 'stmtBatching',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: stmtBatchOptions,
|
||||
default: 'independently',
|
||||
displayOptions: {
|
||||
show: { '/operation': ['deleteTable'] },
|
||||
},
|
||||
description: 'The way queries should be sent to the database',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Important: Single Statement mode works only for the first item',
|
||||
name: 'stmtBatchingNotice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/options.stmtBatching': ['single'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const schemaRLC: INodeProperties = {
|
||||
displayName: 'Schema',
|
||||
name: 'schema',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
placeholder: 'e.g. scott',
|
||||
description: 'The schema that contains the table you want to work on',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'schemaSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const tableRLC: INodeProperties = {
|
||||
displayName: 'Table',
|
||||
name: 'table',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
description: 'The table you want to work on',
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'tableSearch',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const whereFixedCollection: INodeProperties = {
|
||||
displayName: 'Select Rows',
|
||||
name: 'where',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Condition',
|
||||
default: {},
|
||||
description: 'If not set, all rows will be selected',
|
||||
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
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>',
|
||||
default: '',
|
||||
placeholder: 'e.g. ID',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getColumns',
|
||||
loadOptionsDependsOn: ['schema.value', 'table.value'],
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Operator',
|
||||
name: 'condition',
|
||||
type: 'options',
|
||||
description:
|
||||
"The operator to check the column against. When using 'LIKE' operator percent sign ( %) matches zero or more characters, underscore ( _ ) matches any single character.",
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-options-type-unsorted-items
|
||||
options: [
|
||||
{
|
||||
name: 'Equal',
|
||||
value: 'equal',
|
||||
},
|
||||
{
|
||||
name: 'Not Equal',
|
||||
value: '!=',
|
||||
},
|
||||
{
|
||||
name: 'Like',
|
||||
value: 'LIKE',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than',
|
||||
value: '>',
|
||||
},
|
||||
{
|
||||
name: 'Less Than',
|
||||
value: '<',
|
||||
},
|
||||
{
|
||||
name: 'Greater Than Or Equal',
|
||||
value: '>=',
|
||||
},
|
||||
{
|
||||
name: 'Less Than Or Equal',
|
||||
value: '<=',
|
||||
},
|
||||
{
|
||||
name: 'Is Null',
|
||||
value: 'IS NULL',
|
||||
},
|
||||
{
|
||||
name: 'Is Not Null',
|
||||
value: 'IS NOT NULL',
|
||||
},
|
||||
],
|
||||
default: 'equal',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'json',
|
||||
description: 'Parameters to pass to the tool as JSON or string',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
condition: ['IS NULL', 'IS NOT NULL'],
|
||||
},
|
||||
},
|
||||
default: '{"key": "val"}',
|
||||
placeholder: '{ "key": "value" }',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const sortFixedCollection: INodeProperties = {
|
||||
displayName: 'Sort',
|
||||
name: 'sort',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
placeholder: 'Add Sort Rule',
|
||||
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
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/" target="_blank">expression</a>',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getColumns',
|
||||
loadOptionsDependsOn: ['schema.value', 'table.value'],
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'ASC',
|
||||
value: 'ASC',
|
||||
},
|
||||
{
|
||||
name: 'DESC',
|
||||
value: 'DESC',
|
||||
},
|
||||
],
|
||||
default: 'ASC',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const combineConditionsCollection: INodeProperties = {
|
||||
displayName: 'Combine Conditions',
|
||||
name: 'combineConditions',
|
||||
type: 'options',
|
||||
description:
|
||||
'How to combine the conditions defined in "Select Rows": AND requires all conditions to be true, OR requires at least one condition to be true',
|
||||
options: [
|
||||
{
|
||||
name: 'AND',
|
||||
value: 'AND',
|
||||
description: 'Only rows that meet all the conditions are selected',
|
||||
},
|
||||
{
|
||||
name: 'OR',
|
||||
value: 'OR',
|
||||
description: 'Rows that meet at least one condition are selected',
|
||||
},
|
||||
],
|
||||
default: 'AND',
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { AllEntities, Entity } from 'n8n-workflow';
|
||||
|
||||
type OracleDBMap = {
|
||||
database: 'deleteTable' | 'execute' | 'insert' | 'select' | 'update' | 'upsert';
|
||||
};
|
||||
|
||||
export type OracleDBType = AllEntities<OracleDBMap>;
|
||||
|
||||
export type OracleDatabaseType = Entity<OracleDBMap, 'database'>;
|
||||
|
||||
export function isOracleDBOperation(op: string): op is OracleDBMap['database'] {
|
||||
return ['deleteTable', 'execute', 'insert', 'select', 'update', 'upsert'].includes(op);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import * as database from './database/Database.resource';
|
||||
import type { OracleDBType } from './node.type';
|
||||
import { isOracleDBOperation } from './node.type';
|
||||
import type { OracleDBNodeCredentials, OracleDBNodeOptions } from '../helpers/interfaces';
|
||||
import { configureQueryRunner } from '../helpers/utils';
|
||||
import { configureOracleDB } from '../transport';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const items = this.getInputData();
|
||||
const resource = this.getNodeParameter<OracleDBType>('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
if (!isOracleDBOperation(operation)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`The operation "${operation}" is not a valid value!`,
|
||||
);
|
||||
}
|
||||
|
||||
const credentials = await this.getCredentials<OracleDBNodeCredentials>('oracleDBApi');
|
||||
const options = this.getNodeParameter('options', 0, {}) as OracleDBNodeOptions;
|
||||
const node = this.getNode();
|
||||
options.nodeVersion = node.typeVersion;
|
||||
options.operation = operation;
|
||||
options.autoCommit = options.autoCommit ?? true;
|
||||
|
||||
const pool = await configureOracleDB.call(this, credentials, options);
|
||||
const runQueries = configureQueryRunner.call(this, this.getNode(), this.continueOnFail(), pool);
|
||||
const oracleDBNodeData: OracleDBType = {
|
||||
resource,
|
||||
operation,
|
||||
};
|
||||
|
||||
switch (oracleDBNodeData.resource) {
|
||||
case 'database':
|
||||
returnData = await database[oracleDBNodeData.operation].execute.call(
|
||||
this,
|
||||
runQueries,
|
||||
items,
|
||||
options,
|
||||
pool,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`The operation "${operation}" is not supported!`,
|
||||
);
|
||||
}
|
||||
|
||||
if (operation === 'select' && items.length > 1 && !node.executeOnce) {
|
||||
this.addExecutionHints({
|
||||
message: `This node ran ${items.length} times, once for each input item. To run for the first item only, enable 'execute once' in the node settings`,
|
||||
location: 'outputPane',
|
||||
});
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as database from './database/Database.resource';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Oracle Database',
|
||||
name: 'oracleDatabase',
|
||||
icon: 'file:oracle.svg',
|
||||
group: ['input'],
|
||||
version: [1],
|
||||
subtitle: '={{ $parameter["operation"] }}',
|
||||
description: 'Get, add and update data in Oracle database',
|
||||
defaults: {
|
||||
name: 'Oracle Database',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'oracleDBApi',
|
||||
required: true,
|
||||
testedBy: 'oracleDBConnectionTest',
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'hidden',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Database',
|
||||
value: 'database',
|
||||
},
|
||||
],
|
||||
default: 'database',
|
||||
},
|
||||
...database.description,
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user