BVB Source Codes

parse-server Show MongoStorageAdapter.js Source code

Return Download parse-server: download MongoStorageAdapter.js Source code - Download parse-server Source code - Type:.js
  1. import MongoCollection       from './MongoCollection';
  2. import MongoSchemaCollection from './MongoSchemaCollection';
  3. import {
  4.   parse as parseUrl,
  5.   format as formatUrl,
  6. } from '../../../vendor/mongodbUrl';
  7. import {
  8.   parseObjectToMongoObjectForCreate,
  9.   mongoObjectToParseObject,
  10.   transformKey,
  11.   transformWhere,
  12.   transformUpdate,
  13. } from './MongoTransform';
  14. import Parse                 from 'parse/node';
  15. import _                     from 'lodash';
  16. import defaults              from '../../../defaults';
  17.  
  18. const mongodb = require('mongodb');
  19. const MongoClient = mongodb.MongoClient;
  20.  
  21. const MongoSchemaCollectionName = '_SCHEMA';
  22.  
  23. const storageAdapterAllCollections = mongoAdapter => {
  24.   return mongoAdapter.connect()
  25.   .then(() => mongoAdapter.database.collections())
  26.   .then(collections => {
  27.     return collections.filter(collection => {
  28.       if (collection.namespace.match(/\.system\./)) {
  29.         return false;
  30.       }
  31.       // TODO: If you have one app with a collection prefix that happens to be a prefix of another
  32.       // apps prefix, this will go very very badly. We should fix that somehow.
  33.       return (collection.collectionName.indexOf(mongoAdapter._collectionPrefix) == 0);
  34.     });
  35.   });
  36. }
  37.  
  38. const convertParseSchemaToMongoSchema = ({...schema}) => {
  39.   delete schema.fields._rperm;
  40.   delete schema.fields._wperm;
  41.  
  42.   if (schema.className === '_User') {
  43.     // Legacy mongo adapter knows about the difference between password and _hashed_password.
  44.     // Future database adapters will only know about _hashed_password.
  45.     // Note: Parse Server will bring back password with injectDefaultSchema, so we don't need
  46.     // to add _hashed_password back ever.
  47.     delete schema.fields._hashed_password;
  48.   }
  49.  
  50.   return schema;
  51. }
  52.  
  53. // Returns { code, error } if invalid, or { result }, an object
  54. // suitable for inserting into _SCHEMA collection, otherwise.
  55. const mongoSchemaFromFieldsAndClassNameAndCLP = (fields, className, classLevelPermissions) => {
  56.   const mongoObject = {
  57.     _id: className,
  58.     objectId: 'string',
  59.     updatedAt: 'string',
  60.     createdAt: 'string'
  61.   };
  62.  
  63.   for (const fieldName in fields) {
  64.     mongoObject[fieldName] = MongoSchemaCollection.parseFieldTypeToMongoFieldType(fields[fieldName]);
  65.   }
  66.  
  67.   if (typeof classLevelPermissions !== 'undefined') {
  68.     mongoObject._metadata = mongoObject._metadata || {};
  69.     if (!classLevelPermissions) {
  70.       delete mongoObject._metadata.class_permissions;
  71.     } else {
  72.       mongoObject._metadata.class_permissions = classLevelPermissions;
  73.     }
  74.   }
  75.  
  76.   return mongoObject;
  77. }
  78.  
  79.  
  80. export class MongoStorageAdapter {
  81.   // Private
  82.   _uri: string;
  83.   _collectionPrefix: string;
  84.   _mongoOptions: Object;
  85.   // Public
  86.   connectionPromise;
  87.   database;
  88.  
  89.   constructor({
  90.     uri = defaults.DefaultMongoURI,
  91.     collectionPrefix = '',
  92.     mongoOptions = {},
  93.   }) {
  94.     this._uri = uri;
  95.     this._collectionPrefix = collectionPrefix;
  96.     this._mongoOptions = mongoOptions;
  97.  
  98.     // MaxTimeMS is not a global MongoDB client option, it is applied per operation.
  99.     this._maxTimeMS = mongoOptions.maxTimeMS;
  100.   }
  101.  
  102.   connect() {
  103.     if (this.connectionPromise) {
  104.       return this.connectionPromise;
  105.     }
  106.  
  107.     // parsing and re-formatting causes the auth value (if there) to get URI
  108.     // encoded
  109.     const encodedUri = formatUrl(parseUrl(this._uri));
  110.  
  111.     this.connectionPromise = MongoClient.connect(encodedUri, this._mongoOptions).then(database => {
  112.       if (!database) {
  113.         delete this.connectionPromise;
  114.         return;
  115.       }
  116.       database.on('error', () => {
  117.         delete this.connectionPromise;
  118.       });
  119.       database.on('close', () => {
  120.         delete this.connectionPromise;
  121.       });
  122.       this.database = database;
  123.     }).catch((err) => {
  124.       delete this.connectionPromise;
  125.       return Promise.reject(err);
  126.     });
  127.  
  128.     return this.connectionPromise;
  129.   }
  130.  
  131.   handleShutdown() {
  132.     if (!this.database) {
  133.       return;
  134.     }
  135.     this.database.close(false);
  136.   }
  137.  
  138.   _adaptiveCollection(name: string) {
  139.     return this.connect()
  140.       .then(() => this.database.collection(this._collectionPrefix + name))
  141.       .then(rawCollection => new MongoCollection(rawCollection));
  142.   }
  143.  
  144.   _schemaCollection() {
  145.     return this.connect()
  146.       .then(() => this._adaptiveCollection(MongoSchemaCollectionName))
  147.       .then(collection => new MongoSchemaCollection(collection));
  148.   }
  149.  
  150.   classExists(name) {
  151.     return this.connect().then(() => {
  152.       return this.database.listCollections({ name: this._collectionPrefix + name }).toArray();
  153.     }).then(collections => {
  154.       return collections.length > 0;
  155.     });
  156.   }
  157.  
  158.   setClassLevelPermissions(className, CLPs) {
  159.     return this._schemaCollection()
  160.     .then(schemaCollection => schemaCollection.updateSchema(className, {
  161.       $set: { _metadata: { class_permissions: CLPs } }
  162.     }));
  163.   }
  164.  
  165.   createClass(className, schema) {
  166.     schema = convertParseSchemaToMongoSchema(schema);
  167.     const mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(schema.fields, className, schema.classLevelPermissions);
  168.     mongoObject._id = className;
  169.     return this._schemaCollection()
  170.     .then(schemaCollection => schemaCollection._collection.insertOne(mongoObject))
  171.     .then(result => MongoSchemaCollection._TESTmongoSchemaToParseSchema(result.ops[0]))
  172.     .catch(error => {
  173.       if (error.code === 11000) { //Mongo's duplicate key error
  174.         throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Class already exists.');
  175.       } else {
  176.         throw error;
  177.       }
  178.     })
  179.   }
  180.  
  181.   addFieldIfNotExists(className, fieldName, type) {
  182.     return this._schemaCollection()
  183.     .then(schemaCollection => schemaCollection.addFieldIfNotExists(className, fieldName, type));
  184.   }
  185.  
  186.   // Drops a collection. Resolves with true if it was a Parse Schema (eg. _User, Custom, etc.)
  187.   // and resolves with false if it wasn't (eg. a join table). Rejects if deletion was impossible.
  188.   deleteClass(className) {
  189.     return this._adaptiveCollection(className)
  190.     .then(collection => collection.drop())
  191.     .catch(error => {
  192.       // 'ns not found' means collection was already gone. Ignore deletion attempt.
  193.       if (error.message == 'ns not found') {
  194.         return;
  195.       }
  196.       throw error;
  197.     })
  198.     // We've dropped the collection, now remove the _SCHEMA document
  199.     .then(() => this._schemaCollection())
  200.     .then(schemaCollection => schemaCollection.findAndDeleteSchema(className))
  201.   }
  202.  
  203.   // Delete all data known to this adatper. Used for testing.
  204.   deleteAllClasses() {
  205.     return storageAdapterAllCollections(this)
  206.     .then(collections => Promise.all(collections.map(collection => collection.drop())));
  207.   }
  208.  
  209.   // Remove the column and all the data. For Relations, the _Join collection is handled
  210.   // specially, this function does not delete _Join columns. It should, however, indicate
  211.   // that the relation fields does not exist anymore. In mongo, this means removing it from
  212.   // the _SCHEMA collection.  There should be no actual data in the collection under the same name
  213.   // as the relation column, so it's fine to attempt to delete it. If the fields listed to be
  214.   // deleted do not exist, this function should return successfully anyways. Checking for
  215.   // attempts to delete non-existent fields is the responsibility of Parse Server.
  216.  
  217.   // Pointer field names are passed for legacy reasons: the original mongo
  218.   // format stored pointer field names differently in the database, and therefore
  219.   // needed to know the type of the field before it could delete it. Future database
  220.   // adatpers should ignore the pointerFieldNames argument. All the field names are in
  221.   // fieldNames, they show up additionally in the pointerFieldNames database for use
  222.   // by the mongo adapter, which deals with the legacy mongo format.
  223.  
  224.   // This function is not obligated to delete fields atomically. It is given the field
  225.   // names in a list so that databases that are capable of deleting fields atomically
  226.   // may do so.
  227.  
  228.   // Returns a Promise.
  229.   deleteFields(className, schema, fieldNames) {
  230.     const mongoFormatNames = fieldNames.map(fieldName => {
  231.       if (schema.fields[fieldName].type === 'Pointer') {
  232.         return `_p_${fieldName}`
  233.       } else {
  234.         return fieldName;
  235.       }
  236.     });
  237.     const collectionUpdate = { '$unset' : {} };
  238.     mongoFormatNames.forEach(name => {
  239.       collectionUpdate['$unset'][name] = null;
  240.     });
  241.  
  242.     const schemaUpdate = { '$unset' : {} };
  243.     fieldNames.forEach(name => {
  244.       schemaUpdate['$unset'][name] = null;
  245.     });
  246.  
  247.     return this._adaptiveCollection(className)
  248.     .then(collection => collection.updateMany({}, collectionUpdate))
  249.     .then(() => this._schemaCollection())
  250.     .then(schemaCollection => schemaCollection.updateSchema(className, schemaUpdate));
  251.   }
  252.  
  253.   // Return a promise for all schemas known to this adapter, in Parse format. In case the
  254.   // schemas cannot be retrieved, returns a promise that rejects. Requirements for the
  255.   // rejection reason are TBD.
  256.   getAllClasses() {
  257.     return this._schemaCollection().then(schemasCollection => schemasCollection._fetchAllSchemasFrom_SCHEMA());
  258.   }
  259.  
  260.   // Return a promise for the schema with the given name, in Parse format. If
  261.   // this adapter doesn't know about the schema, return a promise that rejects with
  262.   // undefined as the reason.
  263.   getClass(className) {
  264.     return this._schemaCollection()
  265.     .then(schemasCollection => schemasCollection._fechOneSchemaFrom_SCHEMA(className))
  266.   }
  267.  
  268.   // TODO: As yet not particularly well specified. Creates an object. Maybe shouldn't even need the schema,
  269.   // and should infer from the type. Or maybe does need the schema for validations. Or maybe needs
  270.   // the schem only for the legacy mongo format. We'll figure that out later.
  271.   createObject(className, schema, object) {
  272.     schema = convertParseSchemaToMongoSchema(schema);
  273.     const mongoObject = parseObjectToMongoObjectForCreate(className, object, schema);
  274.     return this._adaptiveCollection(className)
  275.     .then(collection => collection.insertOne(mongoObject))
  276.     .catch(error => {
  277.       if (error.code === 11000) { // Duplicate value
  278.         throw new Parse.Error(Parse.Error.DUPLICATE_VALUE,
  279.             'A duplicate value for a field with unique values was provided');
  280.       }
  281.       throw error;
  282.     });
  283.   }
  284.  
  285.   // Remove all objects that match the given Parse Query.
  286.   // If no objects match, reject with OBJECT_NOT_FOUND. If objects are found and deleted, resolve with undefined.
  287.   // If there is some other error, reject with INTERNAL_SERVER_ERROR.
  288.   deleteObjectsByQuery(className, schema, query) {
  289.     schema = convertParseSchemaToMongoSchema(schema);
  290.     return this._adaptiveCollection(className)
  291.     .then(collection => {
  292.       const mongoWhere = transformWhere(className, query, schema);
  293.       return collection.deleteMany(mongoWhere)
  294.     })
  295.     .then(({ result }) => {
  296.       if (result.n === 0) {
  297.         throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
  298.       }
  299.       return Promise.resolve();
  300.     }, () => {
  301.       throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database adapter error');
  302.     });
  303.   }
  304.  
  305.   // Apply the update to all objects that match the given Parse Query.
  306.   updateObjectsByQuery(className, schema, query, update) {
  307.     schema = convertParseSchemaToMongoSchema(schema);
  308.     const mongoUpdate = transformUpdate(className, update, schema);
  309.     const mongoWhere = transformWhere(className, query, schema);
  310.     return this._adaptiveCollection(className)
  311.     .then(collection => collection.updateMany(mongoWhere, mongoUpdate));
  312.   }
  313.  
  314.   // Atomically finds and updates an object based on query.
  315.   // Return value not currently well specified.
  316.   findOneAndUpdate(className, schema, query, update) {
  317.     schema = convertParseSchemaToMongoSchema(schema);
  318.     const mongoUpdate = transformUpdate(className, update, schema);
  319.     const mongoWhere = transformWhere(className, query, schema);
  320.     return this._adaptiveCollection(className)
  321.     .then(collection => collection._mongoCollection.findAndModify(mongoWhere, [], mongoUpdate, { new: true }))
  322.     .then(result => mongoObjectToParseObject(className, result.value, schema));
  323.   }
  324.  
  325.   // Hopefully we can get rid of this. It's only used for config and hooks.
  326.   upsertOneObject(className, schema, query, update) {
  327.     schema = convertParseSchemaToMongoSchema(schema);
  328.     const mongoUpdate = transformUpdate(className, update, schema);
  329.     const mongoWhere = transformWhere(className, query, schema);
  330.     return this._adaptiveCollection(className)
  331.     .then(collection => collection.upsertOne(mongoWhere, mongoUpdate));
  332.   }
  333.  
  334.   // Executes a find. Accepts: className, query in Parse format, and { skip, limit, sort }.
  335.   find(className, schema, query, { skip, limit, sort, keys }) {
  336.     schema = convertParseSchemaToMongoSchema(schema);
  337.     const mongoWhere = transformWhere(className, query, schema);
  338.     const mongoSort = _.mapKeys(sort, (value, fieldName) => transformKey(className, fieldName, schema));
  339.     const mongoKeys = _.reduce(keys, (memo, key) => {
  340.       memo[transformKey(className, key, schema)] = 1;
  341.       return memo;
  342.     }, {});
  343.     return this._adaptiveCollection(className)
  344.     .then(collection => collection.find(mongoWhere, {
  345.       skip,
  346.       limit,
  347.       sort: mongoSort,
  348.       keys: mongoKeys,
  349.       maxTimeMS: this._maxTimeMS,
  350.     }))
  351.     .then(objects => objects.map(object => mongoObjectToParseObject(className, object, schema)))
  352.   }
  353.  
  354.   // Create a unique index. Unique indexes on nullable fields are not allowed. Since we don't
  355.   // currently know which fields are nullable and which aren't, we ignore that criteria.
  356.   // As such, we shouldn't expose this function to users of parse until we have an out-of-band
  357.   // Way of determining if a field is nullable. Undefined doesn't count against uniqueness,
  358.   // which is why we use sparse indexes.
  359.   ensureUniqueness(className, schema, fieldNames) {
  360.     schema = convertParseSchemaToMongoSchema(schema);
  361.     const indexCreationRequest = {};
  362.     const mongoFieldNames = fieldNames.map(fieldName => transformKey(className, fieldName, schema));
  363.     mongoFieldNames.forEach(fieldName => {
  364.       indexCreationRequest[fieldName] = 1;
  365.     });
  366.     return this._adaptiveCollection(className)
  367.     .then(collection => collection._ensureSparseUniqueIndexInBackground(indexCreationRequest))
  368.     .catch(error => {
  369.       if (error.code === 11000) {
  370.         throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Tried to ensure field uniqueness for a class that already has duplicates.');
  371.       } else {
  372.         throw error;
  373.       }
  374.     });
  375.   }
  376.  
  377.   // Used in tests
  378.   _rawFind(className, query) {
  379.     return this._adaptiveCollection(className).then(collection => collection.find(query, {
  380.       maxTimeMS: this._maxTimeMS,
  381.     }));
  382.   }
  383.  
  384.   // Executes a count.
  385.   count(className, schema, query) {
  386.     schema = convertParseSchemaToMongoSchema(schema);
  387.     return this._adaptiveCollection(className)
  388.     .then(collection => collection.count(transformWhere(className, query, schema), {
  389.       maxTimeMS: this._maxTimeMS,
  390.     }));
  391.   }
  392.  
  393.   performInitialization() {
  394.     return Promise.resolve();
  395.   }
  396. }
  397.  
  398. export default MongoStorageAdapter;
  399. module.exports = MongoStorageAdapter; // Required for tests
  400.  
