Giter VIP home page Giter VIP logo

loopback-connector-cloudant's Introduction

loopback-connector-cloudant

IBM Cloudant® is a NoSQL database platform built for the cloud. You can use Cloudant as a fully-managed DBaaS running on public cloud platforms like Bluemix and SoftLayer or via an on-premises version called Cloudant Local.

For more information, see Getting started with Cloudant NoSQL DB

The loopback-connector-cloudant module is the Cloudant connector for the LoopBack framework.

Getting Started

Design

LoopBack tries best to fit its model to a specific database's design, while limited by the nature of database, it's not always possible to support all LoopBack features perfectly, and user should be aware of some key features about Cloudant before they start to design a Cloudant model.

Partial Update

Cloudant does not support the idea of updating a document. All "updates" on a document are destructive replacements.

It implies that if you do want to partially update a document, please make sure unchanged values are included in the update object.

For example:

// original document
{
  "id": ...,
  "_rev": ...,
  "prop1": "1",
  "prop2": "2",
}

// data to be updated
ds.updateOrCreate('User', {
  prop1: 'updated1',
}, function (err, res) {});

// document after update
{
  "id": ...,
  "_rev": ...,
  "prop1": "updated1",
}

Please note how property prop2 was completely dropped upon update.

We have some discussion on update methods, the issue link can be found in Feature Backlog section

Frequent Modification

Cloudant is not designed to change same document frequently and multiple times. It stores status changes by creating different documents and including the same unique id to tell that they are attached to the same item, not updating the same document.

By modeling the data in separate documents that are only written once, we can reduce the chance of concurrent access to the same document by separate processes.

And by properly controlling the conflict, developer can still do a safe modify. For details, refer to Conflict Control

Conflict Control

The basic idea is when modifying a document, user needs to control conflict by handling the revision of a document, currently the connector controls this process, after retriving the latest revision, connector uses it to update/delete doc, and returns 409 conflict error if doc changes during that time slot. In the middle, user could not interfere and provide their own conflict solution.

Model

Map Between Model And Document

Unlike relational db or mongodb, Cloudant doesn't have a concept as 'table' or 'collection', data in a Cloudant database are all stored as documents.

The connector uses a design document to represent a LoopBack model, and common documents to represent model instances.

The following is a comparison among different databases:

Model Model Property Model Instance
Relational DB table column in table row in table
Mongodb collection createIndex if property.index is true document in collection
Cloudant Design documents in database NOT stored in document common documents in database

To create a model, the connector creates a design document with the following config:

type: 'text',
name: 'lb-index-' + modelName,
ddoc: 'lb-index-ddoc-' + modelName,
index: {
  default_field: {
    enabled: false,
  },
  selector: {
    [modelIndex]: modelName
  },
},

By default, modelIndex is 'loopback__model__name', and modelSelector is {[modelIndex]: modelName}. User can customize modelSelector and modelIndex in datasource's json file, for details please check model-specific configuration

To create a model instance, the connector creates a non-design document with value of property 'loopback__model__name' equals to modelName.

For model properties, we plan to create index for property that has config index: true. In the future, it will be the same way as what mongodb connector does.

Model-specific Configuration

You can specify configurations per model for database selection and to map a model to a different document:

common/models/model-name.json

