Просмотр исходного кода

chore: allows properties chaining

e22m4u 1 год назад
Родитель
Сommit
4035dd8b8f
4 измененных файлов с 428 добавлено и 99 удалено
  1. 84 47
      dist/cjs/index.cjs
  2. 3 3
      package.json
  3. 94 49
      src/mongodb-adapter.js
  4. 247 0
      src/mongodb-adapter.spec.js

+ 84 - 47
dist/cjs/index.cjs

@@ -253,6 +253,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * Get id prop name.
    *
    * @param modelName
+   * @private
    */
   _getIdPropName(modelName) {
     return this.getService(import_js_repository8.ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
@@ -263,6 +264,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * Get id col name.
    *
    * @param modelName
+   * @private
    */
   _getIdColName(modelName) {
     return this.getService(import_js_repository8.ModelDefinitionUtils).getPrimaryKeyAsColumnName(
@@ -273,7 +275,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * Coerce id.
    *
    * @param value
-   * @return {ObjectId|*}
+   * @returns {ObjectId|*}
    * @private
    */
   _coerceId(value) {
@@ -299,7 +301,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object} modelData
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _toDatabase(modelName, modelData) {
@@ -329,7 +331,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object} tableData
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _fromDatabase(modelName, tableData) {
@@ -358,7 +360,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * Get collection.
    *
    * @param {string} modelName
-   * @return {*}
+   * @returns {*}
    * @private
    */
   _getCollection(modelName) {
@@ -373,7 +375,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * Get id type.
    *
    * @param modelName
-   * @return {string|*}
+   * @returns {string|*}
    * @private
    */
   _getIdType(modelName) {
@@ -381,42 +383,18 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
     const pkPropName = utils.getPrimaryKeyAsPropertyName(modelName);
     return utils.getDataTypeByPropertyName(modelName, pkPropName);
   }
-  /**
-   * Build projection.
-   *
-   * @param {string} modelName
-   * @param {string|string[]} fields
-   * @return {Record<string, number>|undefined}
-   * @private
-   */
-  _buildProjection(modelName, fields) {
-    if (fields == null) return;
-    if (Array.isArray(fields) === false) fields = [fields];
-    if (!fields.length) return;
-    if (fields.indexOf("_id") === -1) fields.push("_id");
-    return fields.reduce((acc, field) => {
-      if (!field || typeof field !== "string")
-        throw new import_js_repository9.InvalidArgumentError(
-          'The provided option "fields" should be a non-empty String or an Array of non-empty String, but %v given.',
-          field
-        );
-      let colName = this._getColName(modelName, field);
-      acc[colName] = 1;
-      return acc;
-    }, {});
-  }
   /**
    * Get col name.
    *
    * @param {string} modelName
    * @param {string} propName
-   * @return {string}
+   * @returns {string}
    * @private
    */
   _getColName(modelName, propName) {
     if (!propName || typeof propName !== "string")
       throw new import_js_repository9.InvalidArgumentError(
-        "A property name must be a non-empty String, but %v given.",
+        "Property name must be a non-empty String, but %v given.",
         propName
       );
     const utils = this.getService(import_js_repository8.ModelDefinitionUtils);
@@ -430,19 +408,78 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
     }
     return colName;
   }
+  /**
+   * Convert prop names chain to col names chain.
+   *
+   * @param {string} modelName
+   * @param {string} propsChain
+   * @returns {string}
+   * @private
+   */
+  _convertPropNamesChainToColNamesChain(modelName, propsChain) {
+    if (!modelName || typeof modelName !== "string")
+      throw new import_js_repository9.InvalidArgumentError(
+        "Model name must be a non-empty String, but %v given.",
+        modelName
+      );
+    if (!propsChain || typeof propsChain !== "string")
+      throw new import_js_repository9.InvalidArgumentError(
+        "Properties chain must be a non-empty String, but %v given.",
+        propsChain
+      );
+    propsChain = propsChain.replace(/\.{2,}/g, ".");
+    const propNames = propsChain.split(".");
+    const utils = this.getService(import_js_repository8.ModelDefinitionUtils);
+    let currModelName = modelName;
+    return propNames.map((currPropName) => {
+      if (!currModelName) return currPropName;
+      const colName = this._getColName(currModelName, currPropName);
+      currModelName = utils.getModelNameOfPropertyValueIfDefined(
+        currModelName,
+        currPropName
+      );
+      return colName;
+    }).join(".");
+  }
+  /**
+   * Build projection.
+   *
+   * @param {string} modelName
+   * @param {string|string[]} fields
+   * @returns {Record<string, number>|undefined}
+   * @private
+   */
+  _buildProjection(modelName, fields) {
+    if (fields == null) return;
+    if (Array.isArray(fields) === false) fields = [fields];
+    if (!fields.length) return;
+    if (fields.indexOf("_id") === -1) fields.push("_id");
+    return fields.reduce((acc, field) => {
+      if (!field || typeof field !== "string")
+        throw new import_js_repository9.InvalidArgumentError(
+          'The provided option "fields" should be a non-empty String or an Array of non-empty String, but %v given.',
+          field
+        );
+      let colName = this._convertPropNamesChainToColNamesChain(
+        modelName,
+        field
+      );
+      acc[colName] = 1;
+      return acc;
+    }, {});
+  }
   /**
    * Build sort.
    *
    * @param {string} modelName
    * @param {string|string[]} clause
-   * @return {object|undefined}
+   * @returns {object|undefined}
    * @private
    */
   _buildSort(modelName, clause) {
     if (clause == null) return;
     if (Array.isArray(clause) === false) clause = [clause];
     if (!clause.length) return;
-    const utils = this.getService(import_js_repository8.ModelDefinitionUtils);
     const idPropName = this._getIdPropName(modelName);
     return clause.reduce((acc, order) => {
       if (!order || typeof order !== "string")
@@ -456,7 +493,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
         field = "_id";
       } else {
         try {
-          field = utils.getColumnNameByPropertyName(modelName, field);
+          field = this._convertPropNamesChainToColNamesChain(modelName, field);
         } catch (error) {
           if (!(error instanceof import_js_repository9.InvalidArgumentError) || error.message.indexOf("does not have the property") === -1) {
             throw error;
@@ -472,7 +509,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object} clause
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _buildQuery(modelName, clause) {
@@ -507,7 +544,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
       if (key === idPropName) {
         key = "_id";
       } else {
-        key = this._getColName(modelName, key);
+        key = this._convertPropNamesChainToColNamesChain(modelName, key);
       }
       if (typeof cond === "string") {
         query[key] = this._coerceId(cond);
@@ -665,7 +702,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async create(modelName, modelData, filter = void 0) {
     const idPropName = this._getIdPropName(modelName);
@@ -697,7 +734,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string|number} id
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async replaceById(modelName, id, modelData, filter = void 0) {
     id = this._coerceId(id);
@@ -721,7 +758,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async replaceOrCreate(modelName, modelData, filter = void 0) {
     const idPropName = this._getIdPropName(modelName);
@@ -762,7 +799,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async patch(modelName, modelData, where = void 0) {
     const idPropName = this._getIdPropName(modelName);
@@ -780,7 +817,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string|number} id
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async patchById(modelName, id, modelData, filter = void 0) {
     id = this._coerceId(id);
@@ -803,7 +840,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object|undefined} filter
-   * @return {Promise<object[]>}
+   * @returns {Promise<object[]>}
    */
   async find(modelName, filter = void 0) {
     filter = filter || {};
@@ -823,7 +860,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    * @param {string} modelName
    * @param {string|number} id
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async findById(modelName, id, filter = void 0) {
     id = this._coerceId(id);
@@ -842,7 +879,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async delete(modelName, where = void 0) {
     const table = this._getCollection(modelName);
@@ -855,7 +892,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {string|number} id
-   * @return {Promise<boolean>}
+   * @returns {Promise<boolean>}
    */
   async deleteById(modelName, id) {
     id = this._coerceId(id);
@@ -868,7 +905,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {string|number} id
-   * @return {Promise<boolean>}
+   * @returns {Promise<boolean>}
    */
   async exists(modelName, id) {
     id = this._coerceId(id);
@@ -881,7 +918,7 @@ var _MongodbAdapter = class _MongodbAdapter extends import_js_repository3.Adapte
    *
    * @param {string} modelName
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async count(modelName, where = void 0) {
     const query = this._buildQuery(modelName, where);

+ 3 - 3
package.json

@@ -16,8 +16,8 @@
     "lint": "eslint ./src",
     "lint:fix": "eslint ./src --fix",
     "format": "prettier --write \"./src/**/*.js\"",
-    "test": "npm run lint && c8 --reporter=text-summary mocha",
-    "test:coverage": "npm run lint && c8 --reporter=text mocha",
+    "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
+    "test:coverage": "npm run lint && c8 --reporter=text mocha --bail",
     "build:cjs": "rimraf ./dist/cjs && node --no-warnings=ExperimentalWarning build-cjs.js",
     "prepare": "husky"
   },
@@ -42,7 +42,7 @@
   },
   "peerDependencies": {
     "@e22m4u/js-format": "0.1.x",
-    "@e22m4u/js-repository": "0.2.x",
+    "@e22m4u/js-repository": "~0.2.4",
     "@e22m4u/js-service": "0.2.x"
   },
   "devDependencies": {

+ 94 - 49
src/mongodb-adapter.js

@@ -129,6 +129,7 @@ export class MongodbAdapter extends Adapter {
    * Get id prop name.
    *
    * @param modelName
+   * @private
    */
   _getIdPropName(modelName) {
     return this.getService(ModelDefinitionUtils).getPrimaryKeyAsPropertyName(
@@ -140,6 +141,7 @@ export class MongodbAdapter extends Adapter {
    * Get id col name.
    *
    * @param modelName
+   * @private
    */
   _getIdColName(modelName) {
     return this.getService(ModelDefinitionUtils).getPrimaryKeyAsColumnName(
@@ -151,7 +153,7 @@ export class MongodbAdapter extends Adapter {
    * Coerce id.
    *
    * @param value
-   * @return {ObjectId|*}
+   * @returns {ObjectId|*}
    * @private
    */
   _coerceId(value) {
@@ -179,7 +181,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object} modelData
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _toDatabase(modelName, modelData) {
@@ -213,7 +215,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object} tableData
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _fromDatabase(modelName, tableData) {
@@ -246,7 +248,7 @@ export class MongodbAdapter extends Adapter {
    * Get collection.
    *
    * @param {string} modelName
-   * @return {*}
+   * @returns {*}
    * @private
    */
   _getCollection(modelName) {
@@ -263,7 +265,7 @@ export class MongodbAdapter extends Adapter {
    * Get id type.
    *
    * @param modelName
-   * @return {string|*}
+   * @returns {string|*}
    * @private
    */
   _getIdType(modelName) {
@@ -272,44 +274,18 @@ export class MongodbAdapter extends Adapter {
     return utils.getDataTypeByPropertyName(modelName, pkPropName);
   }
 
-  /**
-   * Build projection.
-   *
-   * @param {string} modelName
-   * @param {string|string[]} fields
-   * @return {Record<string, number>|undefined}
-   * @private
-   */
-  _buildProjection(modelName, fields) {
-    if (fields == null) return;
-    if (Array.isArray(fields) === false) fields = [fields];
-    if (!fields.length) return;
-    if (fields.indexOf('_id') === -1) fields.push('_id');
-    return fields.reduce((acc, field) => {
-      if (!field || typeof field !== 'string')
-        throw new InvalidArgumentError(
-          'The provided option "fields" should be a non-empty String ' +
-            'or an Array of non-empty String, but %v given.',
-          field,
-        );
-      let colName = this._getColName(modelName, field);
-      acc[colName] = 1;
-      return acc;
-    }, {});
-  }
-
   /**
    * Get col name.
    *
    * @param {string} modelName
    * @param {string} propName
-   * @return {string}
+   * @returns {string}
    * @private
    */
   _getColName(modelName, propName) {
     if (!propName || typeof propName !== 'string')
       throw new InvalidArgumentError(
-        'A property name must be a non-empty String, but %v given.',
+        'Property name must be a non-empty String, but %v given.',
         propName,
       );
     const utils = this.getService(ModelDefinitionUtils);
@@ -327,19 +303,88 @@ export class MongodbAdapter extends Adapter {
     return colName;
   }
 
+  /**
+   * Convert prop names chain to col names chain.
+   *
+   * @param {string} modelName
+   * @param {string} propsChain
+   * @returns {string}
+   * @private
+   */
+  _convertPropNamesChainToColNamesChain(modelName, propsChain) {
+    if (!modelName || typeof modelName !== 'string')
+      throw new InvalidArgumentError(
+        'Model name must be a non-empty String, but %v given.',
+        modelName,
+      );
+    if (!propsChain || typeof propsChain !== 'string')
+      throw new InvalidArgumentError(
+        'Properties chain must be a non-empty String, but %v given.',
+        propsChain,
+      );
+    // удаление повторяющихся точек,
+    // где строка "foo..bar.baz...qux"
+    // будет преобразована к "foo.bar.baz.qux"
+    propsChain = propsChain.replace(/\.{2,}/g, '.');
+    // разделение цепочки на массив свойств,
+    // и формирование цепочки имен колонок
+    const propNames = propsChain.split('.');
+    const utils = this.getService(ModelDefinitionUtils);
+    let currModelName = modelName;
+    return propNames
+      .map(currPropName => {
+        if (!currModelName) return currPropName;
+        const colName = this._getColName(currModelName, currPropName);
+        currModelName = utils.getModelNameOfPropertyValueIfDefined(
+          currModelName,
+          currPropName,
+        );
+        return colName;
+      })
+      .join('.');
+  }
+
+  /**
+   * Build projection.
+   *
+   * @param {string} modelName
+   * @param {string|string[]} fields
+   * @returns {Record<string, number>|undefined}
+   * @private
+   */
+  _buildProjection(modelName, fields) {
+    if (fields == null) return;
+    if (Array.isArray(fields) === false) fields = [fields];
+    if (!fields.length) return;
+    if (fields.indexOf('_id') === -1) fields.push('_id');
+    return fields.reduce((acc, field) => {
+      if (!field || typeof field !== 'string')
+        throw new InvalidArgumentError(
+          'The provided option "fields" should be a non-empty String ' +
+            'or an Array of non-empty String, but %v given.',
+          field,
+        );
+      let colName = this._convertPropNamesChainToColNamesChain(
+        modelName,
+        field,
+      );
+      acc[colName] = 1;
+      return acc;
+    }, {});
+  }
+
   /**
    * Build sort.
    *
    * @param {string} modelName
    * @param {string|string[]} clause
-   * @return {object|undefined}
+   * @returns {object|undefined}
    * @private
    */
   _buildSort(modelName, clause) {
     if (clause == null) return;
     if (Array.isArray(clause) === false) clause = [clause];
     if (!clause.length) return;
-    const utils = this.getService(ModelDefinitionUtils);
     const idPropName = this._getIdPropName(modelName);
     return clause.reduce((acc, order) => {
       if (!order || typeof order !== 'string')
@@ -354,7 +399,7 @@ export class MongodbAdapter extends Adapter {
         field = '_id';
       } else {
         try {
-          field = utils.getColumnNameByPropertyName(modelName, field);
+          field = this._convertPropNamesChainToColNamesChain(modelName, field);
         } catch (error) {
           if (
             !(error instanceof InvalidArgumentError) ||
@@ -374,7 +419,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object} clause
-   * @return {object}
+   * @returns {object}
    * @private
    */
   _buildQuery(modelName, clause) {
@@ -410,7 +455,7 @@ export class MongodbAdapter extends Adapter {
       if (key === idPropName) {
         key = '_id';
       } else {
-        key = this._getColName(modelName, key);
+        key = this._convertPropNamesChainToColNamesChain(modelName, key);
       }
       // string
       if (typeof cond === 'string') {
@@ -596,7 +641,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async create(modelName, modelData, filter = undefined) {
     const idPropName = this._getIdPropName(modelName);
@@ -631,7 +676,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string|number} id
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async replaceById(modelName, id, modelData, filter = undefined) {
     id = this._coerceId(id);
@@ -656,7 +701,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async replaceOrCreate(modelName, modelData, filter = undefined) {
     const idPropName = this._getIdPropName(modelName);
@@ -700,7 +745,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string} modelName
    * @param {object} modelData
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async patch(modelName, modelData, where = undefined) {
     const idPropName = this._getIdPropName(modelName);
@@ -719,7 +764,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string|number} id
    * @param {object} modelData
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async patchById(modelName, id, modelData, filter = undefined) {
     id = this._coerceId(id);
@@ -743,7 +788,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object|undefined} filter
-   * @return {Promise<object[]>}
+   * @returns {Promise<object[]>}
    */
   async find(modelName, filter = undefined) {
     filter = filter || {};
@@ -764,7 +809,7 @@ export class MongodbAdapter extends Adapter {
    * @param {string} modelName
    * @param {string|number} id
    * @param {object|undefined} filter
-   * @return {Promise<object>}
+   * @returns {Promise<object>}
    */
   async findById(modelName, id, filter = undefined) {
     id = this._coerceId(id);
@@ -784,7 +829,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async delete(modelName, where = undefined) {
     const table = this._getCollection(modelName);
@@ -798,7 +843,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {string|number} id
-   * @return {Promise<boolean>}
+   * @returns {Promise<boolean>}
    */
   async deleteById(modelName, id) {
     id = this._coerceId(id);
@@ -812,7 +857,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {string|number} id
-   * @return {Promise<boolean>}
+   * @returns {Promise<boolean>}
    */
   async exists(modelName, id) {
     id = this._coerceId(id);
@@ -826,7 +871,7 @@ export class MongodbAdapter extends Adapter {
    *
    * @param {string} modelName
    * @param {object|undefined} where
-   * @return {Promise<number>}
+   * @returns {Promise<number>}
    */
   async count(modelName, where = undefined) {
     const query = this._buildQuery(modelName, where);

+ 247 - 0
src/mongodb-adapter.spec.js

@@ -107,6 +107,44 @@ describe('MongodbAdapter', function () {
         expect(res).to.be.eql({_id: 1, bar: 1});
       });
 
+      it('converts property names chain to column names chain', async function () {
+        const schema = createSchema();
+        schema.defineModel({
+          name: 'modelA',
+          datasource: 'mongodb',
+          properties: {
+            foo: {
+              type: DataType.OBJECT,
+              columnName: 'fooCol',
+              model: 'modelB',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelB',
+          properties: {
+            bar: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelC',
+          properties: {
+            baz: {
+              type: DataType.OBJECT,
+              columnName: 'bazCol',
+            },
+          },
+        });
+        const A = await schema
+          .getService(AdapterRegistry)
+          .getAdapter('mongodb');
+        const res = A._buildProjection('modelA', 'foo.bar.baz.qux');
+        expect(res).to.be.eql({_id: 1, 'fooCol.bar.bazCol.qux': 1});
+      });
+
       it('includes "_id" field to the projection', async function () {
         const schema = createSchema();
         schema.defineModel({name: 'model', datasource: 'mongodb'});
@@ -197,6 +235,64 @@ describe('MongodbAdapter', function () {
         expect(res).to.be.eql({_id: 1, bar: 1, qux: 1});
       });
 
+      it('converts property names chain to column names chain', async function () {
+        const schema = createSchema();
+        schema.defineModel({
+          name: 'modelA',
+          datasource: 'mongodb',
+          properties: {
+            foo1: {
+              type: DataType.OBJECT,
+              columnName: 'foo1Col',
+              model: 'modelB',
+            },
+            foo2: {
+              type: DataType.OBJECT,
+              columnName: 'foo2Col',
+              model: 'modelB',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelB',
+          properties: {
+            bar1: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+            bar2: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelC',
+          properties: {
+            baz1: {
+              type: DataType.OBJECT,
+              columnName: 'baz1Col',
+            },
+            baz2: {
+              type: DataType.OBJECT,
+              columnName: 'baz2Col',
+            },
+          },
+        });
+        const A = await schema
+          .getService(AdapterRegistry)
+          .getAdapter('mongodb');
+        const res = A._buildProjection('modelA', [
+          'foo1.bar1.baz1.qux1',
+          'foo2.bar2.baz2.qux2',
+        ]);
+        expect(res).to.be.eql({
+          _id: 1,
+          'foo1Col.bar1.baz1Col.qux1': 1,
+          'foo2Col.bar2.baz2Col.qux2': 1,
+        });
+      });
+
       it('includes "_id" field to the projection', async function () {
         const schema = createSchema();
         schema.defineModel({name: 'model', datasource: 'mongodb'});
@@ -327,6 +423,48 @@ describe('MongodbAdapter', function () {
         expect(res2).to.be.eql({bar: -1});
         expect(res3).to.be.eql({bar: 1});
       });
+
+      it('converts property names chain to column names chain', async function () {
+        const schema = createSchema();
+        schema.defineModel({
+          name: 'modelA',
+          datasource: 'mongodb',
+          properties: {
+            foo: {
+              type: DataType.OBJECT,
+              columnName: 'fooCol',
+              model: 'modelB',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelB',
+          properties: {
+            bar: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelC',
+          properties: {
+            baz: {
+              type: DataType.OBJECT,
+              columnName: 'bazCol',
+            },
+          },
+        });
+        const A = await schema
+          .getService(AdapterRegistry)
+          .getAdapter('mongodb');
+        const res1 = A._buildSort('modelA', 'foo.bar.baz.qux');
+        const res2 = A._buildSort('modelA', 'foo.bar.baz.qux DESC');
+        const res3 = A._buildSort('modelA', 'foo.bar.baz.qux ASC');
+        expect(res1).to.be.eql({'fooCol.bar.bazCol.qux': 1});
+        expect(res2).to.be.eql({'fooCol.bar.bazCol.qux': -1});
+        expect(res3).to.be.eql({'fooCol.bar.bazCol.qux': 1});
+      });
     });
 
     describe('multiple fields', function () {
@@ -431,6 +569,79 @@ describe('MongodbAdapter', function () {
         expect(res2).to.be.eql({bar: -1, qux: 1});
         expect(res3).to.be.eql({bar: 1, qux: -1});
       });
+
+      it('converts property names chain to column names chain', async function () {
+        const schema = createSchema();
+        schema.defineModel({
+          name: 'modelA',
+          datasource: 'mongodb',
+          properties: {
+            foo1: {
+              type: DataType.OBJECT,
+              columnName: 'foo1Col',
+              model: 'modelB',
+            },
+            foo2: {
+              type: DataType.OBJECT,
+              columnName: 'foo2Col',
+              model: 'modelB',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelB',
+          properties: {
+            bar1: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+            bar2: {
+              type: DataType.OBJECT,
+              model: 'modelC',
+            },
+          },
+        });
+        schema.defineModel({
+          name: 'modelC',
+          properties: {
+            baz1: {
+              type: DataType.OBJECT,
+              columnName: 'baz1Col',
+            },
+            baz2: {
+              type: DataType.OBJECT,
+              columnName: 'baz2Col',
+            },
+          },
+        });
+        const A = await schema
+          .getService(AdapterRegistry)
+          .getAdapter('mongodb');
+        const res1 = A._buildSort('modelA', [
+          'foo1.bar1.baz1.qux1',
+          'foo2.bar2.baz2.qux2',
+        ]);
+        const res2 = A._buildSort('modelA', [
+          'foo1.bar1.baz1.qux1 DESC',
+          'foo2.bar2.baz2.qux2 DESC',
+        ]);
+        const res3 = A._buildSort('modelA', [
+          'foo1.bar1.baz1.qux1 ASC',
+          'foo2.bar2.baz2.qux2 ASC',
+        ]);
+        expect(res1).to.be.eql({
+          'foo1Col.bar1.baz1Col.qux1': 1,
+          'foo2Col.bar2.baz2Col.qux2': 1,
+        });
+        expect(res2).to.be.eql({
+          'foo1Col.bar1.baz1Col.qux1': -1,
+          'foo2Col.bar2.baz2Col.qux2': -1,
+        });
+        expect(res3).to.be.eql({
+          'foo1Col.bar1.baz1Col.qux1': 1,
+          'foo2Col.bar2.baz2Col.qux2': 1,
+        });
+      });
     });
   });
 
@@ -478,6 +689,42 @@ describe('MongodbAdapter', function () {
       expect(res).to.be.eql({bar: 'a1', qux: null});
     });
 
+    it('converts property names chain to column names chain', async function () {
+      const schema = createSchema();
+      schema.defineModel({
+        name: 'modelA',
+        datasource: 'mongodb',
+        properties: {
+          foo: {
+            type: DataType.OBJECT,
+            columnName: 'fooCol',
+            model: 'modelB',
+          },
+        },
+      });
+      schema.defineModel({
+        name: 'modelB',
+        properties: {
+          bar: {
+            type: DataType.OBJECT,
+            model: 'modelC',
+          },
+        },
+      });
+      schema.defineModel({
+        name: 'modelC',
+        properties: {
+          baz: {
+            type: DataType.OBJECT,
+            columnName: 'bazCol',
+          },
+        },
+      });
+      const A = await schema.getService(AdapterRegistry).getAdapter('mongodb');
+      const res = A._buildQuery('modelA', {'foo.bar.baz.qux': 10});
+      expect(res).to.be.eql({'fooCol.bar.bazCol.qux': 10});
+    });
+
     it('throws an error when using "$" character', async function () {
       const schema = createSchema();
       schema.defineModel({name: 'model', datasource: 'mongodb'});