From 2a8f4b1ea907a0d0519d650f61e48313525d79ce Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Sat, 23 Oct 2021 09:05:01 +1100 Subject: [PATCH] feat: Add an export to call prepared statements (#54) * feat: Add an export to call prepared queries - Utilises pool.execute (rather than query) to actually prepare parameters through mysql. This is intended for appropriately built queries utilising placeholders and parameters, and should only be used on frequently repeated queries. * refactor(prepare): Clean up results and add total execution time * feat(prepare): Send scalar response for a single value * fix(prepare): Check if result exists before getting keys --- src/execute.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++---- src/index.js | 12 +++++++- yarn.lock | 44 +++++++++++++++--------------- 3 files changed, 102 insertions(+), 28 deletions(-) diff --git a/src/execute.js b/src/execute.js index 7b38751..4b5e714 100644 --- a/src/execute.js +++ b/src/execute.js @@ -1,14 +1,13 @@ import { pool } from './pool'; import { parseParameters } from './parser'; import { slowQueryWarning, debug, resourceName } from './config'; +import { FormatError } from './errors'; const execute = async (query, parameters, resource) => { - ScheduleResourceTick(resourceName); try { - [query, parameters] = parseParameters(query, parameters); - - const [rows, _, executionTime] = await pool.query(query, parameters); + ScheduleResourceTick(resourceName); + const [executionTime, rows] = await pool.query(query, parameters); if (executionTime >= slowQueryWarning || debug) console.log( @@ -27,4 +26,69 @@ const execute = async (query, parameters, resource) => { } }; -export { execute }; +const queryType = (query) => { + switch (query.replace(/\s.*/, '')) { + case 'SELECT': + return 1; + case 'INSERT': + return 2; + case 'UPDATE': + return 3; + case 'DELETE': + return 3; + default: + return false; + } +}; + +const preparedStatement = async (query, parameters, resource) => { + try { + if (!Array.isArray(parameters)) + throw new FormatError(`Placeholders were defined, but query received no parameters!`, query); + + if (typeof query !== 'string') throw new FormatError(`Prepared statements must utilise a single query`); + + const type = queryType(query); + if (!type) throw new FormatError(`Prepared statements only accept SELECT, INSERT, UPDATE, and DELETE methods!`); + + ScheduleResourceTick(resourceName); + const results = []; + let totalTime = 0; + let queryCount = parameters.length; + + for (let i = 0; i < queryCount; i++) { + const [executionTime, rows] = await pool.execute(query, parameters[i]); + totalTime + executionTime; + results[i] = rows && (type === 3 ? rows.affectedRows : type === 2 ? rows.insertId : rows); + } + + totalTime = totalTime / queryCount; + if (totalTime >= slowQueryWarning || debug) + console.log( + `^3[${debug ? 'DEBUG' : 'WARNING'}] ${resource} took ${totalTime}ms to execute ${ + queryCount > 1 ? queryCount + ' queries' : 'a query' + }! + ${query} ${JSON.stringify(parameters)}^0` + ); + + if (results.length === 1) { + if (type === 1) { + if (results[0][0] && Object.keys(results[0][0]).length === 1) { + return Object.values(results[0][0])[0]; + } + return results[0][0]; + } + return results[0]; + } + return results; + } catch (error) { + console.log( + `^1[ERROR] ${resource} was unable to execute a query! + ${error.message} + ${error.sql || `${query} ${JSON.stringify(parameters)}`}^0` + ); + debug && console.trace(error); + } +}; + +export { execute, preparedStatement }; diff --git a/src/index.js b/src/index.js index c64d51d..7b74599 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ import { pool } from './pool'; -import { execute } from './execute'; +import { execute, preparedStatement } from './execute'; import { transaction } from './transaction'; import { debug, isolationLevel } from './config'; @@ -55,7 +55,17 @@ global.exports('transaction', (queries, parameters, cb, resource = GetInvokingRe }); }); +global.exports('prepared', (query, parameters, cb, resource = GetInvokingResource()) => { + preparedStatement(query, parameters, resource).then((result) => + safeCallback(cb || parameters, result, resource, query)) +}); + if (!GetResourceMetadata(GetCurrentResourceName(), 'server_script', 1)) { + global.exports('preparedSync', async (query, parameters) => { + const result = await preparedStatement(query, parameters, GetInvokingResource()); + return result; + }); + global.exports('executeSync', async (query, parameters) => { const result = await execute(query, parameters, GetInvokingResource()); return result; diff --git a/yarn.lock b/yarn.lock index fdf1e4b..36b1ab4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42,9 +42,9 @@ "@types/estree" "*" "@types/eslint@*": - version "7.28.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.1.tgz#50b07747f1f84c2ba8cd394cf0fe0ba07afce320" - integrity sha512-XhZKznR3i/W5dXqUhgU9fFdJekufbeBd5DALmkuXoeFcjbQcPk+2cL+WLHf6Q81HWAnM2vrslIHpGVyCAviRwg== + version "7.28.2" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.2.tgz#0ff2947cdd305897c52d5372294e8c76f351db68" + integrity sha512-KubbADPkfoU75KgKeKLsFHXnU4ipH7wYg0TRT33NK3N3yiu7jlFAAoygIWBV+KbuHx/G+AvuGX6DllnK35gfJA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -60,9 +60,9 @@ integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/node@*": - version "16.11.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97" - integrity sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA== + version "16.11.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.2.tgz#31c249c136c3f9b35d4b60fb8e50e01a1f0cc9a5" + integrity sha512-w34LtBB0OkDTs19FQHXy4Ig/TOXI4zqvXS2Kk1PAsRKZ0I+nik7LlMYxckW0tSNGtvWmzB+mrCTbuEjuB9DVsw== "@webassemblyjs/ast@1.11.1": version "1.11.1" @@ -291,9 +291,9 @@ buffer-from@^1.0.0: integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== caniuse-lite@^1.0.30001265: - version "1.0.30001269" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001269.tgz#3a71bee03df627364418f9fd31adfc7aa1cc2d56" - integrity sha512-UOy8okEVs48MyHYgV+RdW1Oiudl1H6KolybD6ZquD0VcrPSgj25omXO1S7rDydjpqaISCwA8Pyx+jUQKZwWO5w== + version "1.0.30001270" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001270.tgz#cc9c37a4ec5c1a8d616fc7bace902bb053b0cdea" + integrity sha512-TcIC7AyNWXhcOmv2KftOl1ShFAaHQYcB/EPL/hEyMrcS7ZX0/DvV1aoy6BzV0+16wTpoAyTMGDNAJfSqS/rz7A== chalk@^2.4.2: version "2.4.2" @@ -408,9 +408,9 @@ dir-glob@^3.0.1: path-type "^4.0.0" electron-to-chromium@^1.3.867: - version "1.3.871" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.871.tgz#6e87365fd72037a6c898fb46050ad4be3ac9ef62" - integrity sha512-qcLvDUPf8DSIMWarHT2ptgcqrYg62n3vPA7vhrOF24d8UNzbUBaHu2CySiENR3nEDzYgaN60071t0F6KLYMQ7Q== + version "1.3.877" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.877.tgz#956870eea7c9d8cf43cc54ea40687fee4dc0c12a" + integrity sha512-fT5mW5Giw5iyVukeHb2XvB4joBKvzHtl8Vs3QzE7APATpFMt/T7RWyUcIKSZzYkKQgpMbu+vDBTCHfQZvh8klA== enhanced-resolve@^5.8.3: version "5.8.3" @@ -763,9 +763,9 @@ isobject@^3.0.1: integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= jest-worker@^27.0.6: - version "27.3.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.3.0.tgz#6b636b63b6672208b91b92d8dcde112d1d4dba2d" - integrity sha512-xTTvvJqOjKBqE1AmwDHiQN8qzp9VoT981LtfXA+XiJVxHn4435vpnrzVcJ6v/ESiuB+IXPjZakn/ppT00xBCWA== + version "27.3.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.3.1.tgz#0def7feae5b8042be38479799aeb7b5facac24b2" + integrity sha512-ks3WCzsiZaOPJl/oMsDjaf0TRiSv7ctNgs0FqRr2nARsovz6AWWy4oLElwcquGSz692DzgZQrCLScPNs5YlC4g== dependencies: "@types/node" "*" merge-stream "^2.0.0" @@ -881,7 +881,7 @@ minimist@^1.2.0: "mysql2@https://github.com/overextended/node-mysql2.git": version "2.3.2" - resolved "https://github.com/overextended/node-mysql2.git#b50b58cefc23c6eee1811a2769395904666b8899" + resolved "https://github.com/overextended/node-mysql2.git#0621bdc95f3bc6db1e5cd06efd52663ea9410c9a" dependencies: denque "^2.0.1" generate-function "^2.3.1" @@ -910,9 +910,9 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-releases@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.0.tgz#67dc74903100a7deb044037b8a2e5f453bb05400" - integrity sha512-aA87l0flFYMzCHpTM3DERFSYxc6lv/BltdbRTOMZuxZ0cwZCD3mejE5n9vLhSJCN++/eOqr77G1IO5uXxlQYWA== + version "2.0.1" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== normalize-path@^3.0.0: version "3.0.0" @@ -1344,9 +1344,9 @@ webpack-sources@^3.2.0: integrity sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA== webpack@^5.52.1: - version "5.58.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.58.2.tgz#6b4af12fc9bd5cbedc00dc0a2fc2b9592db16b44" - integrity sha512-3S6e9Vo1W2ijk4F4PPWRIu6D/uGgqaPmqw+av3W3jLDujuNkdxX5h5c+RQ6GkjVR+WwIPOfgY8av+j5j4tMqJw== + version "5.59.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.59.1.tgz#60c77e9aad796252153d4d7ab6b2d4c11f0e548c" + integrity sha512-I01IQV9K96FlpXX3V0L4nvd7gb0r7thfuu1IfT2P4uOHOA77nKARAKDYGe/tScSHKnffNIyQhLC8kRXzY4KEHQ== dependencies: "@types/eslint-scope" "^3.7.0" "@types/estree" "^0.0.50"