How can I get another document in a design document function? - couchdb

There are some special design document functions, like updates and validate_doc_update besides of views and lists functions; where our functions are get executed upon specific events by issuing a single document per call.
I wonder if we can fetch another document within a single update function, like so:
updates: {
"hello" : "function(doc, req) {
if (doc.type === 'example') {
otherDoc = fetch(doc.relatedDocId)
doc.relatedDocName = otherDoc.name
return [doc, 'Hey Hey...']
}
}"
Point is I want to use a fetch(documentId) like function in order to update some other documents based on current document information.

When a document is updated, there is no way in CouchDB to automatically update a second, related document. You may want to implement a worker listening to the changes feed and perform such updates there.

Related

Why is data retrieved from Firestore returning empty array when there are subcollections present?

I am trying to retrieve data from my the my firestore database using angularfire2.
This is what my current database looks like. I have a users collection that contains the userId doc which binds the userDetails and userPosts together.
However when I query this collection, it returns an empty array in the console.
I am using a firebase function to retrieve the data.
Firebase Function Index.ts
export const getFeed = functions.https.onCall(async (req,res) =>{
const docs = await admin.firestore().collection('users').get()
return docs.docs.map(doc => {
return {
postID: doc.id,
...doc.data()
}
})
})
TS File
tabTwoFeedInit (){
const getFeed = this.aff.httpsCallable('getFeed')
this.ajax = getFeed({}).subscribe(data=> {
console.log(data)
this.posts = data
})
}
How can I retrieve data from this firebase database successfully?
Firestore reads are shallow, and so they won't return subcollections automatically. Thus, your get() will only return the document ID, since the document has no fields.
To return the subcollections of a document, you need to call the getCollections method on that document. This can only by done by the admin API, but that should be fine for you since you are running inside a cloud function. As the documentation notes, it is generally expected that collection names are predictable (as they appear to be in your case), but if they aren't, you might consider restructuring your data.
Why are shallow reads desirable? It makes it possible to avoid retrieving potentially large collections of information that might be associated with, say, a user, so you can structure data more naturally. Depending on the size of the data, its possible that a field that is a map might make more sense for userDetails (but a collection is probably the right thing for userPosts).
If you are just creating the cloud function to retrieve the posts from this structure. I would prefer to restructure the database a bit and just use Collection Group Query on client side (with no requirement of cloud functions) to pull the posts.
By restructuring I mean, you should store userID inside the documents of userPosts sub collection.
Then simply user Collection Group Query to retrieve post of specific users.
The syntax is of firebase javascript library. You can find it's equivalent of angularfire
let posts= db.collectionGroup('userPosts').where('userId', '==', 'some-user-id');
I also ran into the same problem but I solved it by adding a field to the particular document i am trying to retrieve.Sometimes the cause is because the documents you are trying to get has no field in it. What I mean is that a document must have a field before the server can recognize it as an existing document.
Possibly there may be no field in the document you are trying to retrieve which makes tye server say it is not existing and so will not be retrieved.
So if you run into this problem, then consider adding a field to that document before you can successfully retrieve it.

Is it possible in mongodb to use an async function to populate the $set field?

Each document in my collection has a status field that needs to be updated periodically with an async api call that fetches the document's current status by its id.
For example:
db.items.update({}, { $set: { status: getStatusById(id?) } }, handleResult);
I cannot just select all ids at once as my process runs out of memory if I do that. There are over 6 million documents in the collection.
If there is no shortcut, another approach would be to step through the database 100 at a time but I'm not sure if that's the best approach here.
I am looking for something analogous to docs.map(getStatusById) in JS only with a mongodb collection.
getStatusById makes an api call to a 3rd party resource -- I need to take results from that and add to doc.

Can I create multiple collections per database?

