From 66a00cece77af3083918136c29134d686cf238c8 Mon Sep 17 00:00:00 2001 From: Dian Fay Date: Tue, 30 Apr 2019 18:58:04 -0400 Subject: [PATCH] feat: forUpdate and forShare option for find queries (closes #677) --- lib/statement/select.js | 5 ++ test/database/with.js | 31 ++++++++++ test/statement/select.js | 127 +++++++++++++++++++++++---------------- 3 files changed, 110 insertions(+), 53 deletions(-) diff --git a/lib/statement/select.js b/lib/statement/select.js index 476880d..23dc942 100644 --- a/lib/statement/select.js +++ b/lib/statement/select.js @@ -33,6 +33,8 @@ const Select = function (source, criteria = {}, options = {}) { this.pageLength = options.pageLength; this.single = options.single; this.stream = options.stream; + this.forUpdate = options.forUpdate; + this.forShare = options.forShare; switch (options.order) { case null: @@ -154,6 +156,9 @@ Select.prototype.format = function () { sql += `${this.order}`; + if (this.forUpdate) { sql += ' FOR UPDATE'; } + if (this.forShare) { sql += ' FOR SHARE'; } + if (this.pageLength) { sql += ` FETCH FIRST ${this.pageLength} ROWS ONLY`; } if (this.offset) { sql += ' OFFSET ' + this.offset; } diff --git a/test/database/with.js b/test/database/with.js index 9736c14..f630b01 100644 --- a/test/database/with.js +++ b/test/database/with.js @@ -157,6 +157,37 @@ describe('transactions', function () { }); }); + it('selects for update with sorting and limiting', function () { + return db.withTransaction(tx => { + let promise = tx.products.insert({string: 'alpha'}); + + promise = promise.then(record => { + assert.isOk(record); + assert.isTrue(record.id > 0); + assert.equal(record.string, 'alpha'); + + return tx.products.findOne({id: record.id}, { + order: [{field: 'string', direction: 'desc'}], + forUpdate: true + }); + }); + + return promise; + }, { + mode: new db.pgp.txMode.TransactionMode({ + tiLevel: db.pgp.txMode.isolationLevel.serializable + }) + }).then(record => { + assert.isOk(record); + assert.isTrue(record.id > 0); + assert.equal(record.string, 'alpha'); + + return db.products.find(record.id).then(persisted => { + assert.isOk(persisted); + }); + }); + }); + it('reloads and applies DDL', function () { return db.withTransaction(co.wrap(function* (tx) { yield tx.query('create table test1 (id serial not null primary key, val text not null)'); diff --git a/test/statement/select.js b/test/statement/select.js index b65cd8b..0237c0d 100644 --- a/test/statement/select.js +++ b/test/statement/select.js @@ -39,78 +39,99 @@ describe('Select', function () { assert.equal(result.format(), 'SELECT * FROM ONLY testsource WHERE TRUE ORDER BY 1'); }); - it('should interpolate fields', function () { - const result = new Select(source, {}, {fields: ['col1']}); - assert.equal(result.format(), 'SELECT "col1" FROM testsource WHERE TRUE ORDER BY 1'); - }); + describe('fields', function () { + it('should interpolate fields', function () { + const result = new Select(source, {}, {fields: ['col1']}); + assert.equal(result.format(), 'SELECT "col1" FROM testsource WHERE TRUE ORDER BY 1'); + }); - it('should join arrays', function () { - const result = new Select(source, {}, {fields: ['col1', 'col2']}); - assert.equal(result.format(), 'SELECT "col1","col2" FROM testsource WHERE TRUE ORDER BY 1'); - }); + it('should join arrays', function () { + const result = new Select(source, {}, {fields: ['col1', 'col2']}); + assert.equal(result.format(), 'SELECT "col1","col2" FROM testsource WHERE TRUE ORDER BY 1'); + }); - it('should parse JSON fields', function () { - const result = new Select(source, {}, { - fields: [ - 'field.element', - 'field.array[0]', - 'field.array[1].nested[2].element' - ] + it('should parse JSON fields', function () { + const result = new Select(source, {}, { + fields: [ + 'field.element', + 'field.array[0]', + 'field.array[1].nested[2].element' + ] + }); + + assert.equal(result.format(), `SELECT "field"->>'element',"field"#>>'{array,0}',"field"#>>'{array,1,nested,2,element}' FROM testsource WHERE TRUE ORDER BY 1`); }); - assert.equal(result.format(), `SELECT "field"->>'element',"field"#>>'{array,0}',"field"#>>'{array,1,nested,2,element}' FROM testsource WHERE TRUE ORDER BY 1`); - }); + it('should alias fields in document mode', function () { + const result = new Select(source, {}, { + fields: ['one', 'two'], + document: true + }); - it('should alias fields in document mode', function () { - const result = new Select(source, {}, { - fields: ['one', 'two'], - document: true + assert.equal(result.format(), `SELECT "body"->>'one' AS "one","body"->>'two' AS "two",id FROM testsource WHERE TRUE ORDER BY 1`); }); - assert.equal(result.format(), `SELECT "body"->>'one' AS "one","body"->>'two' AS "two",id FROM testsource WHERE TRUE ORDER BY 1`); - }); + it('should add expressions', function () { + const result = new Select(source, {}, { + exprs: { + colsum: 'col1 + col2', + coldiff: 'col1 - col2' + } + }); - it('should add expressions', function () { - const result = new Select(source, {}, { - exprs: { - colsum: 'col1 + col2', - coldiff: 'col1 - col2' - } + assert.equal(result.format(), 'SELECT col1 + col2 AS "colsum",col1 - col2 AS "coldiff" FROM testsource WHERE TRUE ORDER BY 1'); }); - assert.equal(result.format(), 'SELECT col1 + col2 AS "colsum",col1 - col2 AS "coldiff" FROM testsource WHERE TRUE ORDER BY 1'); + it('should add fields and expressions', function () { + const result = new Select(source, {}, { + fields: ['col1', 'col2'], + exprs: { + colsum: 'col1 + col2', + coldiff: 'col1 - col2' + } + }); + + assert.equal(result.format(), 'SELECT "col1","col2",col1 + col2 AS "colsum",col1 - col2 AS "coldiff" FROM testsource WHERE TRUE ORDER BY 1'); + }); }); - it('should add fields and expressions', function () { - const result = new Select(source, {}, { - fields: ['col1', 'col2'], - exprs: { - colsum: 'col1 + col2', - coldiff: 'col1 - col2' - } + describe('for update/for share', function () { + it('adds FOR UPDATE', function () { + const result = new Select(source, {}, {forUpdate: true}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 FOR UPDATE'); }); - assert.equal(result.format(), 'SELECT "col1","col2",col1 + col2 AS "colsum",col1 - col2 AS "coldiff" FROM testsource WHERE TRUE ORDER BY 1'); - }); + it('adds FOR SHARE', function () { + const result = new Select(source, {}, {forShare: true}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 FOR SHARE'); + }); - it('should add an offset', function () { - const result = new Select(source, {}, {offset: 10}); - assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 OFFSET 10'); + it('applies limits with a FOR', function () { + const result = new Select(source, {}, {forUpdate: true, limit: 1}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 FOR UPDATE LIMIT 1'); + }); }); - it('should limit single queries to one result', function () { - const result = new Select(source, {}, {single: true}); - assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 LIMIT 1'); - }); + describe('offset and limit', function () { + it('should add an offset', function () { + const result = new Select(source, {}, {offset: 10}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 OFFSET 10'); + }); - it('should add a limit', function () { - const result = new Select(source, {}, {limit: 10}); - assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 LIMIT 10'); - }); + it('should limit single queries to one result', function () { + const result = new Select(source, {}, {single: true}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 LIMIT 1'); + }); - it('should add both offset and limit', function () { - const result = new Select(source, {}, {offset: 10, limit: 10}); - assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 OFFSET 10 LIMIT 10'); + it('should add a limit', function () { + const result = new Select(source, {}, {limit: 10}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 LIMIT 10'); + }); + + it('should add both offset and limit', function () { + const result = new Select(source, {}, {offset: 10, limit: 10}); + assert.equal(result.format(), 'SELECT * FROM testsource WHERE TRUE ORDER BY 1 OFFSET 10 LIMIT 10'); + }); }); describe('keyset pagination', function () {