downloadMongoStorageAdapter.js Source code - Download parse-server Source code
Related Source Codes/Software:
react-boilerplate - 2017-06-07
webtorrent - Streaming torrent client for the web ... 2017-06-06
machine-learning-for-software-engineers - A complete daily plan for studying to become a mac... 2017-06-06
upterm - A terminal emulator for the 21st century. 2017-06-06
lottie-android - Render After Effects animations natively on Androi... 2017-06-07
AsyncDisplayKit - Smooth asynchronous user interfaces for iOS apps. ... 2017-06-07
ionicons - The premium icon font for Ionic ... 2017-06-07
storybook - 2017-06-07
prettier - Prettier is an opinionated JavaScript formatter. ... 2017-06-08
CRYENGINE - CRYENGINE is a powerful real-time game development... 2017-06-11
postal - 2017-06-11
reactide - Reactide is the first dedicated IDE for React web ... 2017-06-11
rkt - rkt is a pod-native container engine for Linux. It... 2017-06-11
uWebSockets - Tiny WebSockets https://for... 2017-06-11
realworld - TodoMVC for the RealWorld - Exemplary fullstack Me... 2017-06-11
goreplay - GoReplay is an open-source tool for capturing and ... 2017-06-10
pyenv - Simple Python version management 2017-06-10
redux-saga - An alternative side effect model for Redux apps ... 2017-06-10
angular-starter - 2017-06-10

 Back to top