Switching from mongo to pouchdb (with Cloudant), i like the "one database per user" concept, but is there a way to create multiple collections/tables per database ?
Example
- Peter
- History
- Settings
- Friends
- John
- History
- Settings
- Friends
etc...
Couchdb does not have the concept of collections. However, you can achieve similar results using type identifiers on your documents in conjunction with Couchdb views.
Type Identifiers
When you save a document in Couchdb add a field that specifies the type. For example, you would store a friend like so:
{
_id: "XXXX",
type: "Friend",
first_name: "John",
...
}
And you would store history like this:
{
_id: "XXXX",
type: "History",
url: "http://www.google.com",
...
}
Both of these documents would be in the same database, and if you queried all documents on that database then you would receive both.
Views
You can create views that filter on type and then query those views directly. For example, create a view to retrieve friends like so (in Cloudant you can go to add new Design Document and you can copy and paste this directly):
{
"_id" : "_design/friends",
"views" : {
"all" : {
"map" : "function(doc){ if (doc.type && doc.type == 'Friend') { emit(doc._id, doc._rev)}}"
}
}
}
Let's expand the map function:
function(doc) {
if (doc.type && doc.type == "Friend") {
emit(doc._id, doc._rev);
}
}
Essentially this map function is saying to only associate documents to this view that have type == "Friend". Now, we can query this view and only friends will be returned:
http://SERVER/DATABASE/_design/friends/_view/all
Where friends = name of the design document and all = name of the view. Replace SERVER with your server and DATABASE with your database name.
You can find more information about views here:
https://wiki.apache.org/couchdb/Introduction_to_CouchDB_views
You could look into relational-pouch for something like this. Else you could do "3 databases per user." ;)
I may not fully understand what you need here but in general you can achieve what you describe in 3 different ways in CouchDB/Cloudant/PouchDB.
Single document per person (Peter, John). Sure - if the collections are not enormous and more importantly if they are not updated by different users concurrently (or worse in different database instances) leading to conflicts then, in JSON just an element for each collection, holding an array and you can manipulate everything with just one document. Makes access a breeze.
Single document per collection (Peter History, Peter Settings ect). Similar constraints, but you could create a document to hold each of these collections. Provided they will not be concurrently modified often, you would then have a document for Peter's History, and another for Peter's Settings.
Single document per item. This is the finest grain approach - lots of small simple documents each containing one element (say a single History entry for Peter). The code gets slightly simpler because removing items becomes a delete and many clients can update items simultaneously, but now you depend on Views to bring all the items into a list. A view with keys [person, listName, item] for example would let you access what you want.
Generally your data schema decisions come down to concurrency. You mention PouchDB so it may be that you have a single threaded client and option 1 is nice and easy?

Use linux timestamp in CouchDB map function

Trying to update an existing CouchDB map function so that it only returns docs created in the past 24 hours.
The current map is very simple
function(doc) {
if(doc.email && doc.type == 'user')
emit(doc.email, doc);
}
I'd like to get the current linux timestamp value and compare that to the creationTime.unix value stored in the doc.
Is that possible?
N.B I'm building the view in futon
I do not know if you can do that, but it if you can that would be very bad for CouchDB database sanity.
Map functions for same document should always emit same values, each time you invoke it (provided that document has not changed in the mean time). This is important since CouchDB stores this emited data in the index, and does not recalculate it again until it is necessary. If map functions could emit different values for the same doc, that would render index unusable.
So, no, do not try that.
Good news is that you can easily achieve what you need without that. If you emit creation time, than you can query your view just for docs with creation time in certain interval like in:
/blog/_design/docs/_view/by_date?startkey="2010/01/01 00:00:00"&endkey="2010/02/00 00:00:00"
Read more how you can query your views in CouchDB The Definitive Guide

couchdb, get last 10 documents

Using mysql this would be:
SELECT * FROM thetable ORDER BY id DESC LIMIT 10
How can I do this in couchdb for all documents with "type":"message"? (without pulling all documents with type:message)
Thanks
Create a view that emits all doc ids. The view keys will be used for sorting automatically.
function(doc) {
if(doc.type && doc.type === 'message'){
emit(doc._id, null);
}
}
Then execute a query: http://host/yourdb/_design/yourdesigndoc/_view/viewname?limit=10&include_docs=true&descending=true
Because you want the full document, we didn't included anything as value in the view. Instead, we add include_docs=true to fetch every full document for the view entries.
Note that there is also a builtin view that does the same: http://host/yourdb/_all_docs?limit=10&include_docs=true&descending=true
PS: You should be aware of the fact that CouchDB by default uses UUIDs as IDs, which will render the sorting more or less useless, if you really want to get the latest docs. Either provide your own incremental IDs (what about distribution/replication?) or use a new field that stores the time the doc has been created and use in the view as well.
If your docs have a created field (i.e. UNIX timestamp, JavaScript Date.now() or even a RFC 3339-like string), you can build an index on these values.
Here is the time-based view:
function(doc) {
if(doc.type && doc.type === 'message' && doc.created){
emit(doc.created, null);
}
}
Note that we will not emit the doc._id itself. However, CouchDB stores the doc._id where the data came from for each emitted key/value pair automatically, so we can again use include_docs=true to fetch the complete docs.
Query http://host/yourdb/_design/yourdesigndoc/_view/viewname?limit=10&include_docs=true&descending=true
If the IDs of your documents are already incremental, instead of the default UUIDs of CouchDB, you do not even need to define a view, you can just use the default _all_docs view, e.g.
http://couchdb_host/your_db/_all_docs?limit=10&descending=true&include_docs=true

Resources