From ff629e49f7ff792ef35aa01a79c902872cd31373 Mon Sep 17 00:00:00 2001 From: muxator Date: Mon, 28 Aug 2017 16:09:59 +0200 Subject: [PATCH 1/3] centralized the UPSERT statement in a variable This is a preparatory commit to centralize the UPSERT statement before refactoring it. Refactoring only: no functional changes. --- postgres_db.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/postgres_db.js b/postgres_db.js index 3d70b7b2..6b2da4cd 100644 --- a/postgres_db.js +++ b/postgres_db.js @@ -55,6 +55,8 @@ exports.database.prototype.init = function(callback) var _this = this; + _this.upsertStatement = "SELECT ueberdb_insert_or_update($1,$2)"; + this.db.query(testTableExists, function(err, result) { if (result.rows.length == 0) { _this.db.query(createTable, callback); @@ -117,7 +119,7 @@ exports.database.prototype.set = function (key, value, callback) } else { - this.db.query("SELECT ueberdb_insert_or_update($1,$2)", [key,value], callback); + this.db.query(_this.upsertStatement, [key,value], callback); } } @@ -160,7 +162,7 @@ exports.database.prototype.doBulk = function (bulk, callback) { if (!replaceVALs.length < 1) { for (var v in replaceVALs) { - _this.db.query("SELECT ueberdb_insert_or_update($1,$2)", replaceVALs[v], callback); + _this.db.query(_this.upsertStatement, replaceVALs[v], callback); } } else { callback(); From cc622362fc178feecd25897b54c31d223626bb46 Mon Sep 17 00:00:00 2001 From: muxator Date: Mon, 28 Aug 2017 16:11:07 +0200 Subject: [PATCH 2/3] when available, use native UPSERT Postgresql >= 9.5 and CockroachDB natively support UPSERT statements (ON CONFLICT ... DO UPDATE). - when initializing the library, we run an EXPLAIN INSERT ... ON CONFLICT to check if the construct is supported - if this succeeds, the UPSERT will be done done natively via ON CONFLICT ... DO UPDATE (Postgresql >= 9.5 and CockroachDB) - if this fails, we will continue using the old behaviour of emulating UPSERT via ueberdb_insert_or_update() (Postgresql <= 9.4) Among other things, this commit makes UeberDB compatible with CockroachDB --- postgres_db.js | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/postgres_db.js b/postgres_db.js index 6b2da4cd..54fce59a 100644 --- a/postgres_db.js +++ b/postgres_db.js @@ -51,17 +51,51 @@ exports.database.prototype.init = function(callback) "END; " + "$$ LANGUAGE plpgsql;"; - this.db.query(createFunc, []); - var _this = this; - _this.upsertStatement = "SELECT ueberdb_insert_or_update($1,$2)"; + // this variable will be given a value depending on the result of the + // feature detection + _this.upsertStatement = null; + + /* + * - Detects if this Postgres version supports INSERT .. ON CONFLICT + * UPDATE (PostgreSQL >= 9.5 and CockroachDB) + * - If upsert is not supported natively, creates in the DB a pl/pgsql + * function that emulates it + * - Performs a side effect, setting _this.upsertStatement to the sql + * statement that needs to be used, based on the detection result + * - calls the callback + */ + function detectUpsertMethod(callback) { + var upsertViaFunction = "SELECT ueberdb_insert_or_update($1,$2)"; + var upsertNatively = "INSERT INTO store(key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value"; + + var testNativeUpsert = "EXPLAIN " + upsertNatively; + + _this.db.query(testNativeUpsert, ["test-key", "test-value"], function(err, results) { + if (err) { + // the UPSERT statement failed: we will have to emulate it via + // an sql function + _this.upsertStatement = upsertViaFunction; + + // actually create the emulation function + _this.db.query(createFunc, [], callback); + + return; + } + + // if we get here, the EXPLAIN UPSERT succeeded, and we can use a + // native UPSERT + _this.upsertStatement = upsertNatively; + callback(); + }); + } this.db.query(testTableExists, function(err, result) { if (result.rows.length == 0) { - _this.db.query(createTable, callback); + _this.db.query(createTable, detectUpsertMethod(callback)); } else { - callback(); + detectUpsertMethod(callback); } }); } From ff3a45cf776c15c2d91366459e69cec479b7d386 Mon Sep 17 00:00:00 2001 From: muxator Date: Mon, 28 Aug 2017 16:11:39 +0200 Subject: [PATCH 3/3] put the sql function creation statement into an inner scope --- postgres_db.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/postgres_db.js b/postgres_db.js index 54fce59a..bf5576f1 100644 --- a/postgres_db.js +++ b/postgres_db.js @@ -39,18 +39,6 @@ exports.database.prototype.init = function(callback) '"value" text NOT NULL, ' + 'CONSTRAINT store_pkey PRIMARY KEY (key))'; - var createFunc = "CREATE OR REPLACE FUNCTION ueberdb_insert_or_update(character varying, text) " + - "RETURNS void AS $$ " + - "BEGIN " + - " IF EXISTS( SELECT * FROM store WHERE key = $1 ) THEN " + - " UPDATE store SET value = $2 WHERE key = $1; " + - " ELSE " + - " INSERT INTO store(key,value) VALUES( $1, $2 ); " + - " END IF; "+ - " RETURN; " + - "END; " + - "$$ LANGUAGE plpgsql;"; - var _this = this; // this variable will be given a value depending on the result of the @@ -69,6 +57,17 @@ exports.database.prototype.init = function(callback) function detectUpsertMethod(callback) { var upsertViaFunction = "SELECT ueberdb_insert_or_update($1,$2)"; var upsertNatively = "INSERT INTO store(key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = excluded.value"; + var createFunc = "CREATE OR REPLACE FUNCTION ueberdb_insert_or_update(character varying, text) " + + "RETURNS void AS $$ " + + "BEGIN " + + " IF EXISTS( SELECT * FROM store WHERE key = $1 ) THEN " + + " UPDATE store SET value = $2 WHERE key = $1; " + + " ELSE " + + " INSERT INTO store(key,value) VALUES( $1, $2 ); " + + " END IF; "+ + " RETURN; " + + "END; " + + "$$ LANGUAGE plpgsql;"; var testNativeUpsert = "EXPLAIN " + upsertNatively;