From 9e8f9f5fc1f07c8f4a51cb9d9178c1c77bfdb527 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Wed, 6 Jul 2022 21:00:40 +0300 Subject: [PATCH 01/23] feat: added where not null methods --- index.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/index.js b/index.js index c4414d7..19d6a14 100644 --- a/index.js +++ b/index.js @@ -116,16 +116,31 @@ class Pdo { return this; } + whereIsNotNull(clause) { + this._where = `${clause} IS NOT NULL`; + return this; + } + orWhereIsNull(clause) { this._where = `${this._where} OR ${clause} IS NULL`; return this; } + orWhereIsNotNull(clause) { + this._where = `${this._where} OR ${clause} IS NOT NULL`; + return this; + } + andWhereIsNull(clause) { this._where = `${this._where} AND ${clause} IS NULL`; return this; } + andWhereIsNotNull(clause) { + this._where = `${this._where} AND ${clause} IS NOT NULL`; + return this; + } + andWhere(clause, cond, value) { this._where = `${this._where} AND ${clause} ${cond} ${this.escapeData(value)}`; return this; From b127828b4ad5fb747eee2f64bf9ca3f0e8cfea11 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Wed, 6 Jul 2022 21:57:26 +0300 Subject: [PATCH 02/23] feat: up version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ba9c0f..e56f410 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.13", + "version": "0.0.14", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 3434ad1f0e9f61ab0756fd73a96c604ed102f04c Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Fri, 19 Aug 2022 06:41:51 +0300 Subject: [PATCH 03/23] add catch method --- index.js | 19 +++++++++++-------- package.json | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 19d6a14..6363d8c 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,12 @@ class Pdo { this.escape = options.escapeCb ?? encodeURI; this.unescape = options.unescapeCb ?? unescape; this.clean(); + this._catch = console.error; + } + + catch(fn) { + this._catch = fn; + return this; } clean() { @@ -236,8 +242,8 @@ class Pdo { case 'update': query = `UPDATE ${this._table} SET ${this._values?.map((value, i) => { - return `${this._columns[i]} = ${this.escapeData(value)}`; - }).join(',')}`; + return `${this._columns[i]} = ${this.escapeData(value)}`; + }).join(',')}`; if (this._where) { query += ` WHERE ${this._where}`; } @@ -256,15 +262,12 @@ class Pdo { return query; } - execute() { + async execute() { const query = this.getQuery(); try { - return this.client.unsafe(query); + return await this.client.unsafe(query); } catch (e) { - throw { - error: e, - query, - } + this._catch(e); } } } diff --git a/package.json b/package.json index e56f410..a1427f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.14", + "version": "0.0.15", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 1d4cdfb66f871871e96f460f6496bbc33e629ad6 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Sun, 25 Sep 2022 23:41:01 +0300 Subject: [PATCH 04/23] multiply leftJoin --- index.js | 14 ++++++++------ package.json | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 6363d8c..665f2f3 100644 --- a/index.js +++ b/index.js @@ -20,7 +20,7 @@ class Pdo { this._values = null; this._groupBy = null; this._select = null; - this._join = null; + this._join = []; this._orderBy = null; this._orderDirection = null; this._limit = null; @@ -108,7 +108,7 @@ class Pdo { } leftJoin(joinTable, field1, cond, field2) { - this._join = `LEFT JOIN ${joinTable} ON ${field1} ${cond} ${field2}`; + this._join.push(`LEFT JOIN ${joinTable} ON ${field1} ${cond} ${field2}`); return this; } @@ -212,8 +212,10 @@ class Pdo { case 'select': query = `SELECT ${this.join(this._select)} FROM ${this._table}`; - if (this._join) { - query += ` ${this._join}`; + if (this._join.length) { + this._join.map(str => { + query += ` ${str}`; + }); } if (this._where) { query += ` WHERE ${this._where}`; @@ -242,8 +244,8 @@ class Pdo { case 'update': query = `UPDATE ${this._table} SET ${this._values?.map((value, i) => { - return `${this._columns[i]} = ${this.escapeData(value)}`; - }).join(',')}`; + return `${this._columns[i]} = ${this.escapeData(value)}`; + }).join(',')}`; if (this._where) { query += ` WHERE ${this._where}`; } diff --git a/package.json b/package.json index a1427f5..5a2ebed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.15", + "version": "0.0.16", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From bdd5425eb45cb8a8d68142d5442f3950f79a91f9 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Mon, 26 Sep 2022 00:27:22 +0300 Subject: [PATCH 05/23] where skipEscape --- index.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 665f2f3..1644141 100644 --- a/index.js +++ b/index.js @@ -112,8 +112,8 @@ class Pdo { return this; } - where(clause, cond, value) { - this._where = `${clause} ${cond} ${this.escapeData(value)}`; + where(clause, cond, value, skipEscape = false) { + this._where = `${clause} ${cond} ${skipEscape ? value : this.escapeData(value)}`; return this; } diff --git a/package.json b/package.json index 5a2ebed..d5a730d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.16", + "version": "0.0.17", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From fea0c537ef085e92c553b57c0d686317dd1076d9 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Thu, 13 Oct 2022 11:13:06 +0300 Subject: [PATCH 06/23] conflictDoUpdate, conflictDoNothing --- index.js | 22 ++++++++++++++++++++-- package.json | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 1644141..dd6c124 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,7 @@ class Pdo { this._limit = null; this._offset = null; this._returning = null; + this._conflict = null; } select(select = ['*']) { @@ -167,6 +168,17 @@ class Pdo { return this; } + conflictDoNothing() { + this._conflict = `DO NOTHING`; + return this; + } + + conflictDoUpdate(clause, pairs) { + this._conflict = `(${clause}) DO UPDATE SET ${Object.keys(pairs).map(column => + (`${column} = ${this.escapeData(pairs[column])}`)).join(',')}`; + return this; + } + groupBy(groupBy) { this._groupBy = groupBy; return this; @@ -237,6 +249,9 @@ class Pdo { query = `INSERT INTO ${this._table} (${this._columns?.join(',')}) VALUES (${this.join(this._values?.map((v) => this.escapeData(v)))})`; + if (this._conflict) { + query += ` ON CONFLICT ${this._conflict}`; + } if (this._returning) { query += ` RETURNING ${this._returning}`; } @@ -244,8 +259,11 @@ class Pdo { case 'update': query = `UPDATE ${this._table} SET ${this._values?.map((value, i) => { - return `${this._columns[i]} = ${this.escapeData(value)}`; - }).join(',')}`; + return `${this._columns[i]} = ${this.escapeData(value)}`; + }).join(',')}`; + if (this._conflict) { + query += ` ON CONFLICT ${this._conflict}`; + } if (this._where) { query += ` WHERE ${this._where}`; } diff --git a/package.json b/package.json index d5a730d..39034c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.17", + "version": "0.0.18", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 9406a94c4d89d472fd660343c91709d6b34978c4 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Fri, 13 Jan 2023 15:59:34 +0300 Subject: [PATCH 07/23] added where braces for complex conditions --- index.js | 15 +++++++++++++++ package.json | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index dd6c124..acee814 100644 --- a/index.js +++ b/index.js @@ -168,6 +168,21 @@ class Pdo { return this; } + andWithBrace() { + this._where = `${this._where} AND (1 = 1`; + return this; + } + + orWithBrace() { + this._where = `${this._where} OR (1 = 1`; + return this; + } + + closeBrace() { + this._where = `${this._where})`; + return this; + } + conflictDoNothing() { this._conflict = `DO NOTHING`; return this; diff --git a/package.json b/package.json index 39034c9..24b9de1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.18", + "version": "0.0.19", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From e927b6a026b140c4daca58498a2226cab61d0a9d Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Fri, 27 Jan 2023 23:28:25 +0300 Subject: [PATCH 08/23] Added .andWhereNotIn --- index.js | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index acee814..6dc54fb 100644 --- a/index.js +++ b/index.js @@ -168,6 +168,11 @@ class Pdo { return this; } + andWhereNotIn(clause, arr) { + this._where = `${this._where} AND ${clause} NOT IN (${arr.map(this.escapeData).join(',')})`; + return this; + } + andWithBrace() { this._where = `${this._where} AND (1 = 1`; return this; diff --git a/package.json b/package.json index 24b9de1..fc9b2c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.19", + "version": "0.0.20", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 36c3f5f852ddba3e9ae2a02008491394a8d2fb31 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Fri, 27 Jan 2023 23:54:26 +0300 Subject: [PATCH 09/23] Added .setEscape --- index.js | 15 ++++++++++----- package.json | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 6dc54fb..195578a 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,11 @@ class Pdo { return this; } + setEscape(escape) { + this.escape = escape ?? encodeURI; + return this; + } + clean() { this._type = null; this._table = null; @@ -164,12 +169,12 @@ class Pdo { } andWhereIn(clause, arr) { - this._where = `${this._where} AND ${clause} IN (${arr.map(this.escapeData).join(',')})`; + this._where = `${this._where} AND ${clause} IN (${arr.map(item => this.escapeData(item)).join(',')})`; return this; } andWhereNotIn(clause, arr) { - this._where = `${this._where} AND ${clause} NOT IN (${arr.map(this.escapeData).join(',')})`; + this._where = `${this._where} AND ${clause} NOT IN (${arr.map(item => this.escapeData(item)).join(',')})`; return this; } @@ -218,7 +223,7 @@ class Pdo { escapeData(value) { try { - if (value === null) { + if (value === null || !this.escape) { return value; } else if (typeof value === "boolean") { return value; @@ -279,8 +284,8 @@ class Pdo { case 'update': query = `UPDATE ${this._table} SET ${this._values?.map((value, i) => { - return `${this._columns[i]} = ${this.escapeData(value)}`; - }).join(',')}`; + return `${this._columns[i]} = ${this.escapeData(value)}`; + }).join(',')}`; if (this._conflict) { query += ` ON CONFLICT ${this._conflict}`; } diff --git a/package.json b/package.json index fc9b2c4..b9b74d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.20", + "version": "0.0.21", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 3d6f59067b4b8dd91139fabf052a1ca424a79f3b Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Tue, 5 Mar 2024 15:43:53 +0300 Subject: [PATCH 10/23] Added return deleted rows --- index.js | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 195578a..e256961 100644 --- a/index.js +++ b/index.js @@ -302,6 +302,9 @@ class Pdo { if (this._where) { query += ` WHERE ${this._where}`; } + if (this._returning) { + query += ` RETURNING ${this._returning}`; + } break; } return query; diff --git a/package.json b/package.json index b9b74d8..b9869d2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.21", + "version": "0.0.22", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 07a5c5a53332fc9a928dbfd8c1d5d6d6e0a448aa Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Wed, 29 May 2024 21:13:23 +0300 Subject: [PATCH 11/23] Added options getLogDuration --- index.js | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index e256961..4968b0b 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ class Pdo { this.unescape = options.unescapeCb ?? unescape; this.clean(); this._catch = console.error; + this._getLogDuration = options.getLogDuration; } catch(fn) { @@ -311,9 +312,14 @@ class Pdo { } async execute() { + const startTime = new Date().getTime(); const query = this.getQuery(); try { - return await this.client.unsafe(query); + const result = await this.client.unsafe(query); + if (this._getLogDuration) { + this._getLogDuration(query.replace(/\n/g, ' ').replace(/\s+/g, ' '), new Date().getTime() - startTime); + } + return result; } catch (e) { this._catch(e); } diff --git a/package.json b/package.json index b9869d2..cd2d2ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.22", + "version": "0.0.23", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From cc852165799df783f7e8464314b51fb12eccf974 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Tue, 4 Jun 2024 15:32:43 +0300 Subject: [PATCH 12/23] Expand leftJoin to and clause --- index.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 4968b0b..5fe9912 100644 --- a/index.js +++ b/index.js @@ -114,8 +114,8 @@ class Pdo { return this; } - leftJoin(joinTable, field1, cond, field2) { - this._join.push(`LEFT JOIN ${joinTable} ON ${field1} ${cond} ${field2}`); + leftJoin(joinTable, field1, cond, field2, clause, condWhere, value) { + this._join.push(`LEFT JOIN ${joinTable} ON ${field1} ${cond} ${field2}${clause ? ` AND ${clause} ${condWhere} ${this.escapeData(value)}` : ''}`); return this; } diff --git a/package.json b/package.json index cd2d2ab..5c1c386 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.23", + "version": "0.0.24", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 9584a4c15e0f148435b5260bea63a0b1e98520fa Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Fri, 15 Nov 2024 14:13:53 +0300 Subject: [PATCH 13/23] Added having --- index.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/index.js b/index.js index 5fe9912..ee3f476 100644 --- a/index.js +++ b/index.js @@ -33,6 +33,7 @@ class Pdo { this._offset = null; this._returning = null; this._conflict = null; + this._having = null; } select(select = ['*']) { @@ -216,6 +217,21 @@ class Pdo { return this; } + having(clause, cond, value, skipEscape = false) { + this._having = `${clause} ${cond} ${skipEscape ? value : this.escapeData(value)}`; + return this; + } + + andHaving(clause, cond, value) { + this._having = `${this._having} AND ${clause} ${cond} ${this.escapeData(value)}`; + return this; + } + + orHaving(clause, cond, value) { + this._having = `${this._having} OR ${clause} ${cond} ${this.escapeData(value)}`; + return this; + } + limit(limit, offset = 0) { this._limit = limit; this._offset = offset; @@ -270,6 +286,9 @@ class Pdo { if (this._offset) { query += ` OFFSET ${this._offset}`; } + if (this._having) { + query += ` HAVING ${this._having}`; + } break; case 'insert': query = From 5b31067f6758438645b160cc2d8ea18ce7e0ba50 Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Fri, 15 Nov 2024 14:34:11 +0300 Subject: [PATCH 14/23] Added having --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5c1c386..3fd6616 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.24", + "version": "0.0.25", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 4f7ae6216845d9ea2d1b9660f1c99a3982fab51d Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Fri, 15 Nov 2024 15:20:37 +0300 Subject: [PATCH 15/23] Remove skipEscape in having --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index ee3f476..32715a5 100644 --- a/index.js +++ b/index.js @@ -217,8 +217,8 @@ class Pdo { return this; } - having(clause, cond, value, skipEscape = false) { - this._having = `${clause} ${cond} ${skipEscape ? value : this.escapeData(value)}`; + having(clause, cond, value) { + this._having = `${clause} ${cond} ${this.escapeData(value)}`; return this; } From 4b384eb1c3c8a7ff09d47f15fefbd64f2c575a13 Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Fri, 15 Nov 2024 17:48:49 +0300 Subject: [PATCH 16/23] Fix ordering having of sql query during conversion --- index.js | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 32715a5..2f295d6 100644 --- a/index.js +++ b/index.js @@ -277,6 +277,9 @@ class Pdo { if (this._groupBy) { query += ` GROUP BY ${this._groupBy}`; } + if (this._having) { + query += ` HAVING ${this._having}`; + } if (this._orderBy) { query += ` ORDER BY ${this._orderBy} ${this._orderDirection}`; } @@ -286,9 +289,7 @@ class Pdo { if (this._offset) { query += ` OFFSET ${this._offset}`; } - if (this._having) { - query += ` HAVING ${this._having}`; - } + break; case 'insert': query = diff --git a/package.json b/package.json index 3fd6616..ed8e3b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.0.25", + "version": "0.0.26", "description": "Postgres Data Objects", "main": "index.js", "scripts": { From 7b8e9aeaec5790014feed45b6885ce786a045205 Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Mon, 18 Nov 2024 11:22:39 +0300 Subject: [PATCH 17/23] Fix added skipEscape for having --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 32715a5..ee3f476 100644 --- a/index.js +++ b/index.js @@ -217,8 +217,8 @@ class Pdo { return this; } - having(clause, cond, value) { - this._having = `${clause} ${cond} ${this.escapeData(value)}`; + having(clause, cond, value, skipEscape = false) { + this._having = `${clause} ${cond} ${skipEscape ? value : this.escapeData(value)}`; return this; } From f8df3aa14bd64773f6f399eb3d735683f8cd4d86 Mon Sep 17 00:00:00 2001 From: wowDaiver Date: Mon, 2 Dec 2024 15:11:26 +0300 Subject: [PATCH 18/23] add types --- index.d.ts | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 9 +++-- 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 index.d.ts diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..2479832 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,95 @@ +import postgres = require("postgres"); + +interface IPdoOptions { + escapeCb?: (sqlUnsavedQuery: string) => string; + unescapeCb?: (sqlUnsavedQuery: string) => string; +} + +export class Pdo { + client: postgres.Sql; + escape: (sqlUnsavedQuery: string) => string; + + unescape: (sqlUnsavedQuery: string) => string; + + constructor(client: postgres.Sql, options: IPdoOptions) + + catch(fn: (error: any) => void): Pdo; + + setEscape(escape?: (sqlUnsavedQuery: string) => string): Pdo; + + clean() : void + + select(select?: string[]): Pdo + + from(table: string): Pdo + + + update(pairs: { [key: string]: any } | null): Pdo + + set(pairs: { [key: string]: any } ): Pdo; + + table(table: string): Pdo; + + insert(columns: { [key: string]: any } | string[] | null): Pdo; + + returning(id: string | string[]): Pdo; + + columns(columns: string[]): Pdo; + + values(values: string[]): Pdo; + + into(table: string): Pdo; + + delete(table: string): Pdo; + + leftJoin(joinTable: string, field1: string, cond: string, field2: string, clause?: string, condWhere?: string, value?: any): Pdo; + + where(clause: string, cond: string, value: string, skipEscape?: boolean): Pdo; + + whereIsNull(clause: string): Pdo; + + whereIsNotNull(clause: string): Pdo; + + orWhereIsNull(clause: string): Pdo; + + orWhereIsNotNull(clause: string): Pdo; + + andWhereIsNull(clause: string): Pdo; + + andWhereIsNotNull(clause: string): Pdo; + + andWhere(clause: string, cond: string, value: string): Pdo; + + orWhere(clause: string, cond: string, value: string): Pdo; + + whereIn(clause: string, arr: string[]): Pdo; + + andWhereIn(clause:string, arr: string[]): Pdo; + + andWhereNotIn(clause: string, arr: string[]): Pdo; + + andWithBrace() : Pdo; + + orWithBrace() : Pdo; + + closeBrace() : Pdo; + + having(clause: string, cond: string, value: string, skipEscape?: boolean): Pdo; + + conflictDoNothing(): Pdo; + + conflictDoUpdate(clause: string, pairs: { [key: string]: any }): Pdo; + + groupBy(groupBy: string): Pdo; + + orderBy(column: string, direction: 'DESC' | 'ASC'): Pdo; + + limit(limit: number, offset?: number): Pdo; + + escapeData(value: any): string; + join(arr: string[]): string; + + getQuery(): string | undefined; + + execute(): Promise; +} diff --git a/package.json b/package.json index ed8e3b6..1fa6f62 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { "name": "postgres-pdo", - "version": "0.0.26", + "version": "0.1.0", "description": "Postgres Data Objects", "main": "index.js", + "types": "index.d.ts", + "keywords": ["postgres", "pdo", "database", "typescript"], "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -15,5 +17,8 @@ "bugs": { "url": "https://github.com/wowDaiver/postgres-pdo/issues" }, - "homepage": "https://github.com/wowDaiver/postgres-pdo#readme" + "homepage": "https://github.com/wowDaiver/postgres-pdo#readme", + "dependencies": { + "postgres": "^3.4.5" + } } From 2dc0b000496a569c0617242dd08be9235a9466ed Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Mon, 2 Dec 2024 23:26:13 +0300 Subject: [PATCH 19/23] Added sub queries --- index.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 46403f1..35a67b8 100644 --- a/index.js +++ b/index.js @@ -34,6 +34,7 @@ class Pdo { this._returning = null; this._conflict = null; this._having = null; + this._subQueries = []; } select(select = ['*']) { @@ -238,6 +239,16 @@ class Pdo { return this; } + with(subQueries) { + if (Array.isArray(subQueries)) + this._subQueries = [...subQueries]; + + if (typeof subQueries === 'string') + this._subQueries = [subQueries]; + + return this; + } + escapeData(value) { try { if (value === null || !this.escape) { @@ -289,7 +300,9 @@ class Pdo { if (this._offset) { query += ` OFFSET ${this._offset}`; } - + if (this._subQueries.length) { + query = `WITH ${this._subQueries.join(',')} ${query}`; + } break; case 'insert': query = @@ -301,6 +314,9 @@ class Pdo { if (this._returning) { query += ` RETURNING ${this._returning}`; } + if (this._subQueries.length) { + query = `WITH ${this._subQueries.join(',')} ${query}`; + } break; case 'update': query = `UPDATE ${this._table} @@ -316,6 +332,9 @@ class Pdo { if (this._returning) { query += ` RETURNING ${this._returning}`; } + if (this._subQueries.length) { + query = `WITH ${this._subQueries.join(',')} ${query}`; + } break; case 'delete': query = `DELETE @@ -326,6 +345,9 @@ class Pdo { if (this._returning) { query += ` RETURNING ${this._returning}`; } + if (this._subQueries.length) { + query = `WITH ${this._subQueries.join(',')} ${query}`; + } break; } return query; From b94c4e74278b83fbd86658012d00240fac23a8be Mon Sep 17 00:00:00 2001 From: "ilya.nosenko" Date: Tue, 3 Dec 2024 11:27:34 +0300 Subject: [PATCH 20/23] Added type and up version --- index.d.ts | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 2479832..ef3c483 100644 --- a/index.d.ts +++ b/index.d.ts @@ -86,6 +86,8 @@ export class Pdo { limit(limit: number, offset?: number): Pdo; + with(subQueries: string | string[]): Pdo; + escapeData(value: any): string; join(arr: string[]): string; diff --git a/package.json b/package.json index 1fa6f62..3827efc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.1.0", + "version": "0.1.1", "description": "Postgres Data Objects", "main": "index.js", "types": "index.d.ts", From c41f304c9176f7f6f5dd984e7f0294cbff3a3830 Mon Sep 17 00:00:00 2001 From: "a.perkhunov" Date: Wed, 22 Apr 2026 17:23:15 +0300 Subject: [PATCH 21/23] 1.0.2 trx --- README.md | 51 ++++++++++++++++++++++++++++++++++ index.d.ts | 23 +++++++++++++++- index.js | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 +-- test.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 test.js diff --git a/README.md b/README.md index 3296855..efa8bab 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,54 @@ await pdo .where('id', '>', 1) .execute(); ``` + +###### Transactions + +```aidl +await pdo.transaction(async (trxPdo) => { + const user = await trxPdo + .select() + .from('users') + .where('uid', '=', 1) + .forUpdate() + .first() + .execute(); + + await trxPdo + .update({ + name: 'Updated' + }) + .table('users') + .where('uid', '=', user.uid) + .execute(); +}); +``` + +###### Row Locks + +```aidl +const row = await pdo + .select() + .from('jobs') + .where('status', '=', 'pending') + .forUpdate() + .skipLocked() + .first() + .execute(); +``` + +Available helpers: + +- `forUpdate()` +- `forShare()` +- `skipLocked()` +- `noWait()` +- `first()` +- `returningOne()` + +###### Fork / withClient + +```aidl +const trxPdo = pdo.withClient(trx); +const isolatedBuilder = pdo.fork(); +``` diff --git a/index.d.ts b/index.d.ts index ef3c483..31b8cfb 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,6 +3,7 @@ import postgres = require("postgres"); interface IPdoOptions { escapeCb?: (sqlUnsavedQuery: string) => string; unescapeCb?: (sqlUnsavedQuery: string) => string; + getLogDuration?: (query: string, durationMs: number) => void; } export class Pdo { @@ -17,6 +18,14 @@ export class Pdo { setEscape(escape?: (sqlUnsavedQuery: string) => string): Pdo; + withClient(client: postgres.Sql | postgres.TransactionSql): Pdo; + + fork(client?: postgres.Sql | postgres.TransactionSql): Pdo; + + transaction( + fn: (trxPdo: Pdo, trx: postgres.TransactionSql) => Promise | TResult + ): Promise; + clean() : void select(select?: string[]): Pdo @@ -88,10 +97,22 @@ export class Pdo { with(subQueries: string | string[]): Pdo; + forUpdate(): Pdo; + + forShare(): Pdo; + + skipLocked(): Pdo; + + noWait(): Pdo; + + first(): Pdo; + + returningOne(id: string | string[]): Pdo; + escapeData(value: any): string; join(arr: string[]): string; getQuery(): string | undefined; - execute(): Promise; + execute(): Promise; } diff --git a/index.js b/index.js index 35a67b8..6c72631 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ class Pdo { constructor(client, options = {}) { this.client = client; + this._options = options; this.escape = options.escapeCb ?? encodeURI; this.unescape = options.unescapeCb ?? unescape; this.clean(); @@ -18,6 +19,30 @@ class Pdo { return this; } + withClient(client) { + const pdo = new Pdo(client, this._options); + pdo._catch = this._catch; + pdo._getLogDuration = this._getLogDuration; + pdo.escape = this.escape; + pdo.unescape = this.unescape; + return pdo; + } + + fork(client = this.client) { + return this.withClient(client); + } + + async transaction(fn) { + try { + return await this.client.begin(async trx => { + const trxPdo = this.withClient(trx); + return await fn(trxPdo, trx); + }); + } catch (e) { + this._catch(e); + } + } + clean() { this._type = null; this._table = null; @@ -35,6 +60,10 @@ class Pdo { this._conflict = null; this._having = null; this._subQueries = []; + this._lock = null; + this._skipLocked = false; + this._noWait = false; + this._first = false; } select(select = ['*']) { @@ -249,6 +278,39 @@ class Pdo { return this; } + forUpdate() { + this._lock = 'FOR UPDATE'; + return this; + } + + forShare() { + this._lock = 'FOR SHARE'; + return this; + } + + skipLocked() { + this._skipLocked = true; + return this; + } + + noWait() { + this._noWait = true; + return this; + } + + first() { + this._first = true; + if (!this._limit) { + this._limit = 1; + this._offset = 0; + } + return this; + } + + returningOne(id) { + return this.returning(id).first(); + } + escapeData(value) { try { if (value === null || !this.escape) { @@ -300,6 +362,15 @@ class Pdo { if (this._offset) { query += ` OFFSET ${this._offset}`; } + if (this._lock) { + query += ` ${this._lock}`; + } + if (this._skipLocked) { + query += ` SKIP LOCKED`; + } + if (this._noWait) { + query += ` NOWAIT`; + } if (this._subQueries.length) { query = `WITH ${this._subQueries.join(',')} ${query}`; } @@ -361,6 +432,9 @@ class Pdo { if (this._getLogDuration) { this._getLogDuration(query.replace(/\n/g, ' ').replace(/\s+/g, ' '), new Date().getTime() - startTime); } + if (this._first) { + return result?.[0]; + } return result; } catch (e) { this._catch(e); diff --git a/package.json b/package.json index 3827efc..dd0dbc9 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "postgres-pdo", - "version": "0.1.1", + "version": "0.1.2", "description": "Postgres Data Objects", "main": "index.js", "types": "index.d.ts", "keywords": ["postgres", "pdo", "database", "typescript"], "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node ./test.js" }, "repository": { "type": "git", diff --git a/test.js b/test.js new file mode 100644 index 0000000..cebbeb8 --- /dev/null +++ b/test.js @@ -0,0 +1,78 @@ +const assert = require('assert'); +const Pdo = require('./index'); + +function createMockClient() { + return { + queries: [], + async unsafe(query) { + this.queries.push(query); + return [{ id: 1, uid: 42 }]; + }, + async begin(fn) { + const trx = { + queries: [], + async unsafe(query) { + this.queries.push(query); + return [{ id: 2, uid: 99 }]; + }, + }; + return await fn(trx); + }, + }; +} + +(async () => { + const client = createMockClient(); + const pdo = new Pdo(client); + + const lockedRow = await pdo + .fork() + .select() + .from('users') + .where('uid', '=', 42) + .forUpdate() + .skipLocked() + .first() + .execute(); + + assert.deepStrictEqual(lockedRow, { id: 1, uid: 42 }); + assert.strictEqual( + client.queries[0], + "SELECT *\n FROM users WHERE uid = 42 LIMIT 1 FOR UPDATE SKIP LOCKED" + ); + + const returnedRow = await pdo + .fork() + .update({ name: 'Alice' }) + .table('users') + .where('uid', '=', 42) + .returningOne(['id', 'uid']) + .execute(); + + assert.deepStrictEqual(returnedRow, { id: 1, uid: 42 }); + assert.strictEqual( + client.queries[1], + "UPDATE users\n SET name = 'Alice' WHERE uid = 42 RETURNING id,uid" + ); + + const transactionResult = await pdo.transaction(async (trxPdo) => { + const row = await trxPdo + .select() + .from('users') + .where('uid', '=', 99) + .forUpdate() + .noWait() + .first() + .execute(); + + assert.deepStrictEqual(row, { id: 2, uid: 99 }); + return row.uid; + }); + + assert.strictEqual(transactionResult, 99); + + console.log('postgres-pdo tests passed'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); From b009faa46a95f59512fa63bef9a4e9971accd3f5 Mon Sep 17 00:00:00 2001 From: "a.perkhunov" Date: Wed, 22 Apr 2026 17:27:24 +0300 Subject: [PATCH 22/23] 1.0.3 added parallel --- index.js | 38 +++++++++++++++++++------------------- test.js | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 6c72631..4cad0e9 100644 --- a/index.js +++ b/index.js @@ -67,10 +67,10 @@ class Pdo { } select(select = ['*']) { - this.clean(); - this._type = 'select' - this._select = select; - return this; + const query = this.fork(); + query._type = 'select' + query._select = select; + return query; } from(table) { @@ -79,12 +79,12 @@ class Pdo { } update(pairs = null) { - this.clean(); - this._type = 'update'; + const query = this.fork(); + query._type = 'update'; if (pairs) { - this.set(pairs); + query.set(pairs); } - return this; + return query; } set(pairs) { @@ -103,19 +103,19 @@ class Pdo { } insert(columns = null) { - this.clean(); - this._type = 'insert'; + const query = this.fork(); + query._type = 'insert'; if (columns) { if (Array.isArray(columns)) { - this.columns(columns); + query.columns(columns); } else { - this.columns(Object.keys(columns)); - this.values(Object.values(columns)); + query.columns(Object.keys(columns)); + query.values(Object.values(columns)); } } else { - this.columns(columns); + query.columns(columns); } - return this; + return query; } returning(id) { @@ -139,10 +139,10 @@ class Pdo { } delete(table) { - this.clean(); - this._type = 'delete'; - this._table = table; - return this; + const query = this.fork(); + query._type = 'delete'; + query._table = table; + return query; } leftJoin(joinTable, field1, cond, field2, clause, condWhere, value) { diff --git a/test.js b/test.js index cebbeb8..b6f4728 100644 --- a/test.js +++ b/test.js @@ -71,6 +71,26 @@ function createMockClient() { assert.strictEqual(transactionResult, 99); + // Verify that parallel queries from the same pdo instance don't interfere + const client2 = createMockClient(); + const sharedPdo = new Pdo(client2); + + const stmt1 = sharedPdo.select().from('users').where('uid', '=', 1).first(); + const stmt2 = sharedPdo.select().from('payments').where('uuid', '=', 'x'); + + assert.strictEqual( + stmt1.getQuery(), + "SELECT *\n FROM users WHERE uid = 1 LIMIT 1" + ); + assert.strictEqual( + stmt2.getQuery(), + "SELECT *\n FROM payments WHERE uuid = 'x'" + ); + + const [r1, r2] = await Promise.all([stmt1.execute(), stmt2.execute()]); + assert.deepStrictEqual(r1, { id: 1, uid: 42 }); + assert.deepStrictEqual(r2, [{ id: 1, uid: 42 }]); + console.log('postgres-pdo tests passed'); })().catch((error) => { console.error(error); From f7510f117e6c384bbcbe45c4ef9d5cbc12f8e1e4 Mon Sep 17 00:00:00 2001 From: "a.perkhunov" Date: Wed, 22 Apr 2026 17:29:28 +0300 Subject: [PATCH 23/23] 1.0.3 added parallel --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd0dbc9..95669a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postgres-pdo", - "version": "0.1.2", + "version": "0.1.3", "description": "Postgres Data Objects", "main": "index.js", "types": "index.d.ts",