{
  "name": "User",
  "base": "PersistedModel",
  "idInjection": true,
  ...
  "cloudant": {
    "modelIndex": "custom_doc_type_property_name",
    "modelSelector": { "doc_type": "user" },
    "database": "test2"
  },
  ...

Model-specific configuration settings:

Property        Type Description
database String Database name
modelIndex String Specify the model name to document mapping, defaults to loopback__model__name.
modelSelector JSON Use the Cloudant Query selector syntax to associate models to existing data. NOTE: modelSelector and modelIndex are mutually exclusive; see Selector syntax.

_rev Property

In a document, property _rev is the latest doc revision and must be provided when modifying the doc.

Our connector allows the user to retrieve back the _rev property upon all CRUD operations, however does not add it to the model definition.

If you would like to have a _rev property on your model, as an end user, the onus is on you to add the property in the model definition.

Note: All CRUD operations require _rev (except create) and it is up to the user to specify them. The connector does not handle such cases due to possibilities of race condition when two users try to update the same document.

Example CRUD operations with _rev

model.json

{
  ... 
  "properties": {
    "_rev": {
      "type": "string"
    },
    "name": {
      "type": "string"
    }
  },
  ...
}
  • Create
  Model.create([{
    name: 'Foo',
  }, {
    name: 'Bar',
  }], function(err, result) {
    if (err) throw err;
    console.log('Created instance: ' + JSON.stringify(result));
  });

Note: Cloudant does not allow customized _rev value, hence creating an instance with a _rev value will not give the expected result (i.e Cloudant's CREATE operation ignores the _rev value when provided and generates a random unique one). The onus is on the user if they fail to comply to this rule.

Let's say we have an instance in the database:

{
   "id":"2",
   "_rev":"2-abcedf",
   "name":"Bar"
}
  • Find

    • find
      Model.find(function(err, result) {
        if (err) throw err;
        console.log('Found all instances: ' + JSON.stringify(result));
      });
    • findById
      Model.findById('2', function(err, result) {
        if (err) throw err;
        console.log('Found instance with id: ' + JSON.stringify(result));
      });
  • Replace

    • replaceOrCreate
      Model.replaceOrCreate({
        id:'2',
        _rev:'2-abcedf',
        name:'Bar2'
      }, function(err, result) {
        if (err) throw err;
        console.log('Replace an existing instance: ' + JSON.stringify(result));
      });
    • replaceById
      Model.replaceById('2', {
        _rev:'2-abcedf',
        name:'Bar3'
      }, function(err, result) {
        if (err) throw err;
        console.log('Replace an existing instance with id: ' + JSON.stringify(result));
      });
  • Update

    • updateOrCreate
      Model.updateOrCreate({
        id:'2',
        _rev:'2-abcedf',
        name:'Bar4'
      }, function(err, result) {
        if (err) throw err;
        console.log('Update an existing instance: ' + JSON.stringify(result));
      });
    • update/updateAll

      • with _rev property

          Model.updateAll({
            _rev:'2-abcedf',
            name:'Bar4'
          }, {name: 'Bar4-updated', _rev: '2-abcedf'}, function(err, result) {
            if (err) throw err;
            console.log('Update an existing instance: ' + JSON.stringify(result));
          });
      • without _rev property

          Model.updateAll({
            name:'Bar4'
          }, {name: 'Bar4-updated'}, function(err, result) {
            if (err) throw err;
            console.log('Update an existing instance: ' + JSON.stringify(result));
          });

Force Id

In loopback forceId means user can specify the value of the primary key when creating a model instance, instead of using an auto-generated one. Learn more about LoopBack's forceId.

We recommend user to be careful when creating customized model id instead of using the auto-generated one because data belonging to different models can interfere with each other.

Every document stored in Cloudant has an unique id, stored as _id, and the database has built-in index for it. Retrieving data by its _id gives a better performance than querying by other field. Therefore, the connector always assign the _id field as the loopback model's primary key.

If you have two models that allow customized _id as primary key set with "forceId": false in the model definition, it could result in a document update conflict.

For example:

{_id: 'myid', loopback__model__name: 'Foo'}
{_id: 'myid', loopback__model__name: 'Bar'}

Here is an example of a case that would result in a document update conflict using "forceId": false:

model-definitions

{
  "name": "Employee",
  "base": "PersistedModel",
  "idInjection": true,
  "forceId": false,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "_id": {
      "type": "number",
      "id": true,
      "required": true
    },
    "name": {
      "type": "string"
    },
    "age": {
      "type": "number"
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": {}
}
{
  "name": "Car",
  "base": "PersistedModel",
  "idInjection": true,
  "forceId": false,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "_id": {
      "type": "number",
      "id": true,
      "required": true
    },
    "make": {
      "type": "string"
    },
    "model": {
      "type": "string"
    }
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": {}
}

boot-script

'use strict';

var util = require('util');
var _ = require('lodash');

module.exports = function(app) {
  var db = app.datasources.cloudantDs;
  var Employee = app.models.Employee;
  var Car = app.models.Car;

  db.once('connected', function() {
    db.automigrate(function(err) {
      if (err) throw err;
      console.log('\nAutomigrate completed');

      Employee.create([{
        _id: 1,
        name: 'Foo',
      }, {
        _id: 2,
        name: 'Bar',
      }], function(err, result) {
        if (err) throw err;
        console.log('\nCreated employee instance: ' + util.inspect(result));

        Car.create([{
          _id: 1,
          make: 'Toyota',
        }, {
          _id: 2,
          name: 'BMW',
        }], function(err, result) {
          if (err) throw err;
          console.log('\nCreated car instance: ' + util.inspect(result));
        });
      });
    });
  });
};

Running the above script will throw a document update conflict because there exists a model instance with _id value of 1 and 2.

Web server listening at: http://localhost:3000
Browse your REST API at http://localhost:3000/explorer

Automigrate completed
Created employee instance: [ { _id: '1', name: 'Foo' }, { _id: '2', name: 'Bar' } ]

/Users/ssh/workspace/sandbox/loopback-sandbox/apps/cloudant/cloudant-forceId-app/server/boot/script.js:33
          if (err) throw err;
                   ^
Error: Document update conflict. (duplicate?),Error: Document update conflict. (duplicate?)

In order to avoid this pitfall, please set "forceId": true on either of the model definition which would allow one of the models to have an auto-generated id or do not set "forceId": false on either of the model definitions.

Setup Cloudant Instance

For user that don't have a cloudant server to develop or test, here are some suggestions can help you quickly setup one.

For development use, a docker container of Cloudant local is easy to setup and there is no request limit per second.

Bluemix Cloudant will be more stable for production.

  • Cloudant local (docker image)

  • Cloudant on Bluemix

    • Limit request per second by default.

    • Choose Bluemix Cloudant if you already have a Bluemix account with a better situation than limited-days' free trial.

    • Setup steps:

      1. Open Bluemix console: https://console.ng.bluemix.net
      2. Login with your account.
      3. Click on "CATALOG" in navigation bar.
      4. Search with keyword "cloudant" and choose the "Cloudant NOSQLDB" under "Data and Analytics".
      5. Click on the green button "create" in the popup page to create your Cloudant database.
      6. Go to "DASHBOARD" where you will see your new Cloudant DB icon under "Services".
      7. Click on the icon, and it will direct you to the database page. Check "Service Credentials" on the left to see your credentials.
      8. Check "Manage" then click on button "LAUNCH" to see your Cloudant dashboard.
  • Cloudant DBaaS account

To view the Cloudant dashboard on both DBaaS and Bluemix, sign in with your Cloudant username and password.

Installation

Enter the following in the top-level directory of your LoopBack application:

$ npm install loopback-connector-cloudant --save

The --save option adds the dependency to the application’s package.json file.

Configuration

Generate Datasource

Use the Data source generator to add the Cloudant data source to your application. The entry in the applications /server/datasources.json will look something like this:

"mydb": {
  "name": "mydb",
  "connector": "cloudant",
  "url": "https://<username>:<password>@<host>"
  "database": "test"
}

or

"mydb": {
  "name": "mydb",
  "connector": "cloudant",
  "username": "XXXX-bluemix",
  "password": "YYYYYYYYYYYY",
  "database": "test"
}

Datasource Config

url vs username & password

  • NOTE: The url property will override username and password.

  • It means only when missing url, cloudant will use username and password to create connection.

  • If url is wrong, even user provide correct username and password, the connection still fails.

  • Only specify the username and password fields if you are using the root/admin user for the Cloudant server which has the same string as the hostname for the Cloudant server, because the Cloudant driver used by the connector appends .cloudant.com to the username field when username and password fields are specified. Therefore, it is good practice to use the url field instead of username and password if your host is not username.cloudant.com.

  • Edit datasources.json to add other supported properties as required:

Property Type Description
database String Database name
username String Cloudant username, use either 'url' or username/password
password String Cloudant password
url String Cloudant URL containing both username and password
modelIndex String Specify the model name to document mapping, defaults to loopback__model__name

iamApiKey

We honor the plugins’ configuration and passes them to the driver. Cloudant plugins has section 'iamauth' to show how the IAM authentication is handled by the driver.

To connect to the Cloudant database using IAM authentication, you can configure your LoopBack datasource as:

"mydb": {
 "name": "mydb",
 "connector": "cloudant",
 // make sure you provide the password here
 "url": "https://<username>:<password>@<host>"
 "database": "test",
 "plugins": {
   "iamauth": { "iamApiKey": "xxxxxxxxxx"}
 }
}

Advanced configuration

Besides the basic configuration properties, user can provide advanced configuration information, for example proxy, in requestDefaults:

"mydb": {
  "name": "mydb",
  "connector": "cloudant",
  "url": "https://<username>:<password>@<host>"
  "database": "test",
  "requestDefaults": {"proxy": "http://localhost:8080"}
}

For details, refer to the driver(nodejs-cloudant) document

Requests Plugin

User can provide plugin name and parameters in datasource object. For example, connect to a Cloudant server with plugin called retry, with parameters retryAttempts and retryTimeout:

"mydb": {
  "name": "mydb",
  "connector": "cloudant",
  "url": "https://<username>:<password>@<host>"
  "database": "test",
  "plugin": "retry",
  "retryAttempts": 5,
  "retryTimeout": 1000
}

Please note user can only use one of the plugins list in Cloudant driver's doc, not multiple: https://github.com/cloudant/nodejs-cloudant#request-plugins

Example Usage

// Inside file /server/script.js
var util = require('util');

// Here we create datasource dynamically.
// If you already define a static datasource in server/datasources.json,
// please check how to load it in 
// https://github.com/cloudant/nodejs-cloudant#example-code
var DataSource = require ('loopback-datasource-juggler').DataSource,
    Cloudant   = require ('loopback-connector-cloudant');

var config = {
    username: 'your_cloudant_username',
    password: 'your_cloudant_password',
    database: 'your_cloudant_database'
};

var db = new DataSource (Cloudant, config);

Test = db.define ('Test', {
  name: { type: String },
});

// wait for connected event on the
// datasource before doing any database
// operations since we connect asynchronously
db.once('connected', function() {
  Test.create({
    name: "Tony",
  }).then(function(test) {
    console.log('create instance ' + util.inspect(test, 4));
    return Test.find({ where: { name: "Tony" }});
  }).then(function(test) {
    console.log('find instance: ' + util.inspect(test, 4));
    return Test.destroyAll();
  }).then(function(test) {
    console.log('destroy instance!');
  }).catch(err);
});

CRUD

User can find most CRUD operation apis documented in https://loopback.io/doc/en/lb3/Built-in-models-REST-API.html

We are still in progress of refactoring some methods, more details to be updated.

Update

Currently update does the same thing as replace, for details, refer to https://github.com/loopbackio/loopback-connector-cloudant#no-partial-update

Migration

After attaching a model to a Cloudant datasource, either statically with model.json file or dynamically in boot script code, user need to run automigrate or autoupdate to migrate models to database. Cloudant connector does NOT automatically migrate them.

The following migration functions take either an array of multiple model's name, or a string of a single model's name. The example code will show how to do it.

autoupdate vs automigrate

autoupdate does not destroy existing model instances if model already defined in database. It only creates design document for new models. Under the hood Cloudant allows creating same design doc multiple times, it doesn't return error, but returns existed as result to tell is it a new design doc or existing one.

automigrate destroys existing model instances if model already defined in database. Please make sure you do want to clean up data before running automigrate. Then it does same thing as autoupdate

isActual

User can call this function to check if model exists in database.

Example Code

/server/script.js

module.export = function migrateData(app) {
  // Suppose you already define a datasource called `cloudantDS` 
  // in server/datasources.json
  var ds = app.datasources.cloudantDS;
  
  // static model created with model.json file
  var StaticModel = app.models.StaticModel;
  // dynamic model created in boot script
  var DynamicModel = ds.define('DynamicModel', {
      name: {type: String},
      description: {type: String},
  });

  // Write the three examples in parallel just to avoid dup code,
  // please try ONLY ONE of them at one time.
  ds.once('connected', function() {
    // try autoupdate example - multiple models
    ds.autoupdate(['StaticModel', 'DynamicModel'], function(err) {});
    // OR
    // try automigrate example - single model
    ds.automigrate('StaticModel', function(err) {});
    // OR
    // try isActual example - if any model exist, run autoupdate, otherwise automigrate
    ds.isActual(['StaticModel', 'DynamicModel'], function(err, exist) {
      if (exist) {
        ds.autoupdate(['StaticModel', 'DynamicModel'], function(err){})
      } else {
        ds.automigate(['StaticModel', 'DynamicModel'], function(err){});
      }
    });
  });
}

Discovery

Not implemented yet, track it in story #118

Query

  • Uses Cloudant Query (Lucene) to support ad-hoc searching.
  • LoopBack query support for: fields, limit, order, skip and where filters.
  • Please check Advanced Queries for details about regex filter, nested filter and order.

View

Given a design doc name and the view name in it, user can use a connector level function viewDocs to query the view.

Since viewDocs is a specific api for Cloudant connector only, it is not attached to the dataSource Object defined in loopback-datasource-juggler, which means the correct way to call it is ds.connector.viewDocs:

/server/script.js

module.exports = function(server) {
  // Get Cloudant dataSource as `ds`
  // 'cloudantDB' is the name of Cloudant datasource created in 
  // 'server/datasources.json' file
  var ds = server.datasources.cloudantDB;

  ds.once('connected', function() {
    // 1. Please note `ds.connector.viewDocs()` is the correct way to call it,
    // NOT `ds.viewDocs()`
    // 2. This api matches the Cloudant endpoint:
    // GET /db/_design/<design-doc>/_view/<view-name>
    ds.connector.viewDocs('design_doc', 'view_name', function(err, results) {
      // `results` would be the data returned by querying that view
    });

    // Alternatively user can also specify the filter for view query
    ds.connector.viewDocs('design_doc', 'view_name', {key: 'filter'}, 
      function(err, results) {});
  });
};

Geospatial

Given a design doc name and the filter name in it, user can use a connector level function geoDocs to query a geospatial index.

Since geoDocs is a specific api for Cloudant connector only, it is not attached to the dataSource Object defined in loopback-datasource-juggler, which means the correct way to call it is ds.connector.geoDocs:

/server/script.js

module.exports = function(server) {
  // Get Cloudant dataSource as `ds`
  // 'cloudantDB' is the name of Cloudant datasource created in 
  // 'server/datasources.json' file
  var ds = server.datasources.cloudantDB;

  ds.once('connected', function() {
    // 1. Please note `ds.connector.geoDocs()` is the correct way to call it,
    // NOT `ds.geoDocs()`
    // 2. This api matches the Cloudant endpoint:
    // GET /db/_design/<design-doc>/_geo/<index-name>
    ds.connector.geoDocs('design_doc', 'index_name', function(err, results) {
      // `results` would be the data returned by querying that geospatial index
    });

    // Alternatively user can also specify the filter for geospatial query
    ds.connector.geoDocs('design_doc', 'index_name', {key: 'filter'}, 
      function(err, results) {});
  });
};

Bulk replace

Given an array of data to be updated, Cloudant supports the idea of performing bulk replace on a model instance. Please note, unlike other CRUD operations, bulk replace does not invoke any operation hooks.

Note: To perform bulk replace, each data in the array data set needs to have the id and _rev property corresponding to the documents id and _rev property in the database.

Example:

server/boot/script.js

var dataToCreate = [
  {id: 1, name: 'Foo', age: 1},
  {id: 2, name: 'Bar', age: 1},
  {id: 3, name: 'Baz', age: 2},
  {id: 4, name: 'A', age: 4},
  {id: 5, name: 'B', age: 5},
  {id: 6, name: 'C', age: 6},
  {id: 7, name: 'D', age: 7},
  {id: 8, name: 'E', age: 8},
 ];
var dataToUpdate = [
 {id: 1, name: 'Foo-change', age: 11},
 {id: 5, name: 'B-change', age: 51},
 {id: 8, name: 'E-change', age: 91}
];

module.exports = function(app) {
  var db = app.dataSources.cloudantDS;
  var Employee = app.models.Employee;

  db.once('connected', function() {
    db.automigrate(function(err) {
      if (err) throw err;

      Employee.create(dataToCreate, function(err, result) {
        if (err) throw err;
        console.log('\nCreated instance: ' + JSON.stringify(result));

        dataToUpdate[0].id = result[0].id;
        dataToUpdate[0]._rev = result[0]._rev;
        dataToUpdate[1].id = result[4].id;
        dataToUpdate[1]._rev = result[4]._rev;
        dataToUpdate[2].id = result[7].id;
        dataToUpdate[2]._rev = result[7]._rev;

        // note: it is called `db.connector.bulkReplace`
        // rather than `Employee.bulkReplace`
        db.connector.bulkReplace('Employee', dataToUpdate, function(err, result) {
          if (err) throw err;

          console.log('\nBulk replace performed: ' + JSON.stringify(result));

          Employee.find(function(err, result) {
            if (err) throw err;

            console.log('\nFound all instances: ' + JSON.stringify(result));
          });
        });
      });
    });
  });
};

Partitioned database

If you're using partitioned database, see details on how to configure your model and make use of the partitioned search in https://github.com/loopbackio/loopback-connector-cloudant/blob/master/doc/partitioned-db.md

Testing

  • Cloudant local(docker image)

  • Cloudant DBaaS account

    • username: your sign up username
    • password: your sign up password
    • database: create your own database for testing
  • Cloudant on Bluemix

    • username: see services credentials
    • password: see services credentials
    • database: create your own database for testing

To run the tests:

CLOUDANT_USERNAME=username CLOUDANT_PASSWORD=password CLOUDANT_HOST=localhost CLOUDANT_HOST=5984 CLOUDANT_DATABASE=database npm run mocha

Docker

If you do not have a local Cloudant instance, you can also run the test suite with very minimal requirements. We use couchDB docker image to run the test locally.

NOTICE: we use couchDB3 docker image for testing Cloudant because Cloudant doesn't have a maintained image, and most of the their functionalities are the same (except couchDB doesn't support geosearch).

  • First have Docker installed.

  • To simply run the tests, the following script would spawn a CouchDB3 instance and run tests automatically on your end:

npm t
  • If you'd like to create a container locally with custom configurations, run the following script instead:
source setup.sh <HOST> <USER> <PASSWORD> <PORT> <DATABASE>

Where <HOST>, <PORT>, <USER>, <PASSWORD> and <DATABASE> are optional parameters. The default values are localhost, 5984, admin, pass and testdb respectively. The <USER> and <PASSWORD> you set above will be the admin/password of this couchDB3 container.

The script cloudant-config.sh is generated by the above script. It has all needed environment variables for the tests.

  • Then run the following command to test out your code:
source cloudant-config.sh && npm run mocha

More Info

For more detailed information regarding connector-specific functions and behaviour, see the docs section.

Feature backlog

Index

To be updated

loopback-connector-cloudant's People

Contributors

0candy avatar achrinza avatar agnes512 avatar b-admike avatar bajtos avatar candytangnb avatar crandmck avatar danwakeem avatar dhmlau avatar duffn avatar emonddr avatar gunjpan avatar hibaymj avatar htammen avatar jannyhou avatar jftanner avatar kjdelisle avatar nabdelgadir avatar pkoretic avatar rmg avatar siddhipai avatar simonhoibm avatar smartmouse avatar ssh24 avatar superkhau avatar virkt25 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

loopback-connector-cloudant's Issues

Order and Filter are not working in updateAll where clause

I have documents with _id,Status and dateUploaded fields. I want to update the document where Status is Available and has minimum date.

I am able to successfully run the below code, but it updates all the documents where the Status is 'Available'

orm.TEST.updateAll({Status: 'Available'}, {Status: 'Blocked'}, function(err, result) {
console.log(err + " "+result);
});

The logic I am trying to execute is

orm.TEST.updateAll( {Status: ‘Available’, dateUploaded: logic for min date}, {Status: ‘Blocked’}, function(err, results) {
return callback(err, results);
});

How to get the minimum date in the where clause ?

Release 1.1.0 seems to have broken the upsert

Hi,

When using the latest release 1.1.0, seems like attempting an upsert of an instance that is not yet in the database gives a 404.
Reverting to 1.0.11 fix the issue.

Could you please take a look?
Thanks

Fix update/updateOrCreate in cloudant.js

Use updateAttributes function to update a model instance and use update function in call for updateOrCreate.

Currently, update performs a destructive update. With this change update function should be partial update.

$regex requires an equality operator as the basis of the query

From cloudant document page: https://docs.cloudant.com/cloudant_query.html#sort-syntax

You cannot use combination or array logical operators such as $regex as the basis of a query when using indexes of type json. Only equality operators such as $eq, $gt, $gte, $lt, and $lte (but not $ne) can be used as the basis of a query for json indexes.

{
  "selector": {
    "afieldname": {
      "$regex": "^A"
    }
  }
}

would result in error error: "no_usable_index", reason: "There is no operator in this selector can used with an index."

A solution is to use an equality operator as the basis of the query. You can add a ‘null’ or always true expression as the basis of the query. For example, you could first test that the document has an _id value:
"_id": { "$gt": null }
This expression is always true, enabling the remainder of the selector expression to be applied.

So build selector like:

{
  "selector": {
    "_id": { 
      "$gt": null 
    },
    "afieldname": {
      "$regex": "^A"
    }
  }
}

Filtering on numeric values fails

Model configuration (simplified)

{
  "name": "Record",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "sharedKey": {
      "type": "string",
      "description": "Unique ID",
      "required": true
    }
    ...
  },
  ...
}

Retrieving all records

GET https://myhost/api/Records

[
  {
    "sharedKey":"12345",
    "id":"0266cb32b2afd1d6ea7760ab54981f49",
    "_rev":"2-4cb556a75312172c7730ecbdafb1cd1e"
  },
  {
    "sharedKey":"aString",
    "id":"33c6ca3d10aff422f697322bd1cfddab",
    "_rev":"2-ff30b9a96e4aba1ae36cca785baad2e5"
  }
]

Filtering on the value aString returns the expected document

GET https://myhost/api/Records?filter[where][sharedKey]=aString

[
  {
    "sharedKey":"aString",
    "id":"33c6ca3d10aff422f697322bd1cfddab",
    "_rev":"2-ff30b9a96e4aba1ae36cca785baad2e5"
  }
]

Filtering on the value 123456 returns nothing

GET https://myhost/api/Records?filter[where][sharedKey]=123456

[]

automigration bug -- migration not done before boot script runs

Reproduce:

  • MAKE SURE cloudant database is empty
  • slc loopback, use notes template, so it has auth system and User model
  • Attach User and auth related models to a cloudant datasource
  • In boot script, create a new user instance
  • Get error complains user is not valid
    note: this won't happen if you already have the model's document in database.

Investigation:
setTimeout in boot script like the following code, give cloudant enough time to migrate model before creating user instance, problem solved.

'use strict';

module.exports = function(server) {
  var User = server.models.User;

  var doCreate = function() {
    User.create({username: 'John', email: '[email protected]', password: 'opensesame'}, function(err, users) {
      if (err) console.log(err);
      console.log(users);
    });
  };
  setTimeout(doCreate, 5000);
};

Reason
When create a new user, in validation function(juggler/lib/validation.js), it checks uniqueness first, therefore calls find(), which returns 500 error due to model migration not done

include filter broken

looopback-connector-cloudant v1.0.6

When running any queries with a include filter the relation data returned is empty. While in debug mode I was able to get back the following error:

error": {

"name": "Error",
"status": ​500,
"message": "function_clause",
"error": "function_clause",
"reason": null,
"ref": ​1612317691,
"scope": "couch",
"statusCode": ​500,
"request": 

{

"method": "post",
"headers": 

{
    "content-type": "application/json",
    "accept": "application/json"
},
"uri": "https://XXXXXX:[email protected]/jenkins/_find",
"body": "{\"selector\":{\"loopback__model__name\":\"LicenseFile\",\"packageId\":{\"$in\":[]}},\"use_index\":[\"lb-index-ddoc-LicenseFile\",\"lb-index-LicenseFile\"],\"sort\":[{\"id:string\":\"asc\"}]}"

}

My Package model hasMany LicenseFiles.

udpateAll( ) fails if input data object is a LoopBack model instance

Description of feature (or steps to reproduce if bug)

When updateAll() is invoked with loopback model instance as payload, it fails with complaining: Bad special document member: __cachedRelations and similar.
Other NoSQL connector i.e. mongo connector, AFAIK, supports model instance as payload.

How to find all instances of a model which have their "belongsTo" relation unset

Hi,

Not sure this is really an issue, maybe I just don't know the right way of doing things...
Let's take the following example with two models Person and Vehicle:

{
  "name": "Person",
  "base": "PersistedModel",
  "strict": false,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "name": {
      "type": "name",
      "required": true
    },
  },
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": {}
}

