Add query parameters for CrateDB, PostgresDB, TimescaleDB and QuestDB (Parametrized Queries) (#1577)

* Adding support to ParameterizedQuery on Postgres Node

* Created another parameter to toggle on replacement so it's clear to users what is happening.

* Fixed lint issues

*  Formatting

* Improvements to questDB node so it is more consistent

* Fixed lint issues

* Fixed typing issue

*  Apply suggestions BHesseldieck

Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>

* Standardized output for postgres and postgres-like nodes

This changes the behavior of CrateDB, Postgres, QuestDB and TimescaleDB
nodes.

The Execute Query operation used to execute multiple queries but return
the result from only one of the queries.

This change causes the node output to containt results from all queries
that ran, making the behavior more consistent across all n8n.

* Fixing lint issues

*  Minor improvements

*  Fix breaking changes files

Co-authored-by: Gustavo Arjones <gustavo.arjones@ank.app>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
Co-authored-by: Jan <janober@users.noreply.github.com>
Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
This commit is contained in:
Omar Ajoue 2021-05-01 00:35:34 +02:00 committed by GitHub
parent 4cf8055224
commit fc54f7c82b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 105 additions and 25 deletions

View file

@ -2,6 +2,15 @@
This list shows all the versions which include breaking changes and how to upgrade. This list shows all the versions which include breaking changes and how to upgrade.
## 0.118.0
### What changed?
In the Postgres, CrateDB, QuestDB and TimescaleDB nodes the `Execute Query` operation returns the result from all queries executed instead of just one of the results.
### When is action necessary?
If you use any of the above mentioned nodes with the `Execute Query` operation and the result is relevant to you, you are encouraged to revisit your logic. The node output may now contain more information than before. This change was made so that the behavior is more consistent across n8n where input with multiple rows should yield results acccording all input data instead of only one. Please note: n8n was already running multiple queries based on input. Only the output was changed.
## 0.117.0 ## 0.117.0
### What changed? ### What changed?
@ -50,6 +59,7 @@ If you are using a Dropbox APP with permission type, "App Folder".
### How to upgrade: ### How to upgrade:
Open your Dropbox node's credentials and set the "APP Access Type" parameter to "App Folder". Open your Dropbox node's credentials and set the "APP Access Type" parameter to "App Folder".
>>>>>>> master
## 0.111.0 ## 0.111.0

View file

@ -80,9 +80,9 @@ export class CrateDb implements INodeType {
}, },
}, },
default: '', default: '',
placeholder: 'SELECT id, name FROM product WHERE id < 40', placeholder: 'SELECT id, name FROM product WHERE quantity > $1 AND price <= $2',
required: true, required: true,
description: 'The SQL query to execute.', description: 'The SQL query to execute. You can use n8n expressions or $1 and $2 in conjunction with query parameters.',
}, },
// ---------------------------------- // ----------------------------------
@ -235,6 +235,21 @@ export class CrateDb implements INodeType {
'See the docs for more examples', 'See the docs for more examples',
].join('<br>'), ].join('<br>'),
}, },
{
displayName: 'Query Parameters',
name: 'queryParams',
type: 'string',
displayOptions: {
show: {
'/operation': [
'executeQuery',
],
},
},
default: '',
placeholder: 'quantity,price',
description: 'Comma separated list of properties which should be used as query parameters.',
},
], ],
}, },
], ],

View file

