Skip to content

Commit 4cfb1d4

Browse files
validatePostgresParams added
1 parent 21621b4 commit 4cfb1d4

File tree

6 files changed

+109
-6
lines changed

6 files changed

+109
-6
lines changed

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@babel/node": "^7.27.1",
4444
"@babel/preset-env": "^7.27.2",
4545
"@rollup/plugin-commonjs": "^28.0.6",
46+
"@rollup/plugin-json": "^6.1.0",
4647
"@rollup/plugin-node-resolve": "^16.0.1",
4748
"@rollup/plugin-terser": "^0.4.4",
4849
"@rollup/plugin-typescript": "^12.1.3",

rollup.config.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import resolve from '@rollup/plugin-node-resolve';
22
import commonjs from '@rollup/plugin-commonjs';
3+
import json from '@rollup/plugin-json';
34
import preserveDirectories from 'rollup-preserve-directives';
45
const pkg = JSON.parse(fs.readFileSync('./package.json'))
56

@@ -23,6 +24,7 @@ const inputFiles = getAllInputFiles();
2324
// Prepare Plugins
2425
const plugins = [
2526
resolve({ preferBuiltins: true }),
27+
json(),
2628
commonjs(),
2729
preserveDirectories(),
2830
];

src/PuddySqlInstance.mjs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { open, Database } from 'sqlite';
22
import { EventEmitter } from 'events';
33
import { isJsonObject } from 'tiny-essentials';
44