{
  "name": "Vehicle",
  "base": "PersistedModel",
  "strict": false,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "name": {
      "type": "string",
      "required": true
    },
  },
  "validations": [],
  "relations": {
    "owner": {
      "type": "belongsTo",
      "model": "Person"
    },
  },
  "acls": [],
  "methods": {}
}

Is there any simple query to perform in order to fetch all vehicles that don't have an owner?

Would it be possible to do anything like one of the lines below?
Vehicle.find({where: {personId: null}})
Vehicle.find({where: {personId: undefined}})
Vehicle.find({where: {owner: {exist:false}}})

Append fieldType when sort with nested property

Related to strongloop/loopback#517 (comment)

cloudant sort needs specified fieldType, either :string or :number, details please check https://docs.cloudant.com/cloudant_query.html#sort-syntax

Now we already append the fieldType for first level properties but not nested ones. Therefore when sorting with nested property(e.g. test in juggler), cloudant complains

Unspecified or ambiguous sort type. Try appending :number or :string to the sort field. address.city

APIC Explorer does not generate data properly for model with required date

A customer wrote into support with the following issue:

Using APIC, I create a loopback relation from two models, Item and Review. Item has three fields (id:string, name:string and desc:string). Review has three fields (reviewer: string, reviewDate: date and review:string). All three fields are required. For my relationship, Item has many Reviews. I should also mention that I am using a Cloudant datasource for this scenario. I then go to "Explore" and I use Post to create an Item. So far everything is fine. Then, I go to create a "Review" for my Item, here is where things get a little sticky. First I enter a PersistedModel id for the Item that I want to Post a review for. Then I click the Generate link to generate the data for the review. When I do this however, under data the only thing that gets generated is "" (versus the JSON object that I expect). If I change my Review model where the reviewDate field is not required, then I see a JSON object displayed, although the behavior still appears to be flaky at times. I believe there is a problem with the Explore generate data option.