@ -60,27 +60,40 @@ export async function pgQuery(
getNodeParam: Function, getNodeParam: Function,
pgp: pgPromise.IMain<{}, pg.IClient>, pgp: pgPromise.IMain<{}, pg.IClient>,
db: pgPromise.IDatabase<{}, pg.IClient>, db: pgPromise.IDatabase<{}, pg.IClient>,
input: INodeExecutionData[], items: INodeExecutionData[],
continueOnFail: boolean, continueOnFail: boolean,
overrideMode?: string, overrideMode?: string,
): Promise<IDataObject[]> { ): Promise<IDataObject[]> {
const additionalFields = getNodeParam('additionalFields', 0) as IDataObject; const additionalFields = getNodeParam('additionalFields', 0) as IDataObject;
let valuesArray = [] as string[][];
if (additionalFields.queryParams) {
const propertiesString = additionalFields.queryParams as string;
const properties = propertiesString.split(',').map(column => column.trim());
const paramsItems = getItemsCopy(items, properties);
valuesArray = paramsItems.map((row) => properties.map(col => row[col])) as string[][];
}
const allQueries = [] as Array<{query: string, values?: string[]}>;
for (let i = 0; i < items.length; i++) {
const query = getNodeParam('query', i) as string;
const values = valuesArray[i];
const queryFormat = { query, values };
allQueries.push(queryFormat);
}
const mode = overrideMode ? overrideMode : (additionalFields.mode ?? 'multiple') as string; const mode = overrideMode ? overrideMode : (additionalFields.mode ?? 'multiple') as string;
if (mode === 'multiple') { if (mode === 'multiple') {
const queries: string[] = []; return (await db.multi(pgp.helpers.concat(allQueries))).flat(1);
for (let i = 0; i < input.length; i++) {
queries.push(getNodeParam('query', i) as string);
}
return (await db.multi(pgp.helpers.concat(queries))).flat(1);
} else if (mode === 'transaction') { } else if (mode === 'transaction') {
return db.tx(async t => { return db.tx(async t => {
const result: IDataObject[] = []; const result: IDataObject[] = [];
for (let i = 0; i < input.length; i++) { for (let i = 0; i < allQueries.length; i++) {
try { try {
Array.prototype.push.apply(result, await t.any(getNodeParam('query', i) as string)); Array.prototype.push.apply(result, await t.any(allQueries[i].query, allQueries[i].values));
} catch (err) { } catch (err) {
if (continueOnFail === false) throw err; if (continueOnFail === false) throw err;
result.push({ ...input[i].json, code: err.code, message: err.message }); result.push({ ...items[i].json, code: err.code, message: err.message });
return result; return result;
} }
} }
@ -89,12 +102,12 @@ export async function pgQuery(
} else if (mode === 'independently') { } else if (mode === 'independently') {
return db.task(async t => { return db.task(async t => {
const result: IDataObject[] = []; const result: IDataObject[] = [];
for (let i = 0; i < input.length; i++) { for (let i = 0; i < allQueries.length; i++) {
try { try {
Array.prototype.push.apply(result, await t.any(getNodeParam('query', i) as string)); Array.prototype.push.apply(result, await t.any(allQueries[i].query, allQueries[i].values));
} catch (err) { } catch (err) {
if (continueOnFail === false) throw err; if (continueOnFail === false) throw err;
result.push({ ...input[i].json, code: err.code, message: err.message }); result.push({ ...items[i].json, code: err.code, message: err.message });
} }
} }
return result; return result;

View file

@ -73,9 +73,9 @@ export class Postgres implements INodeType {
}, },
}, },
default: '', default: '',
placeholder: 'SELECT id, name FROM product WHERE id < 40', placeholder: 'SELECT id, name FROM product WHERE quantity > $1 AND price <= $2',
required: true, required: true,
description: 'The SQL query to execute.', description: 'The SQL query to execute. You can use n8n expressions or $1 and $2 in conjunction with query parameters.',
}, },
// ---------------------------------- // ----------------------------------
@ -232,6 +232,21 @@ export class Postgres implements INodeType {
'See the docs for more examples', 'See the docs for more examples',
].join('<br>'), ].join('<br>'),
}, },
{
displayName: 'Query Parameters',
name: 'queryParams',
type: 'string',
displayOptions: {
show: {
'/operation': [
'executeQuery',
],
},
},
default: '',
placeholder: 'quantity,price',
description: 'Comma separated list of properties which should be used as query parameters.',
},
], ],
}, },
], ],

View file

@ -73,9 +73,9 @@ export class QuestDb implements INodeType {
}, },
}, },
default: '', default: '',
placeholder: 'SELECT id, name FROM product WHERE id < 40', placeholder: 'SELECT id, name FROM product WHERE quantity > $1 AND price <= $2',
required: true, required: true,
description: 'The SQL query to execute.', description: 'The SQL query to execute. You can use n8n expressions or $1 and $2 in conjunction with query parameters.',
}, },
// ---------------------------------- // ----------------------------------
@ -176,6 +176,21 @@ export class QuestDb implements INodeType {
'See the docs for more examples', 'See the docs for more examples',
].join('<br>'), ].join('<br>'),
}, },
{
displayName: 'Query Parameters',
name: 'queryParams',
type: 'string',
displayOptions: {
show: {
'/operation': [
'executeQuery',
],
},
},
default: '',
placeholder: 'quantity,price',
description: 'Comma separated list of properties which should be used as query parameters.',
},
], ],
}, },
{ {
@ -215,7 +230,7 @@ export class QuestDb implements INodeType {
const db = pgp(config); const db = pgp(config);
let returnItems = []; let returnItems: INodeExecutionData[] = [];
const items = this.getInputData(); const items = this.getInputData();
const operation = this.getNodeParameter('operation', 0) as string; const operation = this.getNodeParameter('operation', 0) as string;

View file

@ -11,7 +11,6 @@ import {
} from 'n8n-workflow'; } from 'n8n-workflow';
import { import {
getItemCopy,
pgInsert, pgInsert,
pgQuery, pgQuery,
pgUpdate, pgUpdate,
@ -77,15 +76,13 @@ export class TimescaleDb implements INodeType {
}, },
displayOptions: { displayOptions: {
show: { show: {
operation: [ operation: ['executeQuery'],
'executeQuery',
],
}, },
}, },
default: '', default: '',
placeholder: 'SELECT id, name FROM product WHERE id < 40', placeholder: 'SELECT id, name FROM product WHERE quantity > $1 AND price <= $2',
required: true, required: true,
description: 'The SQL query to execute.', description: 'The SQL query to execute. You can use n8n expressions or $1 and $2 in conjunction with query parameters.',
}, },
// ---------------------------------- // ----------------------------------
@ -256,6 +253,21 @@ export class TimescaleDb implements INodeType {
'See the docs for more examples', 'See the docs for more examples',
].join('<br>'), ].join('<br>'),
}, },
{
displayName: 'Query Parameters',
name: 'queryParams',
type: 'string',
displayOptions: {
show: {
'/operation': [
'executeQuery',
],
},
},
default: '',
placeholder: 'quantity,price',
description: 'Comma separated list of properties which should be used as query parameters.',
},
], ],
}, },
], ],