From 9732523ab21e69b630aadf10668684c1ed239ab5 Mon Sep 17 00:00:00 2001 From: Johannes Vogel Date: Thu, 12 Oct 2023 11:11:23 +0200 Subject: [PATCH 1/3] fix: preserve $count for result of SELECT --- db-service/lib/SQLService.js | 14 +++++++++++++- test/compliance/SELECT.test.js | 9 +++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index 89da54661..ec7fa8746 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -79,7 +79,10 @@ class SQLService extends DatabaseService { let rows = await ps.all(values) if (rows.length) if (cqn.SELECT.expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r)) - if (cqn.SELECT.count) rows.$count = await this.count(query, rows) + if (cqn.SELECT.count) { + // REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks + return _arrayWithCount(rows, await this.count(query, rows)) + } return cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1 ? rows[0] : rows } @@ -387,6 +390,15 @@ const _unquirked = q => { return q } +function _arrayWithCount(a, count) { + const _map = a.map + const map = (..._) => _arrayWithCount(_map.call(a, ..._), count) + return Object.defineProperties(a, { + $count: { value: count, enumerable: false, configurable: true, writable: true }, + map: { value: map, enumerable: false, configurable: true, writable: true } + }) +} + const sqls = new class extends SQLService { get factory() { return null } } cds.extend(cds.ql.Query).with( class { diff --git a/test/compliance/SELECT.test.js b/test/compliance/SELECT.test.js index 5eded8c15..50dc53a2a 100644 --- a/test/compliance/SELECT.test.js +++ b/test/compliance/SELECT.test.js @@ -299,8 +299,13 @@ describe('SELECT', () => { }) describe('count', () => { - test.skip('missing', () => { - throw new Error('not supported') + test('count is preserved with .map', async () => { + const query = SELECT.from('complex.Authors') + query.SELECT.count = true + const result = await query + assert.strictEqual(result.$count, 1) + const renamed = result.map(row => ({key: row.ID, fullName: row.name})) + assert.strictEqual(renamed.$count, 1) }) }) From bb8f792304cd831ff8c96076e96ddf538703f179 Mon Sep 17 00:00:00 2001 From: Johannes Vogel Date: Thu, 12 Oct 2023 16:57:56 +0200 Subject: [PATCH 2/3] make _arrayWithCount a static function --- db-service/lib/SQLService.js | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/db-service/lib/SQLService.js b/db-service/lib/SQLService.js index ec7fa8746..afc2a8686 100644 --- a/db-service/lib/SQLService.js +++ b/db-service/lib/SQLService.js @@ -81,7 +81,7 @@ class SQLService extends DatabaseService { if (cqn.SELECT.expand) rows = rows.map(r => (typeof r._json_ === 'string' ? JSON.parse(r._json_) : r._json_ || r)) if (cqn.SELECT.count) { // REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks - return _arrayWithCount(rows, await this.count(query, rows)) + return SQLService._arrayWithCount(rows, await this.count(query, rows)) } return cqn.SELECT.one || query.SELECT.from?.ref?.[0].cardinality?.max === 1 ? rows[0] : rows } @@ -255,6 +255,17 @@ class SQLService extends DatabaseService { */ static CQN2SQL = require('./cqn2sql').class + // REVISIT: There must be a better way + // preserves $count for .map calls on array + static _arrayWithCount = function (a, count) { + const _map = a.map + const map = function (..._) { return SQLService._arrayWithCount(_map.call(a, ..._), count) } + return Object.defineProperties(a, { + $count: { value: count, enumerable: false, configurable: true, writable: true }, + map: { value: map, enumerable: false, configurable: true, writable: true } + }) + } + /** @param {unknown[]} args */ constructor(...args) { super(...args) @@ -390,14 +401,6 @@ const _unquirked = q => { return q } -function _arrayWithCount(a, count) { - const _map = a.map - const map = (..._) => _arrayWithCount(_map.call(a, ..._), count) - return Object.defineProperties(a, { - $count: { value: count, enumerable: false, configurable: true, writable: true }, - map: { value: map, enumerable: false, configurable: true, writable: true } - }) -} const sqls = new class extends SQLService { get factory() { return null } } cds.extend(cds.ql.Query).with( From cf5228851a82ac000754834c51d816fb718fc267 Mon Sep 17 00:00:00 2001 From: Johannes Vogel Date: Thu, 12 Oct 2023 16:58:08 +0200 Subject: [PATCH 3/3] use static function in HANA Service --- hana/lib/HANAService.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hana/lib/HANAService.js b/hana/lib/HANAService.js index a24b4669d..a04417da8 100644 --- a/hana/lib/HANAService.js +++ b/hana/lib/HANAService.js @@ -83,7 +83,10 @@ class HANAService extends SQLService { if (rows.length) { rows = this.parseRows(rows) } - if (cqn.SELECT.count) rows.$count = await this.count(query, rows) + if (cqn.SELECT.count) { + // REVISIT: the runtime always expects that the count is preserved with .map, required for renaming in mocks + return HANAService._arrayWithCount(rows, await this.count(query, rows)) + } return cqn.SELECT.one || query.SELECT.from.ref?.[0].cardinality?.max === 1 ? rows[0] || null : rows }