Silences warnings from mongodb client (#5025)
* Silences warnings from mongodb client * Update count, delete and finds to recommended implementations * With new parser, readPref will be null by default * Update flaky specs wih async/await style * Adds gridstore adapter spec * Use GridFSBucketStorage adapter
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
// * getFileData(filename)
|
||||
// * getFileLocation(config, filename)
|
||||
//
|
||||
// Default is GridStoreAdapter, which requires mongo
|
||||
// Default is GridFSBucketAdapter, which requires mongo
|
||||
// and for the API server to be using the DatabaseController with Mongo
|
||||
// database adapter.
|
||||
|
||||
|
||||
95
src/Adapters/Files/GridFSBucketAdapter.js
Normal file
95
src/Adapters/Files/GridFSBucketAdapter.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
GridFSBucketAdapter
|
||||
Stores files in Mongo using GridStore
|
||||
Requires the database adapter to be based on mongoclient
|
||||
|
||||
@flow weak
|
||||
*/
|
||||
|
||||
// @flow-disable-next
|
||||
import { MongoClient, GridFSBucket, Db } from 'mongodb';
|
||||
import { FilesAdapter } from './FilesAdapter';
|
||||
import defaults from '../../defaults';
|
||||
|
||||
export class GridFSBucketAdapter extends FilesAdapter {
|
||||
_databaseURI: string;
|
||||
_connectionPromise: Promise<Db>;
|
||||
|
||||
constructor(mongoDatabaseURI = defaults.DefaultMongoURI) {
|
||||
super();
|
||||
this._databaseURI = mongoDatabaseURI;
|
||||
}
|
||||
|
||||
_connect() {
|
||||
if (!this._connectionPromise) {
|
||||
this._connectionPromise = MongoClient.connect(this._databaseURI).then(
|
||||
client => client.db(client.s.options.dbName)
|
||||
);
|
||||
}
|
||||
return this._connectionPromise;
|
||||
}
|
||||
|
||||
_getBucket() {
|
||||
return this._connect().then(database => new GridFSBucket(database));
|
||||
}
|
||||
|
||||
// For a given config object, filename, and data, store a file
|
||||
// Returns a promise
|
||||
async createFile(filename: string, data) {
|
||||
const bucket = await this._getBucket();
|
||||
const stream = await bucket.openUploadStream(filename);
|
||||
await stream.write(data);
|
||||
stream.end();
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('finish', resolve);
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async deleteFile(filename: string) {
|
||||
const bucket = await this._getBucket();
|
||||
const documents = await bucket.find({ filename: filename }).toArray();
|
||||
if (documents.length === 0) {
|
||||
throw new Error('FileNotFound');
|
||||
}
|
||||
return Promise.all(
|
||||
documents.map(doc => {
|
||||
return bucket.delete(doc._id);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async getFileData(filename: string) {
|
||||
const stream = await this.getDownloadStream(filename);
|
||||
stream.read();
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
stream.on('data', data => {
|
||||
chunks.push(data);
|
||||
});
|
||||
stream.on('end', () => {
|
||||
resolve(Buffer.concat(chunks));
|
||||
});
|
||||
stream.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getFileLocation(config, filename) {
|
||||
return (
|
||||
config.mount +
|
||||
'/files/' +
|
||||
config.applicationId +
|
||||
'/' +
|
||||
encodeURIComponent(filename)
|
||||
);
|
||||
}
|
||||
|
||||
async getDownloadStream(filename: string) {
|
||||
const bucket = await this._getBucket();
|
||||
return bucket.openDownloadStreamByName(filename);
|
||||
}
|
||||
}
|
||||
|
||||
export default GridFSBucketAdapter;
|
||||
@@ -80,7 +80,7 @@ export default class MongoCollection {
|
||||
}
|
||||
|
||||
count(query, { skip, limit, sort, maxTimeMS, readPreference } = {}) {
|
||||
const countOperation = this._mongoCollection.count(query, {
|
||||
const countOperation = this._mongoCollection.countDocuments(query, {
|
||||
skip,
|
||||
limit,
|
||||
sort,
|
||||
@@ -109,7 +109,7 @@ export default class MongoCollection {
|
||||
// If there is nothing that matches the query - does insert
|
||||
// Postgres Note: `INSERT ... ON CONFLICT UPDATE` that is available since 9.5.
|
||||
upsertOne(query, update) {
|
||||
return this._mongoCollection.update(query, update, { upsert: true });
|
||||
return this._mongoCollection.updateOne(query, update, { upsert: true });
|
||||
}
|
||||
|
||||
updateOne(query, update) {
|
||||
@@ -126,7 +126,7 @@ export default class MongoCollection {
|
||||
|
||||
_ensureSparseUniqueIndexInBackground(indexRequest) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this._mongoCollection.ensureIndex(
|
||||
this._mongoCollection.createIndex(
|
||||
indexRequest,
|
||||
{ unique: true, background: true, sparse: true },
|
||||
error => {
|
||||
|
||||
@@ -135,6 +135,7 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
this._uri = uri;
|
||||
this._collectionPrefix = collectionPrefix;
|
||||
this._mongoOptions = mongoOptions;
|
||||
this._mongoOptions.useNewUrlParser = true;
|
||||
|
||||
// MaxTimeMS is not a global MongoDB client option, it is applied per operation.
|
||||
this._maxTimeMS = mongoOptions.maxTimeMS;
|
||||
@@ -385,7 +386,7 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
return storageAdapterAllCollections(this).then(collections =>
|
||||
Promise.all(
|
||||
collections.map(
|
||||
collection => (fast ? collection.remove({}) : collection.drop())
|
||||
collection => (fast ? collection.deleteMany({}) : collection.drop())
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -557,8 +558,8 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
const mongoWhere = transformWhere(className, query, schema);
|
||||
return this._adaptiveCollection(className)
|
||||
.then(collection =>
|
||||
collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, {
|
||||
new: true,
|
||||
collection._mongoCollection.findOneAndUpdate(mongoWhere, mongoUpdate, {
|
||||
returnOriginal: false,
|
||||
})
|
||||
)
|
||||
.then(result => mongoObjectToParseObject(className, result.value, schema))
|
||||
|
||||
@@ -18,7 +18,7 @@ import DatabaseController from './DatabaseController';
|
||||
import SchemaCache from './SchemaCache';
|
||||
|
||||
// Adapters
|
||||
import { GridStoreAdapter } from '../Adapters/Files/GridStoreAdapter';
|
||||
import { GridFSBucketAdapter } from '../Adapters/Files/GridFSBucketAdapter';
|
||||
import { WinstonLoggerAdapter } from '../Adapters/Logger/WinstonLoggerAdapter';
|
||||
import { InMemoryCacheAdapter } from '../Adapters/Cache/InMemoryCacheAdapter';
|
||||
import { AnalyticsAdapter } from '../Adapters/Analytics/AnalyticsAdapter';
|
||||
@@ -96,7 +96,7 @@ export function getFilesController(
|
||||
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
|
||||
}
|
||||
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
|
||||
return new GridStoreAdapter(databaseURI);
|
||||
return new GridFSBucketAdapter(databaseURI);
|
||||
});
|
||||
return new FilesController(filesControllerAdapter, appId, {
|
||||
preserveFileName,
|
||||
|
||||
@@ -43,7 +43,7 @@ addParseCloud();
|
||||
// ParseServer works like a constructor of an express app.
|
||||
// The args that we understand are:
|
||||
// "analyticsAdapter": an adapter class for analytics
|
||||
// "filesAdapter": a class like GridStoreAdapter providing create, get,
|
||||
// "filesAdapter": a class like GridFSBucketAdapter providing create, get,
|
||||
// and delete
|
||||
// "loggerAdapter": a class like WinstonLoggerAdapter providing info, error,
|
||||
// and query
|
||||
|
||||
Reference in New Issue
Block a user