Cannot create data source "cloudant"

Hey, I'm trying to connect my Loopback application to Cloudant but I am not having much luck. I used the slc loopback:datasource command to add a Cloudant datasource, however, when I start my application I get:

chriss-mbp:project chrisyoung$ npm start

> [email protected] start project
> node server/server.js

project/node_modules/loopback/lib/application.js:238
    throw err;
    ^

TypeError: Cannot create data source "cloudant": Cannot initialize connector "cloudant": Cannot set property 'use' of undefined
    at Cloudant (project/node_modules/loopback-connector-cloudant/node_modules/cloudant/cloudant.js:128:26)
    at new Cloudant (project/node_modules/loopback-connector-cloudant/lib/cloudant.js:52:19)
    at Object.exports.initialize (project/node_modules/loopback-connector-cloudant/lib/cloudant.js:21:18)
    at DataSource.setup (project/node_modules/loopback-datasource-juggler/lib/datasource.js:338:19)
    at new DataSource (project/node_modules/loopback-datasource-juggler/lib/datasource.js:114:8)
    at Registry.createDataSource (project/node_modules/loopback/lib/registry.js:349:12)
    at dataSourcesFromConfig (project/node_modules/loopback/lib/application.js:430:19)
    at EventEmitter.app.dataSource (project/node_modules/loopback/lib/application.js:228:14)
    at project/node_modules/loopback-boot/lib/executor.js:178:9
    at project/node_modules/loopback-boot/lib/executor.js:269:5

