mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d7b9923ef | ||
|
|
d11e93c36c | ||
|
|
d04a7b5a77 | ||
|
|
ce7ff2ca44 | ||
|
|
de92ce5c49 | ||
|
|
66f594342e | ||
|
|
3a6505a32d | ||
|
|
acf205cd7f | ||
|
|
99698928af | ||
|
|
a9aff79cc6 | ||
|
|
df109c0023 | ||
|
|
1d8bcb22bd | ||
|
|
ab32dbc9a2 | ||
|
|
631b1684e2 | ||
|
|
7b3da8b744 | ||
|
|
c762ee4b43 | ||
|
|
315d3bfee8 | ||
|
|
980c2cda3a | ||
|
|
3ea632fa1b | ||
|
|
f08ca7b9cc | ||
|
|
4b8dc9409f | ||
|
|
d478e001b5 | ||
|
|
8c419ec52a | ||
|
|
9470dfc88a | ||
|
|
13626290c0 | ||
|
|
cbd31d513e | ||
|
|
3e414a8c60 | ||
|
|
38482cf0fe | ||
|
|
8c044ab85b | ||
|
|
208f92160d | ||
|
|
e28770e744 | ||
|
|
a4b592a355 | ||
|
|
f0626508d3 | ||
|
|
bc12b1496a | ||
|
|
80a9b70253 | ||
|
|
5d9d7601a9 |
+6
-7
@@ -26,16 +26,15 @@ stage: test
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/.bin/nyc'
|
||||
- NODE_VERSION=8.11.2
|
||||
matrix:
|
||||
- MONGODB_VERSION=3.2.13
|
||||
- MONGODB_VERSION=3.4.4
|
||||
- MONGODB_VERSION=4.0.4
|
||||
- MONGODB_VERSION=3.6.9
|
||||
- PARSE_SERVER_TEST_DB=postgres
|
||||
- PARSE_SERVER_TEST_CACHE=redis
|
||||
- NODE_VERSION=stable
|
||||
- NODE_VERSION=11.4.0
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: NODE_VERSION=stable
|
||||
- env: NODE_VERSION=11.4.0
|
||||
before_install:
|
||||
- nvm install $NODE_VERSION
|
||||
- nvm use $NODE_VERSION
|
||||
@@ -74,9 +73,9 @@ jobs:
|
||||
- provider: npm
|
||||
skip_cleanup: true
|
||||
email:
|
||||
secure: "YweTGc22uqFWpzbfiUa5ptBLRAy6tt6d9TZLwEkPtmnsWzN9dguGyKWmXiw0qL+848FWQ5PWzUgBn5XdigV9tF3rJY6RGs8i38WulNjwSoGuRZa3AChsQHAb1KenANcJybzhnwgEj9gRsrGZPEsyI2whfake/xLDtG91kHjpJANsd4gseOh6fdS4FIYCbyXvSbC7S0yZzIJkgSkMqJO7RJ8r0HYQ7srYIw31dM3ZXSmUYu+GaMDUUu3RLAGYoKaROxhDRnzkjdeLLiwZH8hQ/6CaqwqX54iJ6OS+MOQU3fi5ZXalA7RZvtC4RmrcCkaTf3i0f+5xejYIFgzXdYGMVm8DUc82tDw1s4b6Pb19bgi1xXOQ0IKzRmZuGxnvkRN61dnYdnpnnNuG97HXgVjiOigZXVLZkWazUdnf9zXqmkC+KxPfa4Ldqg0TMjQ9J14n6TXxRti8Tt0xMa1Uzho7VdsxdJy35Bghy398O6X8VdX6parfzEkX7c/JzcA3TIMJ9+S1dy3J4Tb2URB5367W6h7cDeXtGmwLTFu75Q1CNqRJkUGbSrX2NyMqG5tc8oaTM+OWvLxcbKPRy9T6qN7x2JmCHHaapf8/8VR9wCy2PwE5j+KWhmctEHqqOgrtG5gsjC0eCPJsposxZWyM7M6aUpXe3w+olBfKq9apUGStUSU="
|
||||
secure: $NPM_EMAIL
|
||||
api_key:
|
||||
secure: "QprkaqQ+WCvZQR4qIEs5iS6peMCbRd8Hgt0s6HfdmhavNXwDFY8Bkdf6zJwWHLiqs4pyClXDZ2f6QiOs7y9IvJZ+wOIbsf4N5V6s06zOxJ0NAOwhe0mgWS3Us0zgXIfmW4BpmGnU4ql/qGL+9vNfyQJ7wxEJxVK7hiYh9Epu49E2jmefDqTX+SNSrDCg4HkRcxerxYGnAJDCP50QaNlyLSciODD6wHaddrSYkdvmISLMnLHug61OkE4OBIOWXfYV+e31kDj+zgczAfVQgekDKtaimCQclHFrmaEPl0KIm6wsDQAw5HWkepA/WZfv4SbCrDaKJDZw+LBI7dR0ezmiOH/zmWWrRW7D4wjkDGiumWjA8etuf8I4GRyC/d1RS+hnlvPr0Bu+WljuVxLoK3nhZOdiK5t4QlVDoGankkRjLylwFQgo8tzu5N0dc26z3ClowTwcKsjStmFr53gjCD7l3qoFjyPot1JlW3LPhG9Nch7rK33/7ONqVai3zxb1xB9ynd8TSzKi4/66LeYEDcGVM5A9Vmkp+egCnOhkOAXdI8O4jid98NANir+U4xmUYZ2PAMNmSeHlpSpwH2pC1/BHpDKA9RZEuEdr3sgmtuXXwwOCp+xvuVwxZZ6+gVEjG4nGTqSNxUIq1fzjGih8ElJtbM1Uhh2dVE9uxW7EC/oCuuk="
|
||||
secure: $NPM_TOKEN
|
||||
on:
|
||||
tags: true
|
||||
all_branches: true
|
||||
|
||||
+13
-2
@@ -1,7 +1,18 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### master
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.2...master)
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.3...master)
|
||||
|
||||
### 3.1.3
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.2...3.1.3)
|
||||
|
||||
- Postgres: Fixes support for global configuration
|
||||
- Postgres: Fixes support for numeric arrays
|
||||
- Postgres: Fixes issue affecting queries on emtpy arrays
|
||||
- LiveQuery: Adds support for transmitting the original object
|
||||
- Queries: Use estimated count if queyr is empty
|
||||
- Docker: Reduces the size of the docker image to 154Mb
|
||||
|
||||
|
||||
### 3.1.2
|
||||
[Full Changelog](https://github.com/parse-community/parse-server/compare/3.1.1...3.1.2)
|
||||
@@ -46,7 +57,7 @@ For more informations, visit the v3.0.0 [migration guide](https://github.com/par
|
||||
* Cloud Code handlers have a new interface based on promises.
|
||||
* response.success / response.error are removed in Cloud Code
|
||||
* Cloud Code runs with Parse-SDK 2.0
|
||||
* The aggregate now require aggregates to be passed in the form: `{"pipeline": [...]}`
|
||||
* The aggregate now require aggregates to be passed in the form: `{"pipeline": [...]}` (REST Only)
|
||||
|
||||
#### Improvements:
|
||||
* Adds Pipeline Operator to Aggregate Router.
|
||||
|
||||
@@ -61,6 +61,7 @@ Once you have babel running in watch mode, you can start making changes to parse
|
||||
If your pull request introduces a change that may affect the storage or retrieval of objects, you may want to make sure it plays nice with Postgres.
|
||||
|
||||
* Run the tests against the postgres database with `PARSE_SERVER_TEST_DB=postgres npm test`. You'll need to have postgres running on your machine and setup [appropriately](https://github.com/parse-community/parse-server/blob/master/.travis.yml#L37)
|
||||
* The Postgres adapter has a special debugger that traces all the sql commands. You can enable it with setting the environment variable `PARSE_SERVER_LOG_LEVEL=debug`
|
||||
* If your feature is intended to only work with MongoDB, you should disable PostgreSQL-specific tests with:
|
||||
|
||||
- `describe_only_db('mongo')` // will create a `describe` that runs only on mongoDB
|
||||
|
||||
+22
-12
@@ -1,21 +1,31 @@
|
||||
FROM node:carbon
|
||||
# Build stage
|
||||
FROM node:lts-alpine as build
|
||||
|
||||
RUN mkdir -p /parse-server
|
||||
COPY ./ /parse-server/
|
||||
RUN apk update; \
|
||||
apk add git;
|
||||
WORKDIR /tmp
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
RUN mkdir -p /parse-server/config
|
||||
VOLUME /parse-server/config
|
||||
|
||||
RUN mkdir -p /parse-server/cloud
|
||||
VOLUME /parse-server/cloud
|
||||
# Release stage
|
||||
FROM node:lts-alpine as release
|
||||
VOLUME /parse-server/cloud /parse-server/config
|
||||
|
||||
WORKDIR /parse-server
|
||||
|
||||
RUN npm install && \
|
||||
npm run build
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
|
||||
COPY bin bin
|
||||
COPY public_html public_html
|
||||
COPY views views
|
||||
COPY --from=build /tmp/lib lib
|
||||
RUN mkdir -p logs && chown -R node: logs
|
||||
|
||||
ENV PORT=1337
|
||||
|
||||
USER node
|
||||
EXPOSE $PORT
|
||||
|
||||
ENTRYPOINT ["npm", "start", "--"]
|
||||
ENTRYPOINT ["node", "./bin/parse-server"]
|
||||
|
||||
@@ -7,6 +7,12 @@
|
||||
[](https://gitter.im/ParsePlatform/Chat)
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
Parse Server is an [open source version of the Parse backend](http://blog.parseplatform.org/announcements/introducing-parse-server-and-the-database-migration-tool/) that can be deployed to any infrastructure that can run Node.js.
|
||||
|
||||
Parse Server works with the Express web application framework. It can be added to existing web applications, or run by itself.
|
||||
|
||||
Generated
+1224
-2010
File diff suppressed because it is too large
Load Diff
+18
-18
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "3.1.2",
|
||||
"version": "3.1.3",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -27,29 +27,29 @@
|
||||
"body-parser": "1.18.3",
|
||||
"commander": "2.19.0",
|
||||
"deepcopy": "1.0.0",
|
||||
"express": "4.16.2",
|
||||
"follow-redirects": "1.5.9",
|
||||
"express": "4.16.4",
|
||||
"follow-redirects": "1.6.0",
|
||||
"intersect": "1.0.1",
|
||||
"lodash": "4.17.11",
|
||||
"lru-cache": "5.0.0",
|
||||
"lru-cache": "5.1.1",
|
||||
"mime": "2.4.0",
|
||||
"mongodb": "3.1.10",
|
||||
"parse": "2.1.0",
|
||||
"pg-promise": "8.5.2",
|
||||
"pg-promise": "8.5.4",
|
||||
"redis": "2.8.0",
|
||||
"semver": "5.6.0",
|
||||
"tv4": "1.3.0",
|
||||
"uuid": "^3.1.0",
|
||||
"winston": "2.4.1",
|
||||
"winston": "2.4.4",
|
||||
"winston-daily-rotate-file": "1.7.2",
|
||||
"ws": "6.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.1.5",
|
||||
"@babel/core": "7.1.6",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.1.6",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.0.0",
|
||||
"@babel/preset-env": "7.1.6",
|
||||
"@babel/cli": "7.2.3",
|
||||
"@babel/core": "7.2.2",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.2.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.2.0",
|
||||
"@babel/preset-env": "7.2.3",
|
||||
"@parse/minami": "^1.0.0",
|
||||
"babel-eslint": "^10.0.0",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
@@ -64,11 +64,11 @@
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"jsdoc": "^3.5.5",
|
||||
"jsdoc-babel": "^0.5.0",
|
||||
"lint-staged": "^7.2.2",
|
||||
"lint-staged": "^8.1.0",
|
||||
"mongodb-runner": "4.3.2",
|
||||
"nyc": "^12.0.2",
|
||||
"prettier": "1.15.2",
|
||||
"supports-color": "^5.4.0"
|
||||
"nyc": "^13.1.0",
|
||||
"prettier": "1.15.3",
|
||||
"supports-color": "^6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "jsdoc -c ./jsdoc-conf.json",
|
||||
@@ -76,8 +76,8 @@
|
||||
"lint": "flow && eslint --cache ./",
|
||||
"build": "babel src/ -d lib/ --copy-files",
|
||||
"watch": "babel --watch src/ -d lib/ --copy-files",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
|
||||
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 jasmine",
|
||||
"coverage": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=4.0.4} MONGODB_STORAGE_ENGINE=mmapv1 TESTING=1 nyc jasmine",
|
||||
"start": "node ./bin/parse-server",
|
||||
"prepare": "npm run build",
|
||||
"postinstall": "node -p 'require(\"./postinstall.js\")()'"
|
||||
@@ -89,7 +89,7 @@
|
||||
"parse-server": "./bin/parse-server"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "3.0.2",
|
||||
"bcrypt": "3.0.3",
|
||||
"uws": "10.148.1"
|
||||
},
|
||||
"collective": {
|
||||
|
||||
@@ -899,6 +899,56 @@ describe('ParseLiveQueryServer', function() {
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
});
|
||||
|
||||
it('can handle update command with original object', function(done) {
|
||||
jasmine.restoreLibrary('../lib/LiveQuery/Client', 'Client');
|
||||
const Client = require('../lib/LiveQuery/Client').Client;
|
||||
const parseLiveQueryServer = new ParseLiveQueryServer({});
|
||||
// Make mock request message
|
||||
const message = generateMockMessage(true);
|
||||
|
||||
const clientId = 1;
|
||||
const parseWebSocket = {
|
||||
clientId,
|
||||
send: jasmine.createSpy('send'),
|
||||
};
|
||||
const client = new Client(clientId, parseWebSocket);
|
||||
spyOn(client, 'pushUpdate').and.callThrough();
|
||||
parseLiveQueryServer.clients.set(clientId, client);
|
||||
|
||||
// Add mock subscription
|
||||
const requestId = 2;
|
||||
|
||||
addMockSubscription(
|
||||
parseLiveQueryServer,
|
||||
clientId,
|
||||
requestId,
|
||||
parseWebSocket
|
||||
);
|
||||
// Mock _matchesSubscription to return matching
|
||||
parseLiveQueryServer._matchesSubscription = function(parseObject) {
|
||||
if (!parseObject) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
parseLiveQueryServer._matchesACL = function() {
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
parseLiveQueryServer._onAfterSave(message);
|
||||
|
||||
// Make sure we send update command to client
|
||||
setTimeout(function() {
|
||||
expect(client.pushUpdate).toHaveBeenCalled();
|
||||
const args = parseWebSocket.send.calls.mostRecent().args;
|
||||
const toSend = JSON.parse(args[0]);
|
||||
|
||||
expect(toSend.object).toBeDefined();
|
||||
expect(toSend.original).toBeDefined();
|
||||
done();
|
||||
}, jasmine.ASYNC_TEST_WAIT_TIME);
|
||||
});
|
||||
|
||||
it('can handle object create command which matches some subscriptions', function(done) {
|
||||
const parseLiveQueryServer = new ParseLiveQueryServer({});
|
||||
// Make mock request message
|
||||
@@ -1006,6 +1056,59 @@ describe('ParseLiveQueryServer', function() {
|
||||
expect(originalObject.updatedAt).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('can inflate user object', async () => {
|
||||
const parseLiveQueryServer = new ParseLiveQueryServer({});
|
||||
const userJSON = {
|
||||
username: 'test',
|
||||
ACL: {},
|
||||
createdAt: '2018-12-21T23:09:51.784Z',
|
||||
sessionToken: 'r:1234',
|
||||
updatedAt: '2018-12-21T23:09:51.784Z',
|
||||
objectId: 'NhF2u9n72W',
|
||||
__type: 'Object',
|
||||
className: '_User',
|
||||
_hashed_password: '1234',
|
||||
_email_verify_token: '1234',
|
||||
};
|
||||
|
||||
const originalUserJSON = {
|
||||
username: 'test',
|
||||
ACL: {},
|
||||
createdAt: '2018-12-21T23:09:51.784Z',
|
||||
sessionToken: 'r:1234',
|
||||
updatedAt: '2018-12-21T23:09:51.784Z',
|
||||
objectId: 'NhF2u9n72W',
|
||||
__type: 'Object',
|
||||
className: '_User',
|
||||
_hashed_password: '12345',
|
||||
_email_verify_token: '12345',
|
||||
};
|
||||
|
||||
const message = {
|
||||
currentParseObject: userJSON,
|
||||
originalParseObject: originalUserJSON,
|
||||
};
|
||||
parseLiveQueryServer._inflateParseObject(message);
|
||||
|
||||
const object = message.currentParseObject;
|
||||
expect(object instanceof Parse.Object).toBeTruthy();
|
||||
expect(object.get('_hashed_password')).toBeUndefined();
|
||||
expect(object.get('_email_verify_token')).toBeUndefined();
|
||||
expect(object.className).toEqual('_User');
|
||||
expect(object.id).toBe('NhF2u9n72W');
|
||||
expect(object.createdAt).not.toBeUndefined();
|
||||
expect(object.updatedAt).not.toBeUndefined();
|
||||
|
||||
const originalObject = message.originalParseObject;
|
||||
expect(originalObject instanceof Parse.Object).toBeTruthy();
|
||||
expect(originalObject.get('_hashed_password')).toBeUndefined();
|
||||
expect(originalObject.get('_email_verify_token')).toBeUndefined();
|
||||
expect(originalObject.className).toEqual('_User');
|
||||
expect(originalObject.id).toBe('NhF2u9n72W');
|
||||
expect(originalObject.createdAt).not.toBeUndefined();
|
||||
expect(originalObject.updatedAt).not.toBeUndefined();
|
||||
});
|
||||
|
||||
it('can match undefined ACL', function(done) {
|
||||
const parseLiveQueryServer = new ParseLiveQueryServer({});
|
||||
const client = {};
|
||||
@@ -1314,7 +1417,7 @@ describe('ParseLiveQueryServer', function() {
|
||||
new Parse.ACL()
|
||||
);
|
||||
liveQueryRole.id = 'abcdef1234';
|
||||
callback(liveQueryRole)
|
||||
callback(liveQueryRole);
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -238,6 +238,30 @@ describe('Parse.Query testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('query notContainedIn on empty array', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('value', 100);
|
||||
await object.save();
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.notContainedIn('value', []);
|
||||
|
||||
const results = await query.find();
|
||||
equal(results.length, 1);
|
||||
});
|
||||
|
||||
it('query containedIn on empty array', async () => {
|
||||
const object = new TestObject();
|
||||
object.set('value', 100);
|
||||
await object.save();
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.containedIn('value', []);
|
||||
|
||||
const results = await query.find();
|
||||
equal(results.length, 0);
|
||||
});
|
||||
|
||||
it('query with limit', function(done) {
|
||||
const baz = new TestObject({ foo: 'baz' });
|
||||
const qux = new TestObject({ foo: 'qux' });
|
||||
@@ -4517,4 +4541,100 @@ describe('Parse.Query testing', () => {
|
||||
.then(done.fail)
|
||||
.catch(() => done());
|
||||
});
|
||||
|
||||
it('can add new config to existing config', async () => {
|
||||
await request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: {
|
||||
params: {
|
||||
files: [{ __type: 'File', name: 'name', url: 'http://url' }],
|
||||
},
|
||||
},
|
||||
headers: masterKeyHeaders,
|
||||
});
|
||||
|
||||
await request({
|
||||
method: 'PUT',
|
||||
url: 'http://localhost:8378/1/config',
|
||||
json: true,
|
||||
body: {
|
||||
params: { newConfig: 'good' },
|
||||
},
|
||||
headers: masterKeyHeaders,
|
||||
});
|
||||
|
||||
const result = await Parse.Config.get();
|
||||
equal(result.get('files')[0].toJSON(), {
|
||||
__type: 'File',
|
||||
name: 'name',
|
||||
url: 'http://url',
|
||||
});
|
||||
equal(result.get('newConfig'), 'good');
|
||||
});
|
||||
|
||||
it('can set object type key', async () => {
|
||||
const data = { bar: true, baz: 100 };
|
||||
const object = new TestObject();
|
||||
object.set('objectField', data);
|
||||
await object.save();
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
let result = await query.get(object.id);
|
||||
equal(result.get('objectField'), data);
|
||||
|
||||
object.set('objectField.baz', 50, { ignoreValidation: true });
|
||||
await object.save();
|
||||
|
||||
result = await query.get(object.id);
|
||||
equal(result.get('objectField'), { bar: true, baz: 50 });
|
||||
});
|
||||
|
||||
it('can update numeric array', async () => {
|
||||
const data1 = [0, 1.1, 1, -2, 3];
|
||||
const data2 = [0, 1.1, 1, -2, 3, 4];
|
||||
const obj1 = new TestObject();
|
||||
obj1.set('array', data1);
|
||||
await obj1.save();
|
||||
equal(obj1.get('array'), data1);
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('objectId', obj1.id);
|
||||
|
||||
const result = await query.first();
|
||||
equal(result.get('array'), data1);
|
||||
|
||||
result.set('array', data2);
|
||||
equal(result.get('array'), data2);
|
||||
await result.save();
|
||||
equal(result.get('array'), data2);
|
||||
|
||||
const results = await query.find();
|
||||
equal(results[0].get('array'), data2);
|
||||
});
|
||||
|
||||
it('can update mixed array', async () => {
|
||||
const data1 = [0, 1.1, 'hello world', { foo: 'bar' }];
|
||||
const data2 = [0, 1, { foo: 'bar' }, [], [1, 2, 'bar']];
|
||||
const obj1 = new TestObject();
|
||||
obj1.set('array', data1);
|
||||
await obj1.save();
|
||||
equal(obj1.get('array'), data1);
|
||||
|
||||
const query = new Parse.Query(TestObject);
|
||||
query.equalTo('objectId', obj1.id);
|
||||
|
||||
const result = await query.first();
|
||||
equal(result.get('array'), data1);
|
||||
|
||||
result.set('array', data2);
|
||||
equal(result.get('array'), data2);
|
||||
|
||||
await result.save();
|
||||
equal(result.get('array'), data2);
|
||||
|
||||
const results = await query.find();
|
||||
equal(results[0].get('array'), data2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,6 +80,16 @@ export default class MongoCollection {
|
||||
}
|
||||
|
||||
count(query, { skip, limit, sort, maxTimeMS, readPreference } = {}) {
|
||||
// If query is empty, then use estimatedDocumentCount instead.
|
||||
// This is due to countDocuments performing a scan,
|
||||
// which greatly increases execution time when being run on large collections.
|
||||
// See https://github.com/Automattic/mongoose/issues/6713 for more info regarding this problem.
|
||||
if (typeof query !== 'object' || !Object.keys(query).length) {
|
||||
return this._mongoCollection.estimatedDocumentCount({
|
||||
maxTimeMS,
|
||||
});
|
||||
}
|
||||
|
||||
const countOperation = this._mongoCollection.countDocuments(query, {
|
||||
skip,
|
||||
limit,
|
||||
|
||||
@@ -402,8 +402,8 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
|
||||
index = index + 1 + inPatterns.length;
|
||||
} else if (isInOrNin) {
|
||||
var createConstraint = (baseArray, notIn) => {
|
||||
const not = notIn ? ' NOT ' : '';
|
||||
if (baseArray.length > 0) {
|
||||
const not = notIn ? ' NOT ' : '';
|
||||
if (isArrayField) {
|
||||
patterns.push(
|
||||
`${not} array_contains($${index}:name, $${index + 1})`
|
||||
@@ -430,6 +430,13 @@ const buildWhereClause = ({ schema, query, index }): WhereClause => {
|
||||
values.push(fieldName);
|
||||
patterns.push(`$${index}:name IS NULL`);
|
||||
index = index + 1;
|
||||
} else {
|
||||
// Handle empty array
|
||||
if (notIn) {
|
||||
patterns.push('1 = 1'); // Return all values
|
||||
} else {
|
||||
patterns.push('1 = 2'); // Return no values
|
||||
}
|
||||
}
|
||||
};
|
||||
if (fieldValue.$in) {
|
||||
@@ -969,6 +976,7 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
const qs = `CREATE TABLE IF NOT EXISTS $1:name (${patternsArray.join()})`;
|
||||
const values = [className, ...valuesArray];
|
||||
|
||||
debug(qs, values);
|
||||
return conn.task('create-table', function*(t) {
|
||||
try {
|
||||
yield self._ensureSchemaCollectionExists(t);
|
||||
@@ -1426,6 +1434,18 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
schema = toPostgresSchema(schema);
|
||||
|
||||
const originalUpdate = { ...update };
|
||||
|
||||
// Set flag for dot notation fields
|
||||
const dotNotationOptions = {};
|
||||
Object.keys(update).forEach(fieldName => {
|
||||
if (fieldName.indexOf('.') > -1) {
|
||||
const components = fieldName.split('.');
|
||||
const first = components.shift();
|
||||
dotNotationOptions[first] = true;
|
||||
} else {
|
||||
dotNotationOptions[fieldName] = false;
|
||||
}
|
||||
});
|
||||
update = handleDotFields(update);
|
||||
// Resolve authData first,
|
||||
// So we don't end up with multiple key updates
|
||||
@@ -1615,13 +1635,18 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
},
|
||||
''
|
||||
);
|
||||
// Override Object
|
||||
let updateObject = "'{}'::jsonb";
|
||||
|
||||
if (dotNotationOptions[fieldName]) {
|
||||
// Merge Object
|
||||
updateObject = `COALESCE($${index}:name, '{}'::jsonb)`;
|
||||
}
|
||||
updatePatterns.push(
|
||||
`$${index}:name = ('{}'::jsonb ${deletePatterns} ${incrementPatterns} || $${index +
|
||||
`$${index}:name = (${updateObject} ${deletePatterns} ${incrementPatterns} || $${index +
|
||||
1 +
|
||||
keysToDelete.length}::jsonb )`
|
||||
);
|
||||
|
||||
values.push(fieldName, ...keysToDelete, JSON.stringify(fieldValue));
|
||||
index += 2 + keysToDelete.length;
|
||||
} else if (
|
||||
@@ -1632,20 +1657,37 @@ export class PostgresStorageAdapter implements StorageAdapter {
|
||||
const expectedType = parseTypeToPostgresType(schema.fields[fieldName]);
|
||||
if (expectedType === 'text[]') {
|
||||
updatePatterns.push(`$${index}:name = $${index + 1}::text[]`);
|
||||
values.push(fieldName, fieldValue);
|
||||
index += 2;
|
||||
} else {
|
||||
let type = 'text';
|
||||
for (const elt of fieldValue) {
|
||||
if (typeof elt == 'object') {
|
||||
type = 'json';
|
||||
break;
|
||||
values.push(fieldName);
|
||||
const buildSQLArray = fieldValue => {
|
||||
let pattern = 'json_build_array(';
|
||||
for (let i = 0; i < fieldValue.length; i += 1) {
|
||||
const element = fieldValue[i];
|
||||
let type = '';
|
||||
if (Array.isArray(element)) {
|
||||
pattern += buildSQLArray(element) + ',';
|
||||
continue;
|
||||
} else if (typeof element == 'object') {
|
||||
type = '::json';
|
||||
}
|
||||
values.push(element);
|
||||
pattern += `$${index + 1}${type},`;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
updatePatterns.push(
|
||||
`$${index}:name = array_to_json($${index + 1}::${type}[])::jsonb`
|
||||
);
|
||||
// remove last comma
|
||||
if (fieldValue.length > 0) {
|
||||
pattern = pattern.slice(0, -1);
|
||||
}
|
||||
pattern += ')';
|
||||
return pattern;
|
||||
};
|
||||
const sql = `$${index}:name = ${buildSQLArray(fieldValue)}`;
|
||||
|
||||
updatePatterns.push(sql);
|
||||
index += 1;
|
||||
}
|
||||
values.push(fieldName, fieldValue);
|
||||
index += 2;
|
||||
} else {
|
||||
debug('Not supported update', fieldName, fieldValue);
|
||||
return Promise.reject(
|
||||
|
||||
+11
-1
@@ -78,7 +78,11 @@ class Client {
|
||||
}
|
||||
|
||||
_pushEvent(type: string): Function {
|
||||
return function(subscriptionId: number, parseObjectJSON: any): void {
|
||||
return function(
|
||||
subscriptionId: number,
|
||||
parseObjectJSON: any,
|
||||
parseOriginalObjectJSON: any
|
||||
): void {
|
||||
const response: Message = {
|
||||
op: type,
|
||||
clientId: this.id,
|
||||
@@ -92,6 +96,12 @@ class Client {
|
||||
fields = this.subscriptionInfos.get(subscriptionId).fields;
|
||||
}
|
||||
response['object'] = this._toJSONWithFields(parseObjectJSON, fields);
|
||||
if (typeof parseOriginalObjectJSON !== 'undefined') {
|
||||
response['original'] = this._toJSONWithFields(
|
||||
parseOriginalObjectJSON,
|
||||
fields
|
||||
);
|
||||
}
|
||||
}
|
||||
Client.pushResponse(this.parseWebSocket, JSON.stringify(response));
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { runLiveQueryEventHandlers } from '../triggers';
|
||||
import { getAuthForSessionToken, Auth } from '../Auth';
|
||||
import { getCacheController } from '../Controllers';
|
||||
import LRU from 'lru-cache';
|
||||
import UserRouter from '../Routers/UsersRouter';
|
||||
|
||||
class ParseLiveQueryServer {
|
||||
clients: Map;
|
||||
@@ -98,6 +99,7 @@ class ParseLiveQueryServer {
|
||||
_inflateParseObject(message: any): void {
|
||||
// Inflate merged object
|
||||
const currentParseObject = message.currentParseObject;
|
||||
UserRouter.removeHiddenProperties(currentParseObject);
|
||||
let className = currentParseObject.className;
|
||||
let parseObject = new Parse.Object(className);
|
||||
parseObject._finishFetch(currentParseObject);
|
||||
@@ -105,6 +107,7 @@ class ParseLiveQueryServer {
|
||||
// Inflate original object
|
||||
const originalParseObject = message.originalParseObject;
|
||||
if (originalParseObject) {
|
||||
UserRouter.removeHiddenProperties(originalParseObject);
|
||||
className = originalParseObject.className;
|
||||
parseObject = new Parse.Object(className);
|
||||
parseObject._finishFetch(originalParseObject);
|
||||
@@ -289,7 +292,11 @@ class ParseLiveQueryServer {
|
||||
return null;
|
||||
}
|
||||
const functionName = 'push' + type;
|
||||
client[functionName](requestId, currentParseObject);
|
||||
client[functionName](
|
||||
requestId,
|
||||
currentParseObject,
|
||||
originalParseObject
|
||||
);
|
||||
},
|
||||
error => {
|
||||
logger.error('Matching ACL error : ', error);
|
||||
|
||||
Reference in New Issue
Block a user