diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index ba3a93b9..7aaf5194 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -1233,7 +1233,7 @@ describe('Parse.Query testing', () => { query.find(expectError(Parse.Error.INVALID_QUERY, done)); }); - it_exclude_dbs(['postgres'])("Use a regex that requires all modifiers", function(done) { + it("Use a regex that requires all modifiers", function(done) { var thing = new TestObject(); thing.set("myString", "PArSe\nCom"); Parse.Object.saveAll([thing], function() { @@ -1248,6 +1248,10 @@ describe('Parse.Query testing', () => { success: function(results) { equal(results.length, 1); done(); + }, + error: function(err) { + jfail(err); + done(); } }); }); @@ -1271,7 +1275,7 @@ describe('Parse.Query testing', () => { var someAscii = "\\E' !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTU" + "VWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'"; - it_exclude_dbs(['postgres'])("contains", function(done) { + it("contains", function(done) { Parse.Object.saveAll([new TestObject({myString: "zax" + someAscii + "qub"}), new TestObject({myString: "start" + someAscii}), new TestObject({myString: someAscii + "end"}), @@ -1287,7 +1291,7 @@ describe('Parse.Query testing', () => { }); }); - it_exclude_dbs(['postgres'])("startsWith", function(done) { + it("startsWith", function(done) { Parse.Object.saveAll([new TestObject({myString: "zax" + someAscii + "qub"}), new TestObject({myString: "start" + someAscii}), new TestObject({myString: someAscii + "end"}), @@ -1303,7 +1307,7 @@ describe('Parse.Query testing', () => { }); }); - it_exclude_dbs(['postgres'])("endsWith", function(done) { + it("endsWith", function(done) { Parse.Object.saveAll([new TestObject({myString: "zax" + someAscii + "qub"}), new TestObject({myString: "start" + someAscii}), new TestObject({myString: someAscii + "end"}), @@ -1634,7 +1638,7 @@ describe('Parse.Query testing', () => { }) }); - it_exclude_dbs(['postgres'])('properly nested array of mixed objects with bad ids', (done) => { + it('properly nested array of mixed objects with bad ids', (done) => { let objects = []; let total = 0; while(objects.length != 5) { @@ -1744,7 +1748,7 @@ describe('Parse.Query testing', () => { }); }); - it_exclude_dbs(['postgres'])("matches query", function(done) { + it("matches query", function(done) { var ParentObject = Parse.Object.extend("ParentObject"); var ChildObject = Parse.Object.extend("ChildObject"); var objects = []; diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index cbd65753..48e9233d 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -331,8 +331,14 @@ const buildWhereClause = ({ schema, query, index }) => { if (opts.indexOf('i') >= 0) { operator = '~*'; } + if (opts.indexOf('x') >= 0) { + regex = removeWhiteSpace(regex); + } } - patterns.push(`$${index}:name ${operator} $${index+1}`); + + regex = processRegexPattern(regex); + + patterns.push(`$${index}:name ${operator} '$${index+1}:raw'`); values.push(fieldName, regex); index += 2; } @@ -1131,6 +1137,79 @@ function notImplemented() { return Promise.reject(new Error('Not implemented yet.')); } +function removeWhiteSpace(regex) { + if (!regex.endsWith('\n')){ + regex += '\n'; + } + + // remove non escaped comments + return regex.replace(/([^\\])#.*\n/gmi, '$1') + // remove lines starting with a comment + .replace(/^#.*\n/gmi, '') + // remove non escaped whitespace + .replace(/([^\\])\s+/gmi, '$1') + // remove whitespace at the beginning of a line + .replace(/^\s+/, '') + .trim(); +} + +function processRegexPattern(s) { + if (s && s.startsWith('^')){ + // regex for startsWith + return '^' + literalizeRegexPart(s.slice(1)); + + } else if (s && s.endsWith('$')) { + // regex for endsWith + return literalizeRegexPart(s.slice(0, s.length - 1)) + '$'; + } + + // regex for contains + return literalizeRegexPart(s); +} + +function createLiteralRegex(remaining) { + return remaining.split('').map(c => { + if (c.match(/[0-9a-zA-Z]/) !== null) { + // don't escape alphanumeric characters + return c; + } + // escape everything else (single quotes with single quotes, everything else with a backslash) + return c === `'` ? `''` : `\\${c}`; + }).join(''); +} + +function literalizeRegexPart(s) { + const matcher1 = /\\Q((?!\\E).*)\\E$/ + const result1 = s.match(matcher1); + if(result1 && result1.length > 1 && result1.index > -1){ + // process regex that has a beginning and an end specified for the literal text + const prefix = s.substr(0, result1.index); + const remaining = result1[1]; + + return literalizeRegexPart(prefix) + createLiteralRegex(remaining); + } + + // process regex that has a beginning specified for the literal text + const matcher2 = /\\Q((?!\\E).*)$/ + const result2 = s.match(matcher2); + if(result2 && result2.length > 1 && result2.index > -1){ + const prefix = s.substr(0, result2.index); + const remaining = result2[1]; + + return literalizeRegexPart(prefix) + createLiteralRegex(remaining); + } + + // remove all instances of \Q and \E from the remaining text & escape single quotes + return ( + s.replace(/([^\\])(\\E)/, '$1') + .replace(/([^\\])(\\Q)/, '$1') + .replace(/^\\E/, '') + .replace(/^\\Q/, '') + .replace(/([^'])'/, `$1''`) + .replace(/^'([^'])/, `''$1`) + ); +} + // Function to set a key on a nested JSON document const json_object_set_key = 'CREATE OR REPLACE FUNCTION "json_object_set_key"(\ "json" jsonb,\