Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I query firestore by document snapshot updateTime?

Is it possible to query firestore documents by updateTime. The field that is available from the document snapshot as doc.updateTime and use it in a where query?

I am using the node.js sdk.

like image 327
Stephen Avatar asked Feb 03 '19 12:02

Stephen


2 Answers

As far as I know there is no way to query on the metadata that Firestore automatically maintains. If you need to query the last update date, you will need to add a field with that value to the document's data.

like image 194
Frank van Puffelen Avatar answered Oct 04 '22 04:10

Frank van Puffelen


I really need to query Firebase on document _updateTime, so I wrote a function that copies that hidden internal timestamp to a queryable field. This took some work to figure this out, so I am posting the complete solution. (Technically, this is "Cloud Firestore" rather then "Realtime Database".)

This is done using Firebase Functions, which itself took some tries to get working. This tutorial was helpful:

https://firebase.google.com/docs/functions/get-started

However, on Windows 10, the only command line that worked was the new Bash shell, available since about 2017. This was something of a runaround to install, but necessary. The GIT Bash shell, otherwise very useful, was not able to keep track of screen positions during Firebase project setup.

In my example code, I have left in all the 'console.log' statements, to show detail. Not obvious at first was where these logs go. They do not go to the command line, but to the Firebase console:

https://console.firebase.google.com/u/0/

under (yourproject) > Functions > Logs

For testing, I found it useful to, at first, deploy only one function (this is in the CLI):

firebase deploy --only functions:testFn

Below is my working function, heavily commented, and with some redundancy for illustration. Replace 'PlantSpp' with the name of your collection of documents:

// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');

// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();

// Firestore maintains an interal _updateTime for every document, but this is
// not queryable. This function copies that to a visible field 'Updated'
exports.makeUpdateTimeVisible = functions.firestore
      .document('PlantSpp/{sppId}')
      .onWrite((sppDoc, context) => {
  console.log("Event type: ", context.eventType);
  // context.eventType = 'google.firestore.document.write', so cannot use
  // to distinguish e.g. create from update
  const docName = context.params.sppId // this is how to get the document name
  console.log("Before: ", sppDoc.before); // if a create, a 'DocumentSnapshot',
  // otherwise a 'QueryDocumentSnapshot'
  // if a create, everything about sppDoc.before is undefined
  if (typeof sppDoc.before._fieldsProto === "undefined"){
    console.log('document "', docName, '" has been created');
    // set flags here if desired
  }
  console.log("After: ", sppDoc.after); // if a delete, a 'DocumentSnapshot',
  // otherwise a 'QueryDocumentSnapshot'
  // if a delete, everything about sppDoc.after is undefined
  if (typeof sppDoc.after._fieldsProto === "undefined"){
    console.log('document "', docName, '" has been deleted');
    // other fields could be fetched from sppDoc.before
    return null; // no need to proceed
  }
  console.log(sppDoc.after.data()); // the user defined fields:values
  // inside curly braces
  console.log(sppDoc.after._fieldsProto); // similar to previous except with
  // data types, e.g.
  // data() has { Code: 'OLDO',...
  // _fieldsProto has { Code: { stringValue: 'OLDO' },...
  const timeJustUpdated = sppDoc.after._updateTime; // this is how to get the
  // internal nonqueryable timestamp
  console.log(timeJustUpdated);
  //  e.g.      Timestamp { _seconds: 1581615533, _nanoseconds: 496655000 }
  //  later:    Timestamp { _seconds: 1581617552, _nanoseconds: 566223000 }
  // shows this is correctly updating
  // see if the doc has the 'Updated' field yet
  if (sppDoc.after._fieldsProto.hasOwnProperty('Updated')) {
    console.log("doc has the field 'Updated' with the value",
                  sppDoc.after._fieldsProto.Updated);
    console.log("sppDoc:", sppDoc);
    const secondsInternal = timeJustUpdated._seconds;
    console.log(secondsInternal, "seconds, internal timestamp");
    const secondsExternal = sppDoc.after.data().Updated._seconds;
    console.log(secondsExternal, "seconds, external timestamp");
    // Careful here. If we just update the externally visible time to the
    // internal time, we will go into an infinite loop because that update
    // will call this function again, and by then the internal time will have
    // advanced
    // the following exit will not work:
    if (secondsInternal === secondsExternal) return null; // will never exit
    // instead, allow the external time to lag the internal by a little
    const secondsLate = secondsInternal - secondsExternal;
    if (secondsLate < 120) { // two minutes sufficient for this purpose
      console.log("the field 'Updated' is", secondsLate,
                  "seconds late, good enough");
       return null;
    }
    console.log("the field 'Updated' is", secondsLate,
                  "seconds late, updating");
    // return a promise of a set operation to update the timestamp
    return sppDoc.after.ref.set({
      Updated: timeJustUpdated
    }, {merge: true}); // 'merge' prevents overwriting whole doc
    // this change will call this same function again
  } else { // field 'Updated' does not exist in the document yet
    // this illustrates how to add a field
    console.log("doc does not have the field 'Updated', adding it now.");
    // return a promise of a set operation to create the timestamp
    return sppDoc.after.ref.set({
      Updated: timeJustUpdated
    }, {merge: true}); // 'merge' prevents overwriting the whole doc
    // this change will call this same function again
  }
});
like image 37
Rick Shory Avatar answered Oct 04 '22 03:10

Rick Shory