Here are the dependencies in my package.json:

  "dependencies": {
    "cfenv": "^1.0.3",
    "compression": "^1.6.2",
    "cors": "^2.7.1",
    "loopback": "^2.28.0",
    "loopback-boot": "^2.18.1",
    "loopback-component-explorer": "^2.5.0",
    "loopback-connector-cloudant": "^1.0.11",
    "loopback-datasource-juggler": "^2.46.0",
    "morgan": "^1.7.0",
    "newrelic": "^1.28.0",
    "serve-favicon": "^2.3.0",
    "winston": "^2.2.0",
    "winston-loggly": "^1.3.1"
  }

And my datasources.json:

{
  "db": {
    "name": "db",
    "connector": "memory"
  },
  "cloudant": {
    "url": "https://xyz.cloudant.com/apih-tk",
    "database": "project",
    "username": "username",
    "password": "******",
    "name": "cloudant",
    "connector": "cloudant"
  }
}

Any suggestions on what the issue might be or if I might have an incorrect version of packages?

Rate limits from new IBM Bluemix Cloudant plans are not managed by the connector

Cloudant in Bluemix has plans with rate limits. It used to have a "Shared" plan with a limit of storage but the new plans have introduced read and write per second limits:
https://www.ibm.com/blogs/bluemix/2016/09/new-cloudant-lite-standard-plans-are-live-in-bluemix-public/

The Cloudant nodejs package has been updated to cope with the new limits and automatically retry. This is implemented with a Cloudant "retry" plugin:
https://github.com/cloudant/nodejs-cloudant#request-plugins

The loopback-connnector-cloudant should be updated to use the latest Cloudant nodejs package and support the configuration of the retry plugin.

Typically I'm getting http error 429 (the Cloudant error code when you reach the rate limit) and messages like:

Error: You've exceeded your current limit of 5 requests per second for query class. Please try later.
    at Request._callback (/Users/fred/dev/api/node_modules/nano/lib/nano.js:248:15)
    at Request.self.callback (/Users/fred/dev/api/node_modules/request/request.js:187:22)
    at emitTwo (events.js:106:13)
    at Request.emit (events.js:191:7)
    at Request.<anonymous> (/Users/fred/dev/api/node_modules/request/request.js:1044:10)
    at emitOne (events.js:96:13)
    at Request.emit (events.js:188:7)
    at IncomingMessage.<anonymous> (/Users/fred/dev/api/node_modules/request/request.js:965:12)
    at emitNone (events.js:91:20)

Cannot get (id)

I define my own id which is string. When I want to get using the get (id) the result is error :
{
"error": {
"name": "Error",
"status": 404,
"message": "could not find a model with id 1",
"statusCode": 404,
"code": "MODEL_NOT_FOUND",
}
}

Same can be happen if I use put(id). But when I only use get, it is works normally.
But when I store the id as full string, i get the result

replaceById in nodejs returns error

When using a model and using the method replaceById, it returns the following errror:

