diff --git a/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.test.ts b/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.test.ts
new file mode 100644
index 0000000000..a1ef61b0e2
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.test.ts
@@ -0,0 +1,61 @@
+import { NodeTestHarness } from '@nodes-testing/node-test-harness';
+import nock from 'nock';
+
+jest.mock('jsonwebtoken', () => ({
+ sign: jest.fn().mockReturnValue('signature'),
+}));
+
+describe('Test Google BigQuery V2, executeQuery with named parameters', () => {
+ nock('https://oauth2.googleapis.com')
+ .persist()
+ .post(
+ '/token',
+ 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=signature',
+ )
+ .reply(200, { access_token: 'token' });
+
+ nock('https://bigquery.googleapis.com/bigquery')
+ .post('/v2/projects/test-project/jobs', {
+ configuration: {
+ query: {
+ queryParameters: [
+ {
+ name: 'email',
+ parameterType: { type: 'STRING' },
+ parameterValue: { value: 'test@n8n.io' },
+ },
+ {
+ name: 'name',
+ parameterType: { type: 'STRING' },
+ parameterValue: { value: 'Test Testerson' },
+ },
+ {
+ name: 'n8n_variable',
+ parameterType: { type: 'STRING' },
+ parameterValue: { value: 42 },
+ },
+ ],
+ query:
+ 'SELECT * FROM bigquery_node_dev_test_dataset.test_json WHERE email = @email AND name = @name AND n8n_variable = @n8n_variable;',
+ useLegacySql: false,
+ parameterMode: 'NAMED',
+ },
+ },
+ })
+ .reply(200, {
+ jobReference: {
+ jobId: 'job_123',
+ },
+ status: {
+ state: 'DONE',
+ },
+ })
+ .get('/v2/projects/test-project/queries/job_123')
+ .reply(200)
+ .get('/v2/projects/test-project/queries/job_123?maxResults=1000&timeoutMs=10000')
+ .reply(200, { rows: [], schema: {} });
+
+ new NodeTestHarness().setupTests({
+ workflowFiles: ['executeQuery.queryParameters.workflow.json'],
+ });
+});
diff --git a/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.workflow.json b/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.workflow.json
new file mode 100644
index 0000000000..967051db92
--- /dev/null
+++ b/packages/nodes-base/nodes/Google/BigQuery/test/v2/node/executeQuery.queryParameters.workflow.json
@@ -0,0 +1,79 @@
+{
+ "name": "My workflow 12",
+ "nodes": [
+ {
+ "parameters": {},
+ "id": "7db7d51a-83c2-4aa0-a736-9c3d1c031b60",
+ "name": "When clicking \"Execute Workflow\"",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [360, 340]
+ },
+ {
+ "parameters": {
+ "authentication": "serviceAccount",
+ "projectId": {
+ "__rl": true,
+ "value": "test-project",
+ "mode": "list",
+ "cachedResultName": "test-project",
+ "cachedResultUrl": "https://console.cloud.google.com/bigquery?project=test-project"
+ },
+ "sqlQuery": "SELECT * FROM bigquery_node_dev_test_dataset.test_json WHERE email = @email AND name = @name AND n8n_variable = @n8n_variable;",
+ "options": {
+ "queryParameters": {
+ "namedParameters": [
+ {
+ "name": "email",
+ "value": "test@n8n.io"
+ },
+ {
+ "name": "name",
+ "value": "Test Testerson"
+ },
+ {
+ "name": "n8n_variable",
+ "value": "={{ 40 + 2 }}"
+ }
+ ]
+ }
+ }
+ },
+ "id": "83d00275-0f98-4d5e-a3d6-bbca940ff8ac",
+ "name": "Google BigQuery",
+ "type": "n8n-nodes-base.googleBigQuery",
+ "typeVersion": 2,
+ "position": [620, 340],
+ "credentials": {
+ "googleApi": {
+ "id": "66",
+ "name": "Google account 5"
+ }
+ }
+ }
+ ],
+ "pinData": {
+ "Google BigQuery": []
+ },
+ "connections": {
+ "When clicking \"Execute Workflow\"": {
+ "main": [
+ [
+ {
+ "node": "Google BigQuery",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {},
+ "versionId": "be2fc126-5d71-4e86-9a4e-eb62ad266860",
+ "id": "156",
+ "meta": {
+ "instanceId": "36203ea1ce3cef713fa25999bd9874ae26b9e4c2c3a90a365f2882a154d031d0"
+ },
+ "tags": []
+}
diff --git a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts
index 38dcb91f3a..a847395382 100644
--- a/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts
+++ b/packages/nodes-base/nodes/Google/BigQuery/v2/actions/database/executeQuery.operation.ts
@@ -12,6 +12,13 @@ import type { ResponseWithJobReference } from '../../helpers/interfaces';
import { prepareOutput } from '../../helpers/utils';
import { googleBigQueryApiRequestAllItems, googleBigQueryApiRequest } from '../../transport';
+interface IQueryParameterOptions {
+ namedParameters: Array<{
+ name: string;
+ value: string;
+ }>;
+}
+
const properties: INodeProperties[] = [
{
displayName: 'SQL Query',
@@ -151,6 +158,53 @@ const properties: INodeProperties[] = [
description:
'Whether all integer values will be returned as numbers. If set to false, all integer values will be returned as strings.',
},
+ {
+ displayName: 'Query Parameters (Named)',
+ name: 'queryParameters',
+ type: 'fixedCollection',
+ description:
+ 'Use parameterized queries to prevent SQL injections. Positional arguments are not supported at the moment. This feature won\'t be available when using legacy SQL.',
+ displayOptions: {
+ hide: {
+ '/options.useLegacySql': [true],
+ },
+ },
+ typeOptions: {
+ multipleValues: true,
+ },
+ placeholder: 'Add Parameter',
+ default: {
+ namedParameters: [
+ {
+ name: '',
+ value: '',
+ },
+ ],
+ },
+ options: [
+ {
+ name: 'namedParameters',
+ displayName: 'Named Parameter',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the parameter',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description:
+ 'The substitute value. It must be a string. Arrays, dates and struct types mentioned in the official documentation are not yet supported.',
+ },
+ ],
+ },
+ ],
+ },
],
},
];
@@ -189,6 +243,7 @@ export async function execute(this: IExecuteFunctions): Promise {
+ // BigQuery type descriptors are very involved, and it would be hard to support all possible
+ // options, that's why the only supported type here is "STRING".
+ //
+ // If we switch this node to the official JS SDK from Google, we should be able to use `getTypeDescriptorFromValue`
+ // at runtime, which would infer BQ type descriptors of any valid JS value automatically:
+ //
+ // https://github.com/googleapis/nodejs-bigquery/blob/22021957f697ce67491bd50535f6fb43a99feea0/src/bigquery.ts#L1111
+ //
+ // Another, less user-friendly option, would be to allow users to specify the types manually.
+ return { name, parameterType: { type: 'STRING' }, parameterValue: { value } };
+ });
+ }
+
//https://cloud.google.com/bigquery/docs/reference/rest/v2/jobs/insert
const response: ResponseWithJobReference = await googleBigQueryApiRequest.call(
this,