pouchDB query() bug when updating documents
Asked Answered
D

3

7

Let's say I have these three documents:

{ "_id": "11111", "type": "template", "name": "person" }
{ "_id": "22222", "type": "template", "name": "place" }
{ "_id": "33333", "type": "template", "name": "thing" }

I have a cloud database and then I have a device with pouchDB syncing from that database.

These are the steps that I do:

  1. I sync both databases together. So now I have the most recent versions of this document on my device.
  2. I run the below query and I get back all three templates like so:

Code

var template_obj = {};

return device_db.query('filters/templates')
.then((templates) => {
    for (let t of templates.rows) templates_obj[t.id] = true;
    return templates_obj;
});

filters/templates

function (doc) {
  if(doc.type == "template")
    emit(doc._id);
}

return

{ "11111": true, "22222": true, "33333": true }
  1. I update template: person on cloud. And then I update it again. So 2 revisions have gone by without syncing to my device.

  2. I sync with my device.

  3. Now when I run the same query and I only get back the document I edited. Which is weird because I haven't touched any of the other documents. The same view returns the expected results on the cloud but not on the device.

return

{"11111": true}
  1. If I do the following code however, all templates come back as normal and the same _rev from the cloud show up on the device. Meaning the sync was successful and view is getting confused.

new code

return device_db.allDocs({conflicts: true})
.then((data) => {
    for (let d of data.rows) {
        if(d.doc.type == "template") {
            templates_obj[d.doc._id] = true;
        }
    }
    return templates_obj;
 }).catch((err) => {
    console.log(JSON.stringify(err));
})

I'm starting to believe this is a bug because if I destroy my database and do these steps again, I can reproduce this issue.

Drifter answered 29/5, 2018 at 14:41 Comment(7)
Please add the code you have used for updating the documents?Hypnology
@TarunLalwani there is no code. I update them straight from the Cloudant dashboard.Drifter
Are you setting the _rev on the objectHypnology
@TarunLalwani if you are referring to when I update the document, you have to include the _rev. It's the same way Fauxton works if you were to edit the document. The issue is not in how I am updating the document because the filter works on the dashboard.Drifter
Trying to understand: Your code would actually return a single object, not an array of objects. right? And the keys would be the IDs, not the names. So the correct return value would be {'11111': true, '22222': true, '33333': true}.Provence
And filters/templates seems to be a view called 'templates' in a design doc called 'filters', not an actual filter, right? db.query uses previously saved views: pouchdb.com/api.html#query_database - am I correct with that?Provence
@BernhardGschwantner My apologies you're right, I edited to fix those typos. Yes, the design doc is called filters, another thing I failed to mention nice catch. From my experience, db.query has been using my defined view (views sync like the rest of the documents). Even if I use a newly defined view function (that's the same), I get the same issueDrifter
P
1

After realizing you are using React Native, I think this actually has to do with PouchDB in React Native, and it's indeed a bug. There are several reports of that behavior:

Provence answered 1/6, 2018 at 17:10 Comment(1)
Yup, one of them is actually my report. Seems like they just acknowledged it today. Thanks for looking into it BernhardDrifter
P
0

[edit: Seems to be a bug in PouchDB with React Native. I leave this answer because it might be helpful in other ways.]

I suspect it's some side effect with the global variable template_obj you are using. Try to console.log(templates.rows) directly instead of storing it in a variable in the top scope, or use Array.reduce() to avoid side effects. Then you'd always get the correct view results.

This is step by step code:

return device_db.query('filters/templates')
  .then(templates => templates.rows)            // Take only the rows into account.
  .then(rows => rows.map(row => row.id)         // Extract the id. If you wanted the name instead this would be possible with a slightly different view.
                                                // I think it would suffice to log the result right now, 
                                                // but if you really want to have a single object with boolean values, 
                                                // you can do the following:
  .then(ids => ids.reduce((asObject, id) => {   // Use Array.reduce() here to avoid any potential side effects.
    asObject[id] = true;
    return asObject;
  }, {})
  .then(asObject => { console.log(asObject); }; // Debug the result.

Or more concise with ES2015+:

return device_db.query('filters/templates')
  .then(({rows}) => rows.reduce((acc, {id}) => ({...acc, [id]: true }), {}))
  .then(result => console.log(result))

By the way: You could also use other strategies to "filter" your documents, as it's not necessary to emit the _id. Instead you can use the key and/or value for "secondary indexes":

{
  "_id": "_design/docs",
  "views": {
    "byType": "function(doc) { emit(doc.type); }",
    "templatesByName": "function(doc) { if (doc.type === 'template') emit(doc.name); }",
    "byTypeAndName": "function(doc) { emit([doc.type, doc.name], doc.name); }
  }
}
  • you can use docs/byType as an universal view for other doc types too. Just call it with db.query('docs/byType', { key: 'template' })
  • If you want the templates sorted by name, use db.query('docs/templatesByName') or db.query('docs/byTypeAndName', { startkey: ['template'], endkey: ['template', {}]}).

A word of caution: This is all untested and just from memory, so some brackets might be missing in the code, or some bugs might hide in there.

Provence answered 1/6, 2018 at 15:33 Comment(3)
I also tested your view function with the three documents, it should always return this: {"total_rows":3,"offset":0,"rows":[ {"id":"11111","key":"11111","value":null}, {"id":"22222","key":"22222","value":null}, {"id":"33333","key":"33333","value":null} ]}Provence
I just realized that you are using React Native (hard to see in the tags only). Nevertheless you can use console.log() to debug with the React Native Debugger installed. I'm still quite sure the main cause for the incorrect results you get isn't originating in the sync code or conflicts, nor in the map/reduce view. Try to simplify and remove as many parts as possible.Provence
I appreciate the response, but like I said in my question - it works until it doesn't work. It's not like it NEVER worked. It stopped working when I updated the document two revisions deeper then what the local database already had. I've tried doing console.log(templates) just to analyze what was being found. And it stays true to my original results. This feels more of a bug in pouch then a bug in my codeDrifter
F
0

It's not a bug in PDB, it's about outdated unfortunately components in pouchdb-react-native. Confirmed way is to combine pouchdb-react-native yourself like this - then queries work as expected:

import PouchDB from 'pouchdb-core';
PouchDB
.plugin(require('pouchdb-adapter-asyncstorage').default)
.plugin(require('pouchdb-adapter-http'))
.plugin(require('pouchdb-mapreduce'))
.plugin(require('pouchdb-replication'))
.plugin(require('pouchdb-authentication'));
const localDB = new PouchDB(localDBname, {adapter: 'asyncstorage', auto_compaction: true});

This way one can be sure that all components are the latest.

Foreconscious answered 15/7, 2018 at 6:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.