diff --git a/postinstall.js b/postinstall.js index 841b7cdb..fef1fb31 100644 --- a/postinstall.js +++ b/postinstall.js @@ -1,7 +1,7 @@ const pkg = require('./package.json'); -const version = parseFloat( process.version.substr(1) ); -const minimum = parseFloat( pkg.engines.node.match(/\d+/g).join('.') ); +const version = parseFloat(process.version.substr(1)); +const minimum = parseFloat(pkg.engines.node.match(/\d+/g).join('.')); module.exports = function () { const openCollective = ` @@ -47,4 +47,4 @@ module.exports = function () { process.stdout.write(errorMessage); process.exit(1); -}; \ No newline at end of file +}; diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 662b58fa..bce106b0 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -5,6 +5,18 @@ 'use strict'; const Parse = require('parse/node'); +const rp = require('request-promise'); + +const masterKeyHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Rest-API-Key': 'test', + 'X-Parse-Master-Key': 'test' +} + +const masterKeyOptions = { + headers: masterKeyHeaders, + json: true +} describe('Parse.Query testing', () => { it("basic query", function(done) { @@ -570,6 +582,38 @@ describe('Parse.Query testing', () => { }); }); + it("lessThan zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.lessThan('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 3); + done(); + }); + }); + + it("lessThanOrEqualTo zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.lessThanOrEqualTo('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 4); + done(); + }); + }); + it("greaterThan queries", function(done) { const makeBoxedNumber = function(i) { return new BoxedNumber({ number: i }); @@ -606,6 +650,38 @@ describe('Parse.Query testing', () => { }); }); + it("greaterThan zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.greaterThan('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 1); + done(); + }); + }); + + it("greaterThanOrEqualTo zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.greaterThanOrEqualTo('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 2); + done(); + }); + }); + it("lessThanOrEqualTo greaterThanOrEqualTo queries", function(done) { const makeBoxedNumber = function(i) { return new BoxedNumber({ number: i }); @@ -662,6 +738,101 @@ describe('Parse.Query testing', () => { }); }); + it("notEqualTo zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.notEqualTo('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 4); + done(); + }); + }); + + it("equalTo zero queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.equalTo('number', 0); + return query.find(); + }).then((results) => { + equal(results.length, 1); + done(); + }); + }); + + it("number equalTo boolean queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.equalTo('number', false); + return query.find(); + }).then((results) => { + equal(results.length, 0); + done(); + }); + }); + + it("equalTo false queries", (done) => { + const obj1 = new TestObject({ field: false }); + const obj2 = new TestObject({ field: true }); + Parse.Object.saveAll([obj1, obj2]).then(() => { + const query = new Parse.Query(TestObject); + query.equalTo('field', false); + return query.find(); + }).then((results) => { + equal(results.length, 1); + done(); + }); + }); + + it("where $eq false queries (rest)", (done) => { + const options = Object.assign({}, masterKeyOptions, { + body: { + where: { field: { $eq: false } }, + } + }); + const obj1 = new TestObject({ field: false }); + const obj2 = new TestObject({ field: true }); + Parse.Object.saveAll([obj1, obj2]).then(() => { + rp.get(Parse.serverURL + '/classes/TestObject', options) + .then((resp) => { + equal(resp.results.length, 1); + done(); + }); + }) + }); + + it("where $eq null queries (rest)", (done) => { + const options = Object.assign({}, masterKeyOptions, { + body: { + where: { field: { $eq: null } }, + } + }); + const obj1 = new TestObject({ field: false }); + const obj2 = new TestObject({ field: null }); + Parse.Object.saveAll([obj1, obj2]).then(() => { + rp.get(Parse.serverURL + '/classes/TestObject', options) + .then((resp) => { + equal(resp.results.length, 1); + done(); + }); + }) + }); + it("containedIn queries", function(done) { const makeBoxedNumber = function(i) { return new BoxedNumber({ number: i }); @@ -680,6 +851,40 @@ describe('Parse.Query testing', () => { }); }); + it("containedIn false queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.containedIn('number', false); + return query.find(); + }).then(done.fail).catch((error) => { + equal(error.code, Parse.Error.INVALID_JSON); + equal(error.message, 'bad $in value'); + done(); + }); + }); + + it("notContainedIn false queries", (done) => { + const makeBoxedNumber = (i) => { + return new BoxedNumber({ number: i }); + }; + const numbers = [-3, -2, -1, 0, 1]; + const boxedNumbers = numbers.map(makeBoxedNumber); + Parse.Object.saveAll(boxedNumbers).then(() => { + const query = new Parse.Query(BoxedNumber); + query.notContainedIn('number', false); + return query.find(); + }).then(done.fail).catch((error) => { + equal(error.code, Parse.Error.INVALID_JSON); + equal(error.message, 'bad $nin value'); + done(); + }); + }); + it("notContainedIn queries", function(done) { const makeBoxedNumber = function(i) { return new BoxedNumber({ number: i }); diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index ef005c59..ccdfdbd6 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -287,7 +287,14 @@ const buildWhereClause = ({ schema, query, index }) => { index += 2; } else if (typeof fieldValue === 'boolean') { patterns.push(`$${index}:name = $${index + 1}`); - values.push(fieldName, fieldValue); + // Can't cast boolean to double precision + if (schema.fields[fieldName] && schema.fields[fieldName].type === 'Number') { + // Should always return zero results + const MAX_INT_PLUS_ONE = 9223372036854775808; + values.push(fieldName, MAX_INT_PLUS_ONE); + } else { + values.push(fieldName, fieldValue); + } index += 2; } else if (typeof fieldValue === 'number') { patterns.push(`$${index}:name = $${index + 1}`); @@ -329,11 +336,16 @@ const buildWhereClause = ({ schema, query, index }) => { values.push(fieldName, fieldValue.$ne); index += 2; } - - if (fieldValue.$eq) { - patterns.push(`$${index}:name = $${index + 1}`); - values.push(fieldName, fieldValue.$eq); - index += 2; + if (fieldValue.$eq !== undefined) { + if (fieldValue.$eq === null) { + patterns.push(`$${index}:name IS NULL`); + values.push(fieldName); + index += 1; + } else { + patterns.push(`$${index}:name = $${index + 1}`); + values.push(fieldName, fieldValue.$eq); + index += 2; + } } const isInOrNin = Array.isArray(fieldValue.$in) || Array.isArray(fieldValue.$nin); if (Array.isArray(fieldValue.$in) && @@ -393,6 +405,10 @@ const buildWhereClause = ({ schema, query, index }) => { if (fieldValue.$nin) { createConstraint(_.flatMap(fieldValue.$nin, elt => elt), true); } + } else if(typeof fieldValue.$in !== 'undefined') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $in value'); + } else if (typeof fieldValue.$nin !== 'undefined') { + throw new Parse.Error(Parse.Error.INVALID_JSON, 'bad $nin value'); } if (Array.isArray(fieldValue.$all) && isArrayField) { @@ -578,7 +594,7 @@ const buildWhereClause = ({ schema, query, index }) => { } Object.keys(ParseToPosgresComparator).forEach(cmp => { - if (fieldValue[cmp]) { + if (fieldValue[cmp] || fieldValue[cmp] === 0) { const pgComparator = ParseToPosgresComparator[cmp]; patterns.push(`$${index}:name ${pgComparator} $${index + 1}`); values.push(fieldName, toPostgresValue(fieldValue[cmp]));