Error: The connector cloudant does not support replaceById operation. This is not a bug in LoopBack. Please contact the authors of the connector, preferably via GitHub issues.
at Function.DataAccessObject.replaceById (C:\secret_path_to_my_nodejsapplication\node_modules\loopback-datasource-juggler\lib\dao.js:2818:15)
at C:\secret_path_to_my_nodejsapplication\app.js:255:8
at Layer.handle as handle_request
at next (C:\secret_path_to_my_nodejsapplication\node_modules\express\lib\router\route.js:131:13)
at Route.dispatch (C:\secret_path_to_my_nodejsapplication\node_modules\express\lib\router\route.js:112:3)
at Layer.handle as handle_request
at C:\secret_path_to_my_nodejsapplication\node_modules\express\lib\router\index.js:277:22
at Function.process_params (C:\secret_path_to_my_nodejsapplication\node_modules\express\lib\router\index.js:330:12)
at next (C:\secret_path_to_my_nodejsapplication\node_modules\express\lib\router\index.js:271:10)
at C:\secret_path_to_my_nodejs_application\app.js:64:3"

The error declares that i have to contact the authors via github issues, so here i am!

Support API Keys

Bug or feature request

  • Bug
  • Feature request

Description of feature (or steps to reproduce if bug)

Cloudant supports the generation of API keys with their own credentials, which ensures that you're not using administrative credentials to perform operations against the DB.

The nodejs-cloudant driver allows use of keys, and this connector should do the same.

Regex filter not supported?

Trying to query cloudant (findOne) through the connector using regex, but the $regex operator value always gets passed as blank. For instance

MyModel.findOne({where: {myField: {regexp: '10'}}}, function(err, resp) {
        console.error('OUTPUT ', err, resp);
});

This gives the below error in console:

{ [Error: Invalid operator: $regex]
name: 'Error',
error: 'invalid_operator',
reason: 'Invalid operator: $regex',
scope: 'couch',
statusCode: 400,

...

body: '{"selector":{"loopback__model__name":"MyModel","myField":{"$regex":{}}},"use_index":["xy-index-ddoc-MyModel","xy-index-MyModel"],"limit":1,"sort":[{"id:string":"asc"}]}' },
...
...

Configuration options are not appropriately passed to Cloudant Driver

connected to #56

