From c6546218f435360925bebe42d64f6b380a84e4e2 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Wed, 23 Aug 2017 10:33:57 -0500 Subject: [PATCH] PG: Support for nested contains and containedIn (#4109) --- spec/ParseQuery.spec.js | 51 ++++++++++++++++++ .../Postgres/PostgresStorageAdapter.js | 54 +++++++++++++++---- 2 files changed, 94 insertions(+), 11 deletions(-) diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index b7b6621e..da53ca62 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -289,6 +289,40 @@ describe('Parse.Query testing', () => { }, done.fail); }); + it('nested containedIn string', (done) => { + const sender1 = { group: ['A', 'B'] }; + const sender2 = { group: ['A', 'C'] }; + const sender3 = { group: ['B', 'C'] }; + const obj1 = new TestObject({ sender: sender1 }); + const obj2 = new TestObject({ sender: sender2 }); + const obj3 = new TestObject({ sender: sender3 }); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const query = new Parse.Query(TestObject); + query.containedIn('sender.group', ['A']); + return query.find(); + }).then((results) => { + equal(results.length, 2); + done(); + }, done.fail); + }); + + it('nested containedIn number', (done) => { + const sender1 = { group: [1, 2] }; + const sender2 = { group: [1, 3] }; + const sender3 = { group: [2, 3] }; + const obj1 = new TestObject({ sender: sender1 }); + const obj2 = new TestObject({ sender: sender2 }); + const obj3 = new TestObject({ sender: sender3 }); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const query = new Parse.Query(TestObject); + query.containedIn('sender.group', [1]); + return query.find(); + }).then((results) => { + equal(results.length, 2); + done(); + }, done.fail); + }); + it("containsAll number array queries", function(done) { var NumberSet = Parse.Object.extend({ className: "NumberSet" }); @@ -1379,6 +1413,23 @@ describe('Parse.Query testing', () => { }); }); + it('nested contains', (done) => { + const sender1 = { group: ['A', 'B'] }; + const sender2 = { group: ['A', 'C'] }; + const sender3 = { group: ['B', 'C'] }; + const obj1 = new TestObject({ sender: sender1 }); + const obj2 = new TestObject({ sender: sender2 }); + const obj3 = new TestObject({ sender: sender3 }); + Parse.Object.saveAll([obj1, obj2, obj3]).then(() => { + const query = new Parse.Query(TestObject); + query.contains('sender.group', 'A'); + return query.find(); + }).then((results) => { + equal(results.length, 2); + done(); + }, done.fail); + }); + it("startsWith", function(done) { Parse.Object.saveAll([new TestObject({myString: "zax" + someAscii + "qub"}), new TestObject({myString: "start" + someAscii}), diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 6ccab203..5795f86d 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -146,6 +146,25 @@ const handleDotFields = (object) => { return object; } +const transformDotFieldToComponents = (fieldName) => { + return fieldName.split('.').map((cmpt, index) => { + if (index === 0) { + return `"${cmpt}"`; + } + return `'${cmpt}'`; + }); +} + +const transformDotField = (fieldName) => { + if (fieldName.indexOf('.') === -1) { + return `"${fieldName}"`; + } + const components = transformDotFieldToComponents(fieldName); + let name = components.slice(0, components.length - 1).join('->'); + name += '->>' + components[components.length - 1]; + return name; +} + const validateKeys = (object) => { if (typeof object == 'object') { for (const key in object) { @@ -195,18 +214,26 @@ const buildWhereClause = ({ schema, query, index }) => { } if (fieldName.indexOf('.') >= 0) { - const components = fieldName.split('.').map((cmpt, index) => { - if (index === 0) { - return `"${cmpt}"`; - } - return `'${cmpt}'`; - }); - let name = components.slice(0, components.length - 1).join('->'); - name += '->>' + components[components.length - 1]; + let name = transformDotField(fieldName); if (fieldValue === null) { patterns.push(`${name} IS NULL`); } else { - patterns.push(`${name} = '${fieldValue}'`); + if (fieldValue.$in) { + const inPatterns = []; + name = transformDotFieldToComponents(fieldName).join('->'); + fieldValue.$in.forEach((listElem) => { + if (typeof listElem === 'string') { + inPatterns.push(`"${listElem}"`); + } else { + inPatterns.push(`${listElem}`); + } + }); + patterns.push(`(${name})::jsonb @> '[${inPatterns.join(',')}]'::jsonb`); + } else if (fieldValue.$regex) { + // Handle later + } else { + patterns.push(`${name} = '${fieldValue}'`); + } } } else if (fieldValue === null) { patterns.push(`$${index}:name IS NULL`); @@ -298,6 +325,10 @@ const buildWhereClause = ({ schema, query, index }) => { values.push(fieldName, JSON.stringify(baseArray)); index += 2; } else { + // Handle Nested Dot Notation Above + if (fieldName.indexOf('.') >= 0) { + return; + } const inPatterns = []; values.push(fieldName); baseArray.forEach((listElem, listIndex) => { @@ -466,10 +497,11 @@ const buildWhereClause = ({ schema, query, index }) => { } } + const name = transformDotField(fieldName); regex = processRegexPattern(regex); - patterns.push(`$${index}:name ${operator} '$${index + 1}:raw'`); - values.push(fieldName, regex); + patterns.push(`$${index}:raw ${operator} '$${index + 1}:raw'`); + values.push(name, regex); index += 2; }