From 6a09556f6fae253c0589d912359c9a71d2c3c361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Hillerstr=C3=B6m?= Date: Wed, 27 Jan 2021 18:18:10 +0100 Subject: [PATCH 1/2] Added mongoDB like search in embedded objects --- packages/adapter-commons/src/sort.ts | 29 ++++++++-- packages/adapter-commons/test/sort.test.ts | 65 ++++++++++++++++++++++ 2 files changed, 89 insertions(+), 5 deletions(-) diff --git a/packages/adapter-commons/src/sort.ts b/packages/adapter-commons/src/sort.ts index 5803a000c3..d83dcd6880 100644 --- a/packages/adapter-commons/src/sort.ts +++ b/packages/adapter-commons/src/sort.ts @@ -69,17 +69,36 @@ export function compare (a: any, b: any, compareStrings: any = exports.compareNS // An in-memory sorting function according to the // $sort special query parameter export function sorter ($sort: any) { - const criteria = Object.keys($sort).map(key => { - const direction = $sort[key]; + let sortLevels = false; // True if $sort has tags with '.' i.e. '{a: 1, b: -1, "c.x.z": 1}' + + const getVal = (a: any, sortKeys: any[]) => { + let keys = sortKeys.map(key => key); + let val = a; + do { + let key = keys.shift(); + val = val[key]; + } while (keys.length); + + return val; + }; + + const criteria = Object.keys($sort).map(key => { + const direction = $sort[key]; + const keys = key.split('.'); + sortLevels = keys.length > 1; - return { key, direction }; - }); + return { keys, direction }; + }); return function (a: any, b: any) { let compare; for (const criterion of criteria) { - compare = criterion.direction * exports.compare(a[criterion.key], b[criterion.key]); + if (sortLevels) { + compare = criterion.direction * exports.compare(getVal(a, criterion.keys), getVal(b, criterion.keys)); + } else { + compare = criterion.direction * exports.compare(a[criterion.keys[0]], b[criterion.keys[0]]); + } if (compare !== 0) { return compare; diff --git a/packages/adapter-commons/test/sort.test.ts b/packages/adapter-commons/test/sort.test.ts index 88e144b56c..1dde313091 100644 --- a/packages/adapter-commons/test/sort.test.ts +++ b/packages/adapter-commons/test/sort.test.ts @@ -176,4 +176,69 @@ describe('@feathersjs/adapter-commons', () => { ]); }); }); + + describe('sorter mongoDB-like sorting on embedded objects', () => { + let data: any[] = []; + + beforeEach(() => { + data = [ + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 } + ]; + + }); + + it('straight test', () => { + const sort = sorter({ + amount: -1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 } + ]); + }); + + it('embedded sort 1', () => { + const sort = sorter({ + "item.category": 1, + "item.type": 1, + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 } + ]); + }); + + it('embedded sort 2', () => { + const sort = sorter({ + "item.category": 1, + "item.type": 1, + amount: 1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 } + ]); + }); + }); + }); From 3b2e44672cc6200efdb5c8f64308336280430348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Hillerstr=C3=B6m?= Date: Sun, 14 Nov 2021 11:56:00 +0100 Subject: [PATCH 2/2] Fix sortLevel detection in sorter() --- packages/adapter-commons/src/sort.ts | 4 +- packages/adapter-commons/test/sort.test.ts | 49 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/packages/adapter-commons/src/sort.ts b/packages/adapter-commons/src/sort.ts index d83dcd6880..4ad85fd2b9 100644 --- a/packages/adapter-commons/src/sort.ts +++ b/packages/adapter-commons/src/sort.ts @@ -69,7 +69,7 @@ export function compare (a: any, b: any, compareStrings: any = exports.compareNS // An in-memory sorting function according to the // $sort special query parameter export function sorter ($sort: any) { - let sortLevels = false; // True if $sort has tags with '.' i.e. '{a: 1, b: -1, "c.x.z": 1}' + let sortLevels = 0; // > 0 if $sort has tags with '.' i.e. '{a: 1, b: -1, "c.x.z": 1}' const getVal = (a: any, sortKeys: any[]) => { let keys = sortKeys.map(key => key); @@ -85,7 +85,7 @@ export function sorter ($sort: any) { const criteria = Object.keys($sort).map(key => { const direction = $sort[key]; const keys = key.split('.'); - sortLevels = keys.length > 1; + sortLevels += (keys.length > 1) ? 1 : 0; return { keys, direction }; }); diff --git a/packages/adapter-commons/test/sort.test.ts b/packages/adapter-commons/test/sort.test.ts index 1dde313091..67964b57a5 100644 --- a/packages/adapter-commons/test/sort.test.ts +++ b/packages/adapter-commons/test/sort.test.ts @@ -239,6 +239,55 @@ describe('@feathersjs/adapter-commons', () => { { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 } ]); }); + + it('embedded sort 3', () => { + const sort = sorter({ + "item.category": 1, + "item.type": 1, + amount: -1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 } + ]); + }); + + it('embedded sort 4', () => { + const sort = sorter({ + amount: -1, + "item.category": 1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 } + ]); + }); + + it('embedded sort 5', () => { + const sort = sorter({ + "item.category": 1, + amount: 1 + }); + + assert.deepStrictEqual(data.sort(sort), [ + { _id: 6, item: { category: "brownies", type: "blondie" }, amount: 10 }, + { _id: 1, item: { category: "cake", type: "chiffon" }, amount: 10 }, + { _id: 5, item: { category: "cake", type: "carrot" }, amount: 20 }, + { _id: 4, item: { category: "cake", type: "lemon" }, amount: 30 }, + { _id: 3, item: { category: "cookies", type: "chocolate chip" }, amount: 15 }, + { _id: 2, item: { category: "cookies", type: "chocolate chip" }, amount: 50 } + ]); + }); }); });