Skip to content

Commit

Permalink
fix(Microsoft SQL Node): Fix execute query to allow for non select qu…
Browse files Browse the repository at this point in the history
…ery to run (#11335)
  • Loading branch information
ShireenMissi authored Oct 22, 2024
1 parent 901888d commit ba158b4
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 29 deletions.
40 changes: 38 additions & 2 deletions packages/nodes-base/nodes/Microsoft/Sql/GenericFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { IResult } from 'mssql';
import mssql from 'mssql';
import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import mssql from 'mssql';
import type { ITables, OperationInputData } from './interfaces';

import { chunk, flatten } from '@utils/utilities';

import type { ITables, OperationInputData } from './interfaces';

/**
* Returns a copy of the item which only contains the json data and
* of that only the defined properties
Expand Down Expand Up @@ -234,3 +237,36 @@ export async function deleteOperation(tables: ITables, pool: mssql.ConnectionPoo
0,
);
}

export async function executeSqlQueryAndPrepareResults(
pool: mssql.ConnectionPool,
rawQuery: string,
itemIndex: number,
): Promise<INodeExecutionData[]> {
const rawResult: IResult<any> = await pool.request().query(rawQuery);
const { recordsets, rowsAffected } = rawResult;
if (Array.isArray(recordsets) && recordsets.length > 0) {
const result: IDataObject[] = recordsets.length > 1 ? flatten(recordsets) : recordsets[0];

return result.map((entry) => ({
json: entry,
pairedItem: [{ item: itemIndex }],
}));
} else if (rowsAffected && rowsAffected.length > 0) {
// Handle non-SELECT queries (e.g., INSERT, UPDATE, DELETE)
return rowsAffected.map((affectedRows, idx) => ({
json: {
message: `Query ${idx + 1} executed successfully`,
rowsAffected: affectedRows,
},
pairedItem: [{ item: itemIndex }],
}));
} else {
return [
{
json: { message: 'Query executed successfully, but no rows were affected' },
pairedItem: [{ item: itemIndex }],
},
];
}
}
19 changes: 5 additions & 14 deletions packages/nodes-base/nodes/Microsoft/Sql/MicrosoftSql.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ import {
NodeConnectionType,
} from 'n8n-workflow';

import type { ITables } from './interfaces';
import { flatten, generatePairedItemData, getResolvables } from '@utils/utilities';

import {
configurePool,
createTableStruct,
deleteOperation,
executeSqlQueryAndPrepareResults,
insertOperation,
updateOperation,
} from './GenericFunctions';

import { flatten, generatePairedItemData, getResolvables } from '@utils/utilities';
import type { ITables } from './interfaces';

export class MicrosoftSql implements INodeType {
description: INodeTypeDescription = {
Expand Down Expand Up @@ -268,17 +268,8 @@ export class MicrosoftSql implements INodeType {
this.evaluateExpression(resolvable, i) as string,
);
}

const { recordsets }: IResult<any[]> = await pool.request().query(rawQuery);

const result: IDataObject[] = recordsets.length > 1 ? flatten(recordsets) : recordsets[0];

for (const entry of result) {
returnData.push({
json: entry,
pairedItem: [{ item: i }],
});
}
const results = await executeSqlQueryAndPrepareResults(pool, rawQuery, i);
returnData.push(...results);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({
Expand Down
114 changes: 101 additions & 13 deletions packages/nodes-base/nodes/Microsoft/Sql/test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Request } from 'mssql';
import type { IResult } from 'mssql';
import type mssql from 'mssql';
import type { IDataObject } from 'n8n-workflow';

import {
configurePool,
deleteOperation,
executeSqlQueryAndPrepareResults,
insertOperation,
mssqlChunk,
updateOperation,
} from '../GenericFunctions';

describe('MSSQL tests', () => {
let querySpy: jest.SpyInstance<void, Parameters<Request['query']>>;
let querySpy: jest.SpyInstance;
let request: Request;

const assertParameters = (parameters: unknown[][] | IDataObject) => {
Expand All @@ -34,18 +38,12 @@ describe('MSSQL tests', () => {
) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
request = this;
return [
[
[
{
recordsets: [],
recordset: undefined,
output: {},
rowsAffected: [0],
},
],
],
];
return {
recordsets: [],
recordset: [],
output: {},
rowsAffected: [0],
} as unknown as IResult<unknown>;
});
});

Expand Down Expand Up @@ -154,4 +152,94 @@ describe('MSSQL tests', () => {
expect(chunks.map((chunk) => chunk.length)).toEqual([699, 699, 699, 699, 204]);
});
});

describe('executeSqlQueryAndPrepareResults', () => {
it('should handle SELECT query with single record', async () => {
querySpy.mockResolvedValueOnce({
recordsets: [[{ id: 1, name: 'Test' }]] as any,
recordset: [{ id: 1, name: 'Test', columns: [{ name: 'id' }, { name: 'name' }] }],
rowsAffected: [1],
output: {},
} as unknown as IResult<unknown>);

const pool = { request: () => new Request() } as any as mssql.ConnectionPool;
const result = await executeSqlQueryAndPrepareResults(pool, 'SELECT * FROM users', 0);

expect(result).toEqual([
{
json: { id: 1, name: 'Test' },
pairedItem: [{ item: 0 }],
},
]);
expect(querySpy).toHaveBeenCalledWith('SELECT * FROM users');
});

it('should handle SELECT query with multiple records', async () => {
querySpy.mockResolvedValueOnce({
recordsets: [[{ id: 1 }], [{ name: 'Test' }]] as unknown,
rowsAffected: [1, 1],
output: {},
} as unknown as IResult<unknown>);

const pool = { request: () => new Request() } as any as mssql.ConnectionPool;
const result = await executeSqlQueryAndPrepareResults(pool, 'SELECT id; SELECT name', 1);

expect(result).toEqual([
{ json: { id: 1 }, pairedItem: [{ item: 1 }] },
{ json: { name: 'Test' }, pairedItem: [{ item: 1 }] },
]);
});

it('should handle non-SELECT query', async () => {
querySpy.mockResolvedValueOnce({
recordsets: [],
recordset: [],
rowsAffected: [5],
output: {},
} as unknown as IResult<unknown>);

const pool = { request: () => new Request() } as any as mssql.ConnectionPool;
const result = await executeSqlQueryAndPrepareResults(pool, 'UPDATE users SET active = 1', 2);

expect(result).toEqual([
{
json: { message: 'Query 1 executed successfully', rowsAffected: 5 },
pairedItem: [{ item: 2 }],
},
]);
});

it('should handle query with no affected rows', async () => {
querySpy.mockResolvedValueOnce({
recordsets: [],
recordset: [],
rowsAffected: [],
output: {},
} as unknown as IResult<unknown>);

const pool = { request: () => new Request() } as any as mssql.ConnectionPool;
const result = await executeSqlQueryAndPrepareResults(
pool,
'DELETE FROM users WHERE id = 999',
3,
);

expect(result).toEqual([
{
json: { message: 'Query executed successfully, but no rows were affected' },
pairedItem: [{ item: 3 }],
},
]);
});

it('should throw an error when query fails', async () => {
const errorMessage = 'Database error';
querySpy.mockRejectedValueOnce(new Error(errorMessage));

const pool = { request: () => new Request() } as any as mssql.ConnectionPool;
await expect(executeSqlQueryAndPrepareResults(pool, 'INVALID SQL', 4)).rejects.toThrow(
errorMessage,
);
});
});
});

0 comments on commit ba158b4

Please sign in to comment.