Firestore query endBefore & endAt not working as expected

Viewed 1813

I am using Node 10, firebase 8.0, firebase-admin 9.3.0. I've included a copy of my test code & client side js version.

Data

Document ID | name | is_visible
----------- | ---- | ----------
     1      |   a  | true
     2      |   b  | true
     3      |   c  | true
     4      |   d  | true
     5      |   e  | true
     6      |   f  | true
     7      |   g  | true
     8      |   h  | true
     9      |   i  | true

Code

var admin = require("firebase-admin");

const firebaseConfig = {xxx}

admin.initializeApp({
    credential: admin.credential.cert(firebaseConfig)
  });

const getDocs = async () =>{

  try {

    const cursor = await admin.firestore().collection('ordering').doc('5').get();

    const beforeDocs = await admin.firestore().collection('ordering')
    .orderBy("name", "desc")
    .endAt(cursor)
    .limit(2)
    .get()


      beforeDocs.forEach((doc)=> {
        console.log("Doc ID - ",doc.id, ", Name -", doc.get('name'));
      })

    
    const afterDocs = await admin.firestore().collection('ordering')
      .orderBy("name", "desc")
      .startAt(cursor)
      .limit(2)
      .get()


    afterDocs.forEach((doc)=> {
      console.log("Doc ID - ",doc.id, ", Name -", doc.get('name'));
    })

  }
  catch (error) {
    console.error(error);
  }
}

getDocs();

run using node getDocs.js

Expect Results

Doc ID -  6 , Name - f
Doc ID -  5 , Name - e
Doc ID -  5 , Name - e
Doc ID -  4 , Name - d

Actual results

Doc ID -  9 , Name - i
Doc ID -  8 , Name - h
Doc ID -  5 , Name - e
Doc ID -  4 , Name - d

My temporary work around for the endAt is to reverse the order to "asc" use startAt then within the QuerySnapshot reverse the docs array before the forEach.

I've created this JS to show my issue, this is running of my test collection, its super rough, please be kind, outputs to the console.

I'm clearly doing something wrong, but just not sure what.

<script src="https://www.gstatic.com/firebasejs/8.0.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.0.0/firebase-firestore.js"></script>

<body>
  <div id="output">Check the console!</div>
</body>
<script>

  var firebaseConfig = {
    apiKey: "AIzaSyAxLEo7LfABdATToJTMQGae3Vgum_CAlQg",
    authDomain: "bmtestbase.firebaseapp.com",
    databaseURL: "https://bmtestbase.firebaseio.com",
    projectId: "bmtestbase",
    storageBucket: "bmtestbase.appspot.com",
    messagingSenderId: "326028950684",
    appId: "1:326028950684:web:74b55887466c63b4a9c159"
  };

  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  
const db = firebase.firestore()
   
// Setting the Focus point using a Snapshot 
db.collection('ordering').get()
  .then (docs => {
    docs.forEach(doc => {
          console.log("MYCODE_allDocs: DocID ", doc.id, ", Name ", doc.get('name'))
    })
  })

db.collection('ordering').doc('5').get()
  .then( focusSnapshot => {

    db.collection("ordering")
      .orderBy("name", "asc")
      .endAt(focusSnapshot)
      .limit(2)
      .get()
    .then(docs => {
  
       console.log("MYCODE_endAt_expected:","DocID 4, Name d")
       console.log("MYCODE_endAt_expected:","DocID 5, Name e")      

      docs.forEach(doc => {  
        console.log("MYCODE_endAt:","DocID ", doc.id, ", Name ", doc.get('name'))
      }); 
    })


    db.collection("ordering")
      .orderBy("name", "asc")
      .startAt(focusSnapshot)
      .limit(2)
      .get()
    .then(docs => {

       console.log("MYCODE_startAt_expected:","DocID 5, Name e")
       console.log("MYCODE_startAt_expected:","DocID 6, Name f")      

      docs.forEach(doc => {  
        console.log("MYCODE_startAt:","DocID ", doc.id, ", Name ", doc.get('name'))
      }); 
    })
  
})

</script>

2 Answers

According to firebase support, you need to use limitToLast with endBefore and endAt

Rewriting your example like this should work:

<script src="https://www.gstatic.com/firebasejs/8.0.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.0.0/firebase-firestore.js"></script>

<body>
  <div id="output">Check the console!</div>
</body>
<script>

  var firebaseConfig = {
    apiKey: "AIzaSyAxLEo7LfABdATToJTMQGae3Vgum_CAlQg",
    authDomain: "bmtestbase.firebaseapp.com",
    databaseURL: "https://bmtestbase.firebaseio.com",
    projectId: "bmtestbase",
    storageBucket: "bmtestbase.appspot.com",
    messagingSenderId: "326028950684",
    appId: "1:326028950684:web:74b55887466c63b4a9c159"
  };

  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  
const db = firebase.firestore()
   
// Setting the Focus point using a Snapshot 
db.collection('ordering').get()
  .then (docs => {
    docs.forEach(doc => {
          console.log("MYCODE_allDocs: DocID ", doc.id, ", Name ", doc.get('name'))
    })
  })

db.collection('ordering').doc('5').get()
  .then( focusSnapshot => {

    db.collection("ordering")
      .orderBy("name", "asc")
      .endAt(focusSnapshot)
      .limitToLast(2)
      .get()
    .then(docs => {
  
       console.log("MYCODE_endAt_expected:","DocID 4, Name d")
       console.log("MYCODE_endAt_expected:","DocID 5, Name e")      

      docs.forEach(doc => {  
        console.log("MYCODE_endAt:","DocID ", doc.id, ", Name ", doc.get('name'))
      }); 
    })


    db.collection("ordering")
      .orderBy("name", "asc")
      .startAt(focusSnapshot)
      .limit(2)
      .get()
    .then(docs => {

       console.log("MYCODE_startAt_expected:","DocID 5, Name e")
       console.log("MYCODE_startAt_expected:","DocID 6, Name f")      

      docs.forEach(doc => {  
        console.log("MYCODE_startAt:","DocID ", doc.id, ", Name ", doc.get('name'))
      }); 
    })
  
})

</script>

I think your expectations for startAt and endAt are not correct. I suggest reviewing the documentation.

About startAt, it says:

Use the startAt() or startAfter() methods to define the start point for a query. The startAt() method includes the start point, while the startAfter() method excludes it.

So, if you pass a snapshot for startAt() with limit 5, you are going to start the query at that document, and get 4 documents that follow it in the query order.

Similarly, use the endAt() or endBefore() methods to define an end point for your query results.

endAt() with a limit 5 causes the query results to stop at that document. So you will get that document, in addition to the 4 documents that precede it in the query order.

Related