5+
import { validatePostgresParams } from './Utils.mjs';
56
import { pg, sqlite3 } from './Modules.mjs';
67
import PuddySqlEngine from './PuddySqlEngine.mjs';
78
import PuddySqlQuery from './PuddySqlQuery.mjs';
@@ -420,8 +421,7 @@ class PuddySqlInstance extends PuddySqlEngine {
420421
* @throws {Error} If the table has already been initialized.
421422
*/
422423
async initTable(settings = {}, tableData = []) {
423-
if (!isJsonObject(settings))
424-
throw new Error('settings must be a plain object');
424+
if (!isJsonObject(settings)) throw new Error('settings must be a plain object');
425425
if (typeof settings.name !== 'string') throw new Error('settings.name must be a string');
426426
if (!this.#tables[settings.name]) {
427427
const newTable = new PuddySqlQuery();
@@ -591,10 +591,9 @@ class PuddySqlInstance extends PuddySqlEngine {
591591
* @returns {void}
592592
*/
593593
const rejectConnection = (reject, err) => {
594-
if (isConnectionError(err)) event.emit('connection-error', err);
594+
if (isConnectionError(err)) this.#events.emit('connection-error', err);
595595
reject(err);
596596
};
597-
const event = this.#events;
598597
const getId = () => this.#debugCount++;
599598
const isDebug = () => this.#debug;
600599

@@ -644,6 +643,7 @@ class PuddySqlInstance extends PuddySqlEngine {
644643
*/
645644
this.all = async function (query, params = [], debugName = '') {
646645
return new Promise((resolve, reject) => {
646+
validatePostgresParams(query, params, reject);
647647
const id = getId();
648648
sendSqlDebug(id, debugName, query, params);
649649
db.all(query, params)
@@ -664,6 +664,7 @@ class PuddySqlInstance extends PuddySqlEngine {
664664
*/
665665
this.get = async function (query, params = [], debugName = '') {
666666
return new Promise((resolve, reject) => {
667+
validatePostgresParams(query, params, reject);
667668
const id = getId();
668669
sendSqlDebug(id, debugName, query, params);
669670
db.get(query, params)
@@ -684,6 +685,7 @@ class PuddySqlInstance extends PuddySqlEngine {
684685
*/
685686
this.run = async function (query, params, debugName = '') {
686687
return new Promise((resolve, reject) => {
688+
validatePostgresParams(query, params, reject);
687689
const id = getId();
688690
sendSqlDebug(id, debugName, query, params);
689691
db.run(query, params)
@@ -756,9 +758,8 @@ class PuddySqlInstance extends PuddySqlEngine {
756758
* @returns {void}
757759
*/
758760
const rejectConnection = (err) => {
759-
if (isConnectionError(err)) event.emit('connection-error', err);
761+
if (isConnectionError(err)) this.#events.emit('connection-error', err);
760762
};
761-
const event = this.#events;
762763
db.on('error', rejectConnection);
763764

764765
const getId = () => this.#debugCount++;
@@ -810,6 +811,7 @@ class PuddySqlInstance extends PuddySqlEngine {
810811
*/
811812
this.all = async function (query, params = [], debugName = '') {
812813
try {
814+
validatePostgresParams(query, params);
813815
const id = getId();
814816
sendSqlDebug(id, debugName, query, params);
815817
const res = await db.query(query, params);
@@ -830,6 +832,7 @@ class PuddySqlInstance extends PuddySqlEngine {
830832
*/
831833
this.get = async function (query, params = [], debugName = '') {
832834
try {
835+
validatePostgresParams(query, params);
833836
const id = getId();
834837
sendSqlDebug(id, debugName, query, params);
835838
const res = await db.query(query, params);
@@ -852,6 +855,7 @@ class PuddySqlInstance extends PuddySqlEngine {
852855
*/
853856
this.run = async function (query, params, debugName = '') {
854857
try {
858+
validatePostgresParams(query, params);
855859
const id = getId();
856860
sendSqlDebug(id, debugName, query, params);
857861
const res = await db.query(query, params);

src/Utils.mjs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Converts PostgreSQL-style SQL placeholders ($1, $2, ...) to MySQL-style placeholders (?).
3+
* Maintains full compatibility with repeated and unordered params.
4+
*
5+
* @param {string} query - The PostgreSQL-style query.
6+
* @param {any[]} params - Parameters corresponding to placeholders.
7+
* @param {(reason?: any) => void} [errCallback]
8+
* @returns {{ query: string, params: any[] }} - MySQL-compatible query and reordered parameters.
9+
*/
10+
export function convertPgToMySQLSyntax(query, params, errCallback) {
11+
/** @param {string} msg */
12+
const sendError = (msg) => {
13+
const err = new Error(msg);
14+
if (typeof errCallback === 'function') return errCallback(err);
15+
throw err;
16+
};
17+
18+
/** @type {any[]} */
19+
const usedParams = [];
20+
const transformedQuery = query.replace(/\$([0-9]+)/g, (_, numStr) => {
21+
const index = parseInt(numStr, 10) - 1;
22+
if (index < 0 || index >= params.length) sendError(`Invalid parameter reference: $${numStr}`);
23+
usedParams.push(params[index]);
24+
return '?';
25+
});
26+
27+
return { query: transformedQuery, params: usedParams };
28+
}
29+
30+
/**
31+
* Validates if a PostgreSQL-style query with $1, $2, ... placeholders
32+
* matches the number of parameters in the params array.
33+
* Ignores other placeholder styles like :name or ?.
34+
*
35+
* @param {string} query - SQL query with $n placeholders.
36+
* @param {any[]} params - Array of parameter values.
37+
* @param {(reason?: any) => void} [errCallback]
38+
* @returns {true} Returns true if valid.
39+
* @throws {Error} Throws if placeholders reference missing parameters or invalid indexes.
40+
*/
41+
export function validatePostgresParams(query, params, errCallback) {
42+
const placeholders = new Set();
43+
const matches = [...query.matchAll(/\$([0-9]+)/g)];
44+
45+
/** @param {string} msg */
46+
const sendError = (msg) => {
47+
const err = new Error(msg);
48+
if (typeof errCallback === 'function') return errCallback(err);
49+
throw err;
50+
};
51+
52+
for (const [, numStr] of matches) {
53+
const index = parseInt(numStr, 10);
54+
if (index <= 0) {
55+
sendError(`Invalid placeholder $${index}: must be greater than 0.`);
56+
}
57+
if (index > params.length) {
58+
sendError(`Query references $${index} but only ${params.length} parameter(s) provided.`);
59+
}
60+
placeholders.add(index);
61+
}
62+
63+
// Check for gaps: all numbers between 1 and max index must be present
64+
const maxIndex = placeholders.size > 0 ? Math.max(...placeholders) : 0;
65+
for (let i = 1; i <= maxIndex; i++) {
66+
if (!placeholders.has(i)) {
67+
sendError(`Placeholder $${i} is missing but higher placeholders exist.`);
68+
}
69+
}
70+
71+
return true;
72+
}

src/index.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { pg, sqlite3 } from './Modules.mjs';
2+
import * as Utils from './Utils.mjs';
23
import PuddySqlEvents from './PuddySqlEvents.mjs';
34
import PuddySqlInstance from './PuddySqlInstance.mjs';
45
import PuddySqlQuery from './PuddySqlQuery.mjs';
@@ -9,6 +10,7 @@ class PuddySql {
910
static Query = PuddySqlQuery;
1011
static Tags = PuddySqlTags;
1112
static Events = PuddySqlEvents;
13+
static Utils = Utils;
1214
static pg = pg;
1315
static sqlite3 = sqlite3;
1416

0 commit comments

Comments
 (0)