The options object we expect at connector construction is not being handled in a way that allows
configuration options to be passed through to the Cloudant driver (see
https://github.com/strongloop/loopback-connector-cloudant/blob/master/lib/cloudant.js#L56).

We should only minimally inspect the options object for the correct parameters and format (to ensure that they've provided credentials), but otherwise should not prevent this pass-through.

updateAll doesn't replace the property with array type.

Reproduce:

  1. Create a model Test

  2. Create an instance for Test:

    testInstance: {
      id: 1,
      name: 'test1'
      skills: [
        {id: 0, text: "JAVA"},
        {id: 1, text: "CSS"},
        {id: 2, text: "HTML" },
        {id: 3, text: "JAVASCRIPT"},
        {id: 4, text: ".NET"}
      ]
    }
  3. Try updateAll:

    Test.updateAll({id: test.id}, {skills: [
    { id: 1, text: "newJAVA"},
    { id: 2, text: "newCSS"}
    ]}, function(err, count){} );
  4. The updated skills are expected to be:

     skills: [
       { id: 1, text: "newJAVA"},
       { id: 2, text: "newCSS"}
     ]

    But actual result is:

    skills: [
      {id: 0, text: "newJAVA"},
      {id: 1, text: "newCSS"},
      {id: 2, text: "HTML" },
      {id: 3, text: "JAVASCRIPT"},
      {id: 4, text: ".NET"}
    ]

Details see: http://stackoverflow.com/questions/40111147/updateall-loopback-datasource-does-not-delete-deletions-inside-updated-data

Keep ._rev property on returned instance of model

A further discussion of #54 (comment)
Regarding

When I step back a bit and look at the problem from a high level perspective, then I think it should be acceptable to require callers of patch/replace operations to provide _rev in the data, as that's the way how CouchDB/Cloudant allows clients to detect and resolve conflicts

Our current implementation:
Now whenever rev is needed, eg: in function Cloudant.prototype.delete(), Cloudant.prototype.replaceById(), we call getCurrentRevision().
But considering the nature of Cloudant db, we should ask the caller to provide rev, and return error if it's missing.

Affected functions:

  • updateOrCreate
    • PATCH Models/
    • Provide rev when updating an existed instance with data: {rev: rev_of_new_data}
  • replaceOrCreate
    • PUT Models/
    • Provide rev when replacing an existed instance with data: {rev: rev_of_new_data}
  • save
    • need to investigate
  • replaceById
    • Provide rev when replacing an existed instance with data: {rev: rev_of_new_data}
  • delete
    • DELETE Models/
    • We disabled this endpoint and DELETE Models/{id} doesn't accept data...any idea? Probably keep our current design.

replaceById operation not supported by cloudant connector

Bug or feature request

  • Bug
  • Feature request

Description of feature (or steps to reproduce if bug)

Currently when attempting to do a createOrReplace or any other replace command through my API, I am receiving a 500 error code:

"error": {
"statusCode": 500,
"name": "Error",
"message": "The connector cloudant does not support replaceById operation. This is not a bug in LoopBack. Please contact the authors of the connector, preferably via GitHub issues.",
"stack": "Error: The connector cloudant does not support replaceById operation. This is not a bug in LoopBack. Please contact the authors of the connector, preferably via GitHub issues. ...

I am posting this here wondering if there is a solution to this or if there is a work around available.

Additional information (Node.js version, LoopBack version, etc)

Node v6.9.4
Loopback v3.10.10

automigrate bug -- document conflict error

BUG

  • with empty db, or db doesn't have the model's document
  • use db.define() to create model
  • run db.automigrate()
  • it complains conflict error, since the automigrate function runs twice, once when connection established, once when the function get called. The conflict might raised by race condition(two model create request runs together)

Could not found minus value.

Hi.

I tried found at cloudant data, but could not found minus value data.

success: lat = 90
failed: lat = -90

How to find the minus value data ?

source: --databasename--.js

.
--databasename--.find({"where":{"and":[{"lat":-90}]}},function(err, result) {
            if (err) {
                console.log("--- error ---";
            } else {
                console.log("---  lng: ",  result.lng);
            }
}
.

model.properties: --databasename--.json

.
  "properties": {
    "_id": {
      "type": "string",
      "id": 1
    },
    "_rev": {
      "type": "string"
    },
    "timestamp": {
      "type": "date"
    },
    "lat": {
      "type": "number"
    }
}
.

cloudant data:

{
  "_id": "{any_id}",
  "_rev": "{any_rev}",
  "timestamp": "2016-05-12T06:00:00Z",
  "lat": -90
  "lng": 130
}
,
{
  "_id": "{any_id}",
  "_rev": "{any_rev}",
  "timestamp": "2016-05-12T06:00:00Z",
  "lat": 90
  "lng": 135
}

I tried in cloudant query box , but also could not found.

query:

{
  "selector": {
    "loopback__model__name": "--databacename--",
    "lat": -90
  },
  "fields": ["_id", "_rev",  "lat", "lng" ]
}

too many boolean clauses error on large include query

The fix in #3 allowed queries to return results > 200 from the connector. However, now I am getting a "too many boolean clauses" error when returning my results.

Context: I am running two queries. The first finds the total count of a particular package model (2k+ results). This returns fine without any problems. My second query has a include filter to tie in another relation to the package instances and this is where I get the error. It looks like the problem is with the query containing a $in field of the array of package ids which is over the boolean clauses limit.

Further looking at the code in lib/cloudant.js it looks like the include query is ran on the total docs returned. Would running the include query on each 200 page bookmark solve this issue?

@tonyffrench your thoughts? Also thanks for all the help in the previous issue!

datasource.json generated from cloudant connector comes with in memory connector

steps to reproduce:
1.npm install -g strongloop-connector-cloudant
2.mkdir testLoopbackconnectorcloudant
3.cd testLoopbackconnectorcloudant
4.slc loopback
5.slc loopback:datasource

strongbot@plnx133:~/testLoopbackconnectorcloudant> slc loopback:datasource
? Enter the data-source name: cloudantdb
? Select the connector for cloudantdb: IBM Cloudant DB (supported by StrongLoop)

Then create model
slc loopback:model

strongbot@plnx133:~/testLoopbackconnectorcloudant> slc loopback:model
? Enter the model name: cloudantmodel
? Select the data-source to attach cloudantmodel to: cloudantdb (cloudant)
? Select model's base class Model
? Expose cloudantmodel via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? server

Afterwards,
in the datasources.json

you will see the following:
I am not sure why the db with memory connector is there. I never selected it and only
selected cloudant connector

"db":
"name": "db",
"connector": "memory"
},
"cloudant": {
"name": "cloudant",
"connector": "cloudant"
}

Add the ability to have the cloudant use a proxy agent

Feature

Description of feature (or steps to reproduce if bug)

Add the ability to have the cloudant use a proxy agent. Originally raised by pr #53

Link to sample repo to reproduce issue (if bug)

Expected result

Additional information (Node.js version, LoopBack version, etc)

Support second level property(relation property) filter in cloudant

Let's take an example:

{
  "name": "Person",
  "base": "PersistedModel",
  "strict": false,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "name": {
      "type": "name",
      "required": true
    },
  },
  "validations": [],
  "relations": {
    "vehicle": {
      "type": "hasOne",
      "model": "Vehicle"
    }
  }
  "acls": [],
  "methods": {}
}

{
  "name": "Vehicle",
  "base": "PersistedModel",
  "strict": false,
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "brand": {
      "type": "string",
      "required": true
    },
    "name": {
      "type": "string",
      "required": true
    },
  },
  "validations": [],
  "relations": {
    "owner": {
      "type": "belongsTo",
      "model": "Person"
    }
  },
  "acls": [],
  "methods": {}
}

Is there any way to fetch with a simple query/filter, all the persons who have a vehicle of brand XXX ?

AssertionError: url is not valid

When the data-source configuration does not provide all required settings, the connector initialisation fails with AssertionError: url is not valid. This error message is misleading - it hints that the url setting is required, which is not true.

Please improve the code building settings for cloundant driver (source code) to verify that the configuration is valid and report a helpful error message otherwise.

/cc @ritch @raymondfeng

updateAll is not working properly

When given an array as input for a field, we are getting TypeError k.match is not a function.

We cannot give primary id as where clause in input parameter.

Both the issues are for updateAll method

TypeError: Cannot read property 'length' of undefined loopback-connector-cloudant/lib/cloudant.js:230

Hi ,

Application is crashing frequently because of the following error. (loopback-connector-cloudant 1.0.11 ).

ERR /home/vcap/app/node_modules/loopback-connector-cloudant/lib/cloudant.js:230
2017-01-16T15:38:39.42+0530 [App/0] ERR for (var i = 0; i < docs.length; i++) {
2017-01-16T15:38:39.42+0530 [App/0] ERR ^
2017-01-16T15:38:39.42+0530 [App/0] ERR TypeError: Cannot read property 'length' of undefined
2017-01-16T15:38:39.42+0530 [App/0] ERR at include (/home/vcap/app/node_modules/loopback-connector-cloudant/lib/cloudant.js:230:31)
2017-01-16T15:38:39.42+0530 [App/0] ERR at Request._callback (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/cloudant-nano/lib/nano.js:216:16)
2017-01-16T15:38:39.42+0530 [App/0] ERR at Request.self.callback (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/request/request.js:186:22)
2017-01-16T15:38:39.42+0530 [App/0] ERR at emitTwo (events.js:87:13)
2017-01-16T15:38:39.42+0530 [App/0] ERR at Request.emit (events.js:172:7)
2017-01-16T15:38:39.42+0530 [App/0] ERR at emitOne (events.js:77:13)
2017-01-16T15:38:39.42+0530 [App/0] ERR at Request.emit (events.js:169:7)
2017-01-16T15:38:39.42+0530 [App/0] ERR at IncomingMessage. (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/request/request.js:980:12)
2017-01-16T15:38:39.42+0530 [App/0] ERR at IncomingMessage.g (events.js:260:16)
2017-01-16T15:38:39.42+0530 [App/0] ERR at emitNone (events.js:72:20)
2017-01-16T15:38:39.42+0530 [App/0] ERR at endReadableNT (_stream_readable.js:923:12)
2017-01-16T15:38:39.42+0530 [App/0] ERR at /home/vcap/app/node_modules/loopback/node_modules/continuation-local-storage/node_modules/async-listener/glue.js:188:31
2017-01-16T15:38:39.42+0530 [App/0] ERR at nextTickCallbackWith2Args (node.js:458:9)
2017-01-16T15:38:39.42+0530 [App/0] ERR at process._tickDomainCallback (node.js:413:17)
2017-01-16T15:38:39.42+0530 [App/0] ERR at process.fallback (/home/vcap/app/node_modules/loopback/node_modules/continuation-local-storage/node_modules/async-listener/index.js:482:15)

Could you please help

Regex query does not obey ignoreCase flag

I'm making a query through the Cloudant connector with a filter that looks like this:

{where: {name: {regexp: '/Foo/i'}}}

I've narrowed down the problem to the following lines: (Starts at 329, for me, in cloudant.js.)

if (cond.constructor.name === 'RegExp') {
    if (cond.global)
        console.warn('Cloudant regex syntax does not support global');
    query[k] = {$regex: cond.source};
} else {
    query[k] = {$regex: cond};
}

At the start of the code, my cond object is as follows:

{
"global": false,
"ignoreCase": true,
"multiline": false,
"source": "Foo"
}

As a RegExp object, it drops into the first if and builds the query with
query[k] = {$regex: cond.source}
and thus ignores the ignoreCase and multiline flags.

I'll submit a pull request shortly with a fix.

Error: Invalid operator: $regex

I get the following error when calling a _find with regex via my loopback application. When using the in-memory database instead of Cloudant everything works fine.

Regards Helmut

_datasource:node_logs:1457390961693, source:node-js-console, _writetime:06/14/16 19:21:41:821 +0000, severity:I 
datetime=2016-06-14T19:21:33.787 +0000,,message=[2016-06-14 19:21:33.786] [ERROR] console - { [Error: Invalid operator: $regex]
  name: 'Error',
  error: 'invalid_operator',
  reason: 'Invalid operator: $regex',
  scope: 'couch',
  statusCode: 400,
  request: 
   { method: 'post',
     headers: 
      { 'content-type': 'application/json',
        accept: 'application/json' },
     uri: 'https://XXXXXX:[email protected]/n-odata-server-example-db/_find">https://XXXXXX:[email protected]/n-odata-server-example-db/_find',
     body: '{"selector":{"loopback__model__name":"BusinessTrip","Location":{"$regex":{}}},"use_index":["lb-index-ddoc-BusinessTrip","lb-index-BusinessTrip"],"limit":20,"sort":[{"Starttime:string":"desc"}]}' },
  headers: 
   { 'x-couch-request-id': '71dff32505',
     date: 'Tue, 14 Jun 2016 19:21:33 GMT',
     'content-type': 'application/json',
     connection: 'close',
     'cache-control': 'must-revalidate',
     'strict-transport-security': 'max-age=31536000',
     'x-content-type-options': 'nosniff;',
     statusCode: 400,
     uri: 'https://XXXXXX:[email protected]/n-odata-server-example-db/_find' },
  errid: 'non_200',
  description: 'couch returned 400' }
Error: Invalid operator: $regex
    at Request._callback (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/nano/lib/nano.js:248:15)
    at Request.self.callback (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/nano/node_modules/request/request.js:200:22)
    at emitTwo (events.js:87:13)
    at Request.emit (events.js:172:7)
    at Request.<anonymous> (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/nano/node_modules/request/request.js:1067:10)
    at emitOne (events.js:82:20)
    at Request.emit (events.js:169:7)
    at IncomingMessage.<anonymous> (/home/vcap/app/node_modules/loopback-connector-cloudant/node_modules/cloudant/node_modules/nano/node_modules/request/request.js:988:12)
    at emitNone (events.js:72:20)
    at IncomingMessage.emit (events.js:166:7)
,source=node-js-console,severity=I

use $elemMatch when query on elements in an array field

$elemMatch: Matches and returns all documents that contain an array field with at least one element that matches all the specified query criteria.

Use case:
seed data

{
      seq: 0,
      name: 'John Lennon',
      friends: [
        {name: 'Paul McCartney'},
        {name: 'George Harrison'},
        {name: 'Ringo Starr'},
      ],
    },

wrong query
{where: {friends.name: 'Ringo Starr'}}
right query
{where: {friends: {$elemMatch: {name: 'Ringo Starr'}}}}

"Cannot read property 'password' of undefined"

Bug or feature request

  • Bug
  • Feature request

Description of feature (or steps to reproduce if bug)

After updating to the latest Angular CLI I have been receiving this error everytime that I attempt to send a post or get request to upload/retrieve a document.

I am not clear what property password it is referring to and why this error is occurring after I updated the angular cli.

Link to sample repo to reproduce issue (if bug)

Expected result

I am hoping to be able to upload and retrieve documents from my cloudant db

Actual result (if bug)

I receive the following error:

"error": {
"name": "TypeError",
"status": 500,
"message": "Cannot read property 'password' of undefined",
"stack": "TypeError: Cannot read property 'password' of undefined\n
}

Additional information (Node.js version, LoopBack version, etc)

Noje.js version = 7.7.3
LoopBack version = 2.38.2

Cloudant - Model property with type number and marked ID expects string as value

PMR link: https://jazzop27.rtp.raleigh.ibm.com:9443/ccm/web/projects/CloudOE#action=com.ibm.team.workitem.viewWorkItem&id=210887

Problem:

Attempting to insert a record into Cloudant with a numeric ID will fail, since the inserted object does not have an id of type String.

Potential Fix

Investigate whether or not calling .toString() on the _id parameter before an insert request is made will fix the issue.

Acceptance Criteria

  • Numeric IDs should be valid for inserting records into Cloudant
  • Test case(s) should validate this behaviour
  • Document this workaround on loopback-connector-cloudant

Find query does not return the cloudant bookmark field

I am using the loopback model find function with the loopback-connector for cloudant. The number of models returned are always <= 200. Upon further investigation I found that this is a limitation imposed by cloudant's query.

https://developer.ibm.com/answers/questions/233525/nodered-cloudant-using-cloudant-node/
https://docs.cloudant.com/cloudant_query.html#finding-documents-using-an-index

The bookmark field is used to for paging through the result sets each of which being a max of 200 rows.

When debugging the Find matching model instances by filter code in lib\cloudant.js it looks like the query results return both a doc and bookmark field. However, the results.doc is the only thing that is callback.

Is there a way to have a filtered query result of greater than 200 or use the bookmark to page through in the current connector implementation?

updateAttributes doesn't replace the property with array type.

#Same problem as updateAll: #44

Reproduce:

A model instance with an array type property

  modelInstance: {
     name: 'test',
     arrProperty: [
       {id: 1, name: original1},
       {id: 2, name: original2},
       {id: 3, name: original3}
     ] 
  }

When do updateAttributes:

modelInstance.updateAttributes({arrProperty: [
  {id: 1, name: new1},
]})

The expected new arrProperty is

arrProperty: [{id: 1, name: new1}]

But actual result is

arrProperty: [
       {id: 1, name: new1},
       {id: 2, name: original2},
       {id: 3, name: original3}
     ] 

corrupted model index? "Error performing text search"

Hi,

Our loopback APIs with DB queries have been working fine until all of a sudden, APIs involve one particular type of model stops working and always returns "Error performing text search".

e.g. APIs using this query:
{"selector":{"loopback__model__name":"package","_id":"bd3728cc668db9d363f0b0d98c448c96"},"use_index":["lb-index-ddoc-package","lb-index-package"],"limit":1,"sort":[{"id:string":"asc"}]}

Returns error:
Error performing text search: {timeout, {gen_server,call, [<13537.1556.3932>, {prompt, [<<"index_doc">>, {[{<<"_id">>, <<"fb6e036200e23926d5a1de801592f068">>}, {<<"_rev">>, <<"1-1e219de3b35ba0c9a55802a897cc384e">>}, {<<"name">>,<<"IIBCloud">>}, {<<"version">>,<<"0.0.0">>}, {<<"path">>,<<"iib.cloud.ui.web.war">>}, {<<"scanId">>, <<"cfce92cc5425d21cb65dce09553b0ce3">>}, {<<"license_types">>, [<<"Apache-2.0">>,<<"BSD-2-Clause">>, ... ...

Seems to me trouble with index ["lb-index-ddoc-package","lb-index-package"].

How to validate and fix this? This is impacting our production env. Please advice.

Connector assumes username and subdomain should be same

Bug or feature request

  • Bug
  • Feature request

Description of feature (or steps to reproduce if bug)

Connector assumes that username and cloudant url subdomain is same. But I do not have to use the admin credentials with full access.
I should be able to create a restricted user for connection through cloudant dashboard and that can also be used by the connector.
Right now this causes an invalid url error because it just copies username as subdomain.

Link to sample repo to reproduce issue (if bug)

Create a permission for a new user with just read and write access and try adding datasource with these credentials.

Expected result

Datasource should be connected to cloudant without any errors.

Actual result (if bug)

Throws invalid url error.

Additional information (Node.js version, LoopBack version, etc)

I tried this with [email protected] and [email protected]

Support to query view

Bug or feature request

  • Bug
  • [x ] Feature request

Description of feature (or steps to reproduce if bug)

Couch connector supports to query the view, will cloudant connector support this feature?

https://www.npmjs.com/package/loopback-connector-couch

Link to sample repo to reproduce issue (if bug)

Expected result

Actual result (if bug)

Additional information (Node.js version, LoopBack version, etc)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.