From 92fd48a6ef461f95fcc7444aa3221d4fc78e2bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 16 Sep 2020 09:30:18 +0200 Subject: [PATCH 1/9] Add replicaset support --- README.md | 31 ++++++++++ docker-compose/docker-compose.yml | 26 ++++++++ index.js | 2 + package.json | 1 + replicaset.js | 67 +++++++++++++++++++++ tests/replicaset.test.js | 98 +++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+) create mode 100644 docker-compose/docker-compose.yml create mode 100644 replicaset.js create mode 100644 tests/replicaset.test.js diff --git a/README.md b/README.md index 6161771..3859585 100644 --- a/README.md +++ b/README.md @@ -127,4 +127,35 @@ update.withDetailById(id) - `composeQb` - `composeQb(options, qb => ...)` automatic wrap for composing multiple querybuilders in different layers of the application. Prevents qb option overwriting. +### Replicas + +- create a sql replicaset supported knex isntance + ```js + const { replicaset } = require('databless'); + const knex = replicaset.initKnex({ + writeNodes: [/* Knex.Config */], + readNodes: [/* Knex.Config */], + proxy: { client: /* Knex.Config['client'] */ }, + select: replicaset.createRoundRobinSelectionStrategy(), + }); + ``` + + - `proxy` is a "virtual knex" instance, that - when it comes to executing + a query - selects a real knex instance via `select` method and executes query on that knex instance. + - `select` is any fn `(instances: Knex[], isWriteQuery: bool) => Knex)`, + you can use predefined `replicaset.createRoundRobinSelectionStrategy()` as + a default Round Robin selector. +- acessing read/write Knex instances directly + - using proxy knex instance + ```js + replicaset.writeReplicas(knex) // Knex[] + replicaset.readReplicas(knex) // Knex[] + ``` +- destroying proxy instance destroys all underlying knex instances +- testing (`docker-compose` required): + - `cd docker-compose` + - `docker-compose up` + - remove `skip` from `/tests/replicaset.test.js` + - `npm t` + diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml new file mode 100644 index 0000000..d7b7325 --- /dev/null +++ b/docker-compose/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3' +services: + postgres1: + image: postgres:11 + environment: + POSTGRES_USER: databless + POSTGRES_PASSWORD: databless + POSTGRES_DB: databless + ports: + - "10001:5432" + postgres2: + image: postgres:11 + environment: + POSTGRES_USER: databless + POSTGRES_PASSWORD: databless + POSTGRES_DB: databless + ports: + - "10002:5432" + postgres3: + image: postgres:11 + environment: + POSTGRES_USER: databless + POSTGRES_PASSWORD: databless + POSTGRES_DB: databless + ports: + - "10003:5432" \ No newline at end of file diff --git a/index.js b/index.js index 6161f68..372a9ef 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,12 @@ const initKnex = require('./initKnex'); +const replicaset = require('./replicaset'); const initBookshelf = require('./initBookshelf'); /* eslint-disable global-require */ module.exports = { initBookshelf, initKnex, + replicaset, registerBookshelfModels: require('./registerBookshelfModels'), defaultBookshelfRepository: require('./defaultBookshelfRepository'), getKnex: (key = 'default') => initKnex.knexInstances.get(key), diff --git a/package.json b/package.json index f0403d0..b95178b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "devDependencies": { "jest": "^24.5.0", "jest-extended": "^0.11.1", + "pg": "^8.3.3", "sqlite3": "^4.0.2" } } diff --git a/replicaset.js b/replicaset.js new file mode 100644 index 0000000..bfecb40 --- /dev/null +++ b/replicaset.js @@ -0,0 +1,67 @@ +// Based on https://github.com/knex/knex/issues/2253#issuecomment-551610832 + +const Knex = require('knex'); +const initKnex = require('./initKnex'); + +exports.createRoundRobinSelectionStrategy = () => { + let round = { + true: 0, + false: 0, + }; + return (pool, isWrite) => { + const selected = pool[round[isWrite]]; + round[isWrite] = (round[isWrite] + 1) % (pool.length); + return selected; + }; +}; + +exports.isWriteQuery = (query) => { + return ['insert', 'del', 'update'].includes(query.method); +}; + +exports.isWriteBuilder = (builder) => { + // Enable query context override: knex.select('*').queryContext({ replicaNode: 'write' | 'read' }) + if (builder._queryContext && 'replicaNode' in builder._queryContext) { + return builder._queryContext === 'write'; + } + const sql = builder.toSQL(); + return Array.isArray(sql) ? sql.some(exports.isWriteQuery) : exports.isWriteQuery(sql); +} + +/** + * + * @param {Object} config + * @param {Array} config.writeNodes List of knex configurations + * for SQL master instances + * @param {Array} config.readNodes List of knex configurations + * for SQL read-only instances + * @param {Knex.Config} config.proxy Knex configuration for "knex proxy" + * used as a single knex handle that ultimately chooses one of the read/write + * instances, based on given strategy. + * @param {(pool: Array, isWrite: bool) => Knex} config.select + * @param {*} key Datables knex instance key. + */ +exports.initKnex = (config = { writeNodes: [], readNodes: [], proxy: {}, select: createRoundRobinSelectionStrategy() }, key = 'default') => { + const createKnex = require('knex'); // eslint-disable-line global-require + config.select = config.select || this.createRoundRobinSelectionStrategy(); + const writeNodes = config.writeNodes.map(createKnex); + const readNodes = config.readNodes.map(createKnex); + const replicaKnex = initKnex(config.proxy, key); + replicaKnex.client.runner = function (builder) { + const useWriteNode = exports.isWriteBuilder(builder); + return config.select(useWriteNode ? writeNodes : readNodes, useWriteNode) + .client.runner(builder); + }; + replicaKnex.client.destroy = () => { + return Promise.all([ + ...writeNodes.map(node => node.client.destroy()), + ...readNodes.map(node => node.client.destroy()), + ]); + }; + replicaKnex.__rdbgwReplicaWriteNodes = writeNodes; + replicaKnex.__rdbgwReplicaReadNodes = readNodes; + return replicaKnex; +}; + +exports.writeReplicas = (knex) => knex.__rdbgwReplicaWriteNodes; +exports.readReplicas = (knex) => knex.__rdbgwReplicaReadNodes; diff --git a/tests/replicaset.test.js b/tests/replicaset.test.js new file mode 100644 index 0000000..faddff1 --- /dev/null +++ b/tests/replicaset.test.js @@ -0,0 +1,98 @@ +const databless = require('../index'); +const { replicaset } = require('../index'); + +describe.skip('Replicaset', () => { + let knex; + beforeAll(async () => { + replicaset.initKnex({ + writeNodes: [ + { + client: 'pg', + connection: { + host: 'localhost', + port: '10001', + user: 'databless', + password: 'databless', + }, + }, + ], + readNodes: [ + { + client: 'pg', + connection: { + host: 'localhost', + port: '10002', + user: 'databless', + password: 'databless', + }, + }, + { + client: 'pg', + connection: { + host: 'localhost', + port: '10003', + user: 'databless', + password: 'databless', + }, + }, + ], + proxy: { + client: 'pg', + }, + select: replicaset.createRoundRobinSelectionStrategy(), + }); + knex = databless.getKnex(); + // Prepare instances - purge & create some schema with a one row + // to identify an instance + await Promise.all( + [ + ['readInstance1', replicaset.readReplicas(knex)[0]], + ['readInstance2', replicaset.readReplicas(knex)[1]], + ['writeInstance1', replicaset.writeReplicas(knex)[0]], + ] + .map(async ([instance, knex]) => { + await knex.raw(` + DROP SCHEMA public CASCADE; + CREATE SCHEMA public; + `); + await knex.schema.createTable('records', table => { + table.increments('id').primary(); + table.string('title'); + }); + await knex('records').insert({ title: instance }); + }) + ); + }); + afterAll(async () => { + await knex.destroy(); + }) + test('RR for reads', async () => { + { + const result = await knex('records'); + // 1st read -> readInstance1 + expect(result.find(x => x.title === 'readInstance1')).not.toBeUndefined(); + } + { + const result = await knex('records'); + // 2nd read -> readInstance2 + expect(result.find(x => x.title === 'readInstance2')).not.toBeUndefined(); + } + { + const result = await knex('records'); + // 3rd read -> readInstance1 + expect(result.find(x => x.title === 'readInstance1')).not.toBeUndefined(); + } + { + await knex('records').insert({ title: 'inserted' }); + const result = await replicaset.writeReplicas(knex)[0]('records'); + // 1st write -> writeInstance1 + expect(result.find(x => x.title === 'inserted')).not.toBeUndefined(); + expect(result.find(x => x.title === 'writeInstance1')).not.toBeUndefined(); + } + { + const result = await knex('records'); + // 4th read -> readInstance2 + expect(result.find(x => x.title === 'readInstance2')).not.toBeUndefined(); + } + }); +}); \ No newline at end of file From 5702d0caea86ca6e7ba901afaefb03fe16ab8b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Tue, 22 Sep 2020 14:20:33 +0200 Subject: [PATCH 2/9] =?UTF-8?q?=E2=9C=A8=20Transacted=20queries=20are=20pa?= =?UTF-8?q?ssed=20to=20write=20replicas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- replicaset.js | 4 ++++ tests/replicaset.test.js | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/replicaset.js b/replicaset.js index bfecb40..cddfc7b 100644 --- a/replicaset.js +++ b/replicaset.js @@ -52,6 +52,10 @@ exports.initKnex = (config = { writeNodes: [], readNodes: [], proxy: {}, select: return config.select(useWriteNode ? writeNodes : readNodes, useWriteNode) .client.runner(builder); }; + replicaKnex.client.transaction = function (container, txConfig, outerTx) { + return config.select(writeNodes, true) + .client.transaction(container, txConfig, outerTx); + }; replicaKnex.client.destroy = () => { return Promise.all([ ...writeNodes.map(node => node.client.destroy()), diff --git a/tests/replicaset.test.js b/tests/replicaset.test.js index faddff1..a6c90b0 100644 --- a/tests/replicaset.test.js +++ b/tests/replicaset.test.js @@ -95,4 +95,21 @@ describe.skip('Replicaset', () => { expect(result.find(x => x.title === 'readInstance2')).not.toBeUndefined(); } }); + test('Transacted queries always use write instances', async () => { + const TRX_INSERT = { title: 'trx-insert' }; + await knex.transaction(async trx => { + await knex('records').transacting(trx) + .insert(TRX_INSERT); + { + const result = await knex('records').transacting(trx); + expect(result.find(x => x.title === TRX_INSERT.title)).not.toBeUndefined(); + expect(result.find(x => x.title === 'writeInstance1')).not.toBeUndefined(); + } + }); + // And non-trx queries are again from read instances + { + const result = await knex('records'); + expect(result.find(x => x.title === 'writeInstance1')).toBeUndefined(); + } + }); }); \ No newline at end of file From 48d6976c2bc9cf6590de6d1e89e1b08068fbce41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 23 Sep 2020 15:49:25 +0200 Subject: [PATCH 3/9] =?UTF-8?q?=E2=AC=87=EF=B8=8F=20Use=20pg=207.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b95178b..8781a69 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "devDependencies": { "jest": "^24.5.0", "jest-extended": "^0.11.1", - "pg": "^8.3.3", + "pg": "^7.18.2", "sqlite3": "^4.0.2" } } From 1c3fabd1deb0c6ce742a6822662c72ffcdf3e734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 23 Sep 2020 15:50:51 +0200 Subject: [PATCH 4/9] =?UTF-8?q?=E2=9C=A8=20Pass=20write=20node=20config=20?= =?UTF-8?q?to=20proxy=20knex=20instance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- replicaset.js | 1 + 1 file changed, 1 insertion(+) diff --git a/replicaset.js b/replicaset.js index cddfc7b..9b28f5b 100644 --- a/replicaset.js +++ b/replicaset.js @@ -62,6 +62,7 @@ exports.initKnex = (config = { writeNodes: [], readNodes: [], proxy: {}, select: ...readNodes.map(node => node.client.destroy()), ]); }; + replicaKnex.client.config = writeNodes[0].client.config; replicaKnex.__rdbgwReplicaWriteNodes = writeNodes; replicaKnex.__rdbgwReplicaReadNodes = readNodes; return replicaKnex; From befb3ffbf50fe3a1bcb9a93cb2193ce99f8a68d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 23 Sep 2020 16:11:10 +0200 Subject: [PATCH 5/9] =?UTF-8?q?=E2=9C=85=20Add=20bookshelf=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/replicaset.test.js | 9 ++- tests/replicasetbs.test.js | 138 +++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/replicasetbs.test.js diff --git a/tests/replicaset.test.js b/tests/replicaset.test.js index a6c90b0..c7d9c78 100644 --- a/tests/replicaset.test.js +++ b/tests/replicaset.test.js @@ -11,9 +11,11 @@ describe.skip('Replicaset', () => { connection: { host: 'localhost', port: '10001', + database: 'databless', user: 'databless', password: 'databless', }, + // debug: true, }, ], readNodes: [ @@ -24,6 +26,7 @@ describe.skip('Replicaset', () => { port: '10002', user: 'databless', password: 'databless', + database: 'databless', }, }, { @@ -33,6 +36,7 @@ describe.skip('Replicaset', () => { port: '10003', user: 'databless', password: 'databless', + database: 'databless', }, }, ], @@ -41,6 +45,7 @@ describe.skip('Replicaset', () => { }, select: replicaset.createRoundRobinSelectionStrategy(), }); + knex = databless.getKnex(); // Prepare instances - purge & create some schema with a one row // to identify an instance @@ -65,7 +70,7 @@ describe.skip('Replicaset', () => { }); afterAll(async () => { await knex.destroy(); - }) + }); test('RR for reads', async () => { { const result = await knex('records'); @@ -112,4 +117,4 @@ describe.skip('Replicaset', () => { expect(result.find(x => x.title === 'writeInstance1')).toBeUndefined(); } }); -}); \ No newline at end of file +}); diff --git a/tests/replicasetbs.test.js b/tests/replicasetbs.test.js new file mode 100644 index 0000000..0c69376 --- /dev/null +++ b/tests/replicasetbs.test.js @@ -0,0 +1,138 @@ +const databless = require('../index'); +const { replicaset } = require('../index'); +const { format: sprintf } = require('util') + +describe.skip('Replicaset - Bookshelf', () => { + let knex; + beforeAll(async () => { + replicaset.initKnex({ + writeNodes: [ + { + client: 'pg', + connection: { + host: 'localhost', + port: '10001', + database: 'databless', + user: 'databless', + password: 'databless', + }, + }, + ], + readNodes: [ + { + client: 'pg', + connection: { + host: 'localhost', + port: '10001', + database: 'databless', + user: 'databless', + password: 'databless', + }, + }, + ], + proxy: { + client: 'pg', + }, + select: replicaset.createRoundRobinSelectionStrategy(), + }); + + knex = databless.getKnex(); + // Prepare instances - purge & create some schema with a one row + // to identify an instance + await Promise.all( + [ + replicaset.writeReplicas(knex) + ] + .map(async ([knex]) => { + await knex.raw(` + DROP SCHEMA public CASCADE; + CREATE SCHEMA public; + `); + }) + ); + }); + afterAll(async () => { + await knex.destroy(); + }); + describe('Bookshelf integration', () => { + let Model; + beforeAll(async () => { + await knex.schema.createTable('records', table => { + table.increments('id').primary(); + table.string('title'); + }); + const registerModel = (bookshelf) => { + const Model = databless.getBookshelf().Model.extend({ + tableName: 'records', + + tags() { + return this.belongsToMany(Tag); + } + }); + return bookshelf.model('Model', Model); + } + const bookshelf = databless.initBookshelf(knex); + Model = registerModel(bookshelf); + }); + + afterAll(async () => { + await knex.destroy(); + }); + test('CRUD', async () => { + let createId; + // Create + { + const result = (await Model.forge({ title: 'bs-model-insert' }).save()).toJSON(); + expect(result.id).not.toBeUndefined(); + createId = result.id; + await mirror(knex); + } + // Read + { + const result = (await Model.forge({ id: createId }).fetch()); + expect(result.id).toEqual(createId); + } + // Update + { + (await Model.forge({ id: createId }) + .save({ title: 'bs-model-insert-updated' })).toJSON(); + await mirror(knex); + const result = (await Model.where({ id: createId }).fetch()).toJSON(); + expect(result.title).toEqual('bs-model-insert-updated'); + } + { + await Model.where({ id: createId }).destroy(); + await mirror(knex); + const result = (await Model.where({ id: createId }).fetch()); + expect(result).toEqual(null); + } + }); + // Unksip when MIGRATION_DIR and SEED_DIR is provided + describe.skip('Seeds', () => { + beforeAll(async () => { + await knex.migrate.latest({ directory: process.env.MIGRATION_DIR }); + }); + afterAll(() => { + // Maybe your seeds create always a new instance and cannot exist gracefully + process.exit(0); + }); + test('Run seeds', async () => { + await knex.seed.run({ directory: process.env.SEED_DIR }); + }) + }); + }); +}); + +async function mirror(replicaKnex) { + const source = replicaset.writeReplicas(replicaKnex)[0]; // ! only one + const dests = replicaset.readReplicas(replicaKnex); + const records = await source('records'); + await Promise.all( + dests.map(async dest => { + await dest.raw(sprintf('TRUNCATE TABLE %s', 'records')); + await Promise.all( + records.map(record => dest('records').insert(record)) + ); + }) + ); +} \ No newline at end of file From ab138d80419b33ade51e8986b4240ba83802fcec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 23 Sep 2020 16:12:31 +0200 Subject: [PATCH 6/9] =?UTF-8?q?=F0=9F=9A=A7=20Prepare=20to=20remove=20mock?= =?UTF-8?q?=20replica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- replicaset.js | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/replicaset.js b/replicaset.js index 9b28f5b..f5e0432 100644 --- a/replicaset.js +++ b/replicaset.js @@ -70,3 +70,44 @@ exports.initKnex = (config = { writeNodes: [], readNodes: [], proxy: {}, select: exports.writeReplicas = (knex) => knex.__rdbgwReplicaWriteNodes; exports.readReplicas = (knex) => knex.__rdbgwReplicaReadNodes; + + +// WIP POC of using first write replica as for proxy knex +// @experimental +exports.initKnexMasterAsProxy = (config = { writeNodes: [], readNodes: [], select: createRoundRobinSelectionStrategy() }, key = 'default') => { + const createKnex = require('knex'); // eslint-disable-line global-require + config.select = config.select || this.createRoundRobinSelectionStrategy(); + const replicaKnex = initKnex(config.writeNodes[0], key); + const writeNodes = config.writeNodes.slice(1).map(createKnex); + writeNodes.unshift(replicaKnex); + const readNodes = config.readNodes.map(createKnex); + + const originalClientRunner = replicaKnex.client.runner.bind(replicaKnex.client); + const originalClientTransaction = replicaKnex.client.transaction.bind(replicaKnex.client); + const originalClientDestroy = replicaKnex.client.destroy.bind(replicaKnex.client); + replicaKnex.client.runner = function (builder) { + const useWriteNode = exports.isWriteBuilder(builder); + let node = config.select(useWriteNode ? writeNodes : readNodes, useWriteNode); + if (node === replicaKnex) { + return originalClientRunner(builder); + } + return node.client.runner(builder); + }; + replicaKnex.client.transaction = function (container, txConfig, outerTx) { + let node = config.select(writeNodes, true) + if (node === replicaKnex) { + return originalClientTransaction(container, txConfig, outerTx); + } + return node.client.transaction(container, txConfig, outerTx); + }; + replicaKnex.client.destroy = () => { + return Promise.all([ + originalClientDestroy(), + ...writeNodes.slice(1).map(node => node.client.destroy()), + ...readNodes.map(node => node.client.destroy()), + ]); + }; + replicaKnex.__rdbgwReplicaWriteNodes = writeNodes; + replicaKnex.__rdbgwReplicaReadNodes = readNodes; + return replicaKnex; +}; \ No newline at end of file From 5e901d5c2d063c68c57ce9ce69a40ae4cf754ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 30 Sep 2020 07:31:03 +0200 Subject: [PATCH 7/9] =?UTF-8?q?=E2=9C=A8=20Truncate=20queries=20as=20write?= =?UTF-8?q?=20query?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonín Křivohlavý --- replicaset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/replicaset.js b/replicaset.js index f5e0432..95d0a93 100644 --- a/replicaset.js +++ b/replicaset.js @@ -16,7 +16,7 @@ exports.createRoundRobinSelectionStrategy = () => { }; exports.isWriteQuery = (query) => { - return ['insert', 'del', 'update'].includes(query.method); + return ['insert', 'del', 'update', 'truncate'].includes(query.method); }; exports.isWriteBuilder = (builder) => { @@ -110,4 +110,4 @@ exports.initKnexMasterAsProxy = (config = { writeNodes: [], readNodes: [], selec replicaKnex.__rdbgwReplicaWriteNodes = writeNodes; replicaKnex.__rdbgwReplicaReadNodes = readNodes; return replicaKnex; -}; \ No newline at end of file +}; From aab0db911c5e00078c7637f5ffcdd47a0ad54bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0mol=C3=ADk?= Date: Wed, 30 Sep 2020 12:03:26 +0200 Subject: [PATCH 8/9] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20query=20context'?= =?UTF-8?q?s=20replica=20node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Antonín Křivohlavý --- replicaset.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/replicaset.js b/replicaset.js index 95d0a93..386a158 100644 --- a/replicaset.js +++ b/replicaset.js @@ -22,7 +22,7 @@ exports.isWriteQuery = (query) => { exports.isWriteBuilder = (builder) => { // Enable query context override: knex.select('*').queryContext({ replicaNode: 'write' | 'read' }) if (builder._queryContext && 'replicaNode' in builder._queryContext) { - return builder._queryContext === 'write'; + return builder._queryContext.replicaNode === 'write'; } const sql = builder.toSQL(); return Array.isArray(sql) ? sql.some(exports.isWriteQuery) : exports.isWriteQuery(sql); From 2a3eca9908d373de4d108eb4bc63d400a077c80a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vlas=C3=A1k?= Date: Wed, 15 Sep 2021 15:19:58 +0200 Subject: [PATCH 9/9] =?UTF-8?q?=E2=AC=86=EF=B8=8FUpdate=20knex=20and=20pg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit noref #0 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8781a69..ee43666 100644 --- a/package.json +++ b/package.json @@ -39,13 +39,13 @@ "bookshelf-cursor-pagination": "^1.4.2", "bookshelf-paranoia": "^0.11.0", "desmond": "^0.5.6", - "knex": "^0.19.5", + "knex": "^0.21.21", "mysql": "^2.15.0" }, "devDependencies": { "jest": "^24.5.0", "jest-extended": "^0.11.1", - "pg": "^7.18.2", + "pg": "^8.7.1", "sqlite3": "^4.0.2" } }