Firestore unique index or unique constraint?

前端 未结 3 1865
悲哀的现实
悲哀的现实 2020-11-30 07:53

Is it possible in Firestore to define an index with a unique constraint? If not, how is it possible to enforce uniqueness on a document field (without using document ID)?

相关标签:
3条回答
  • 2020-11-30 08:17

    Yes, this is possible using a combination of two collections, Firestore rules and batched writes.

    https://cloud.google.com/firestore/docs/manage-data/transactions#batched-writes

    The simple idea is, using a batched write, you write your document to your "data" collection and at the same write to a separate "index" collection where you index the value of the field that you want to be unique.

    Using the Firestore rules, you can then ensure that the "data" collection can only have a document written to it if the document field's value also exists in the index collection and, vice versa, that the index collection can only be written to if value in the index matches what's in the data collection.

    Example

    Let's say that we have a User collection and we want to ensure that the username field is unique.

    Our User collection will contain simply the username

    /User/{id}
    {
      username: String 
    }
    

    Our Index collection will contain the username in the path and a value property that contains the id of the User that is indexed.

    /Index/User/username/{username}
    {
      value: User.id
    }
    

    To create our User we use a batch write to create both the User document and the Index document at the same time.

    const firebaseApp = ...construct your firebase app
    
    const createUser = async (username) => {
      const database = firebaseApp.firestore()
      const batch = database.batch()
    
      const Collection = database.collection('User')
      const ref = Collection.doc()
      batch.set(ref, {
        username
      })
    
      const Index = database.collection('Index')
      const indexRef = Index.doc(`User/username/${username}`)
      batch.set(indexRef, {
        value: ref.id
      })
    
      await batch.commit()
    }
    

    To update our User's username we use a batch write to update the User document, delete the previous Index document and create a new Index document all at the same time.

    const firebaseApp = ...construct your firebase app
    
    const updateUser = async (id, username) => {
      const database = firebaseApp.firestore()
      const batch = database.batch()
    
      const Collection = database.collection('User')
      const ref = Collection.doc(id)
      const refDoc = await ref.get()
      const prevData = refDoc.data()
      batch.update(ref, {
        username
      })
    
      const Index = database.collection('Index')
      const prevIndexRef = Index.doc(`User/username/${prevData.username}`)
      const indexRef = Index.doc(`User/username/${username}`)
      batch.delete(prevIndexRef)
      batch.set(indexRef, {
        value: ref.id
      })
    
      await batch.commit()
    }
    

    To delete a User we use a batch write to delete both the User document and the Index document at the same time.

    const firebaseApp = ...construct your firebase app
    
    const deleteUser = async (id) => {
      const database = firebaseApp.firestore()
      const batch = database.batch()
    
      const Collection = database.collection('User')
      const ref = Collection.doc(id)
      const refDoc = await ref.get()
      const prevData = refDoc.data()
      batch.delete(ref)
    
    
      const Index = database.collection('Index')
      const indexRef = Index.doc(`User/username/${prevData.username}`)
      batch.delete(indexRef)
    
      await batch.commit()
    }
    

    We then setup our Firestore rules so that they only allow a User to be created if the username is not already indexed for a different User. A User's username can only be updated if an Index does not already exist for the username and a User can only be deleted if the Index is deleted as well. Create and update will fail with a "Missing or insufficient permissions" error if a User with the same username already exists.

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
    
    
        // Index collection helper methods
    
        function getIndexAfter(path) {
          return getAfter(/databases/$(database)/documents/Index/$(path))
        }
    
        function getIndexBefore(path) {
          return get(/databases/$(database)/documents/Index/$(path))
        }
    
        function indexExistsAfter(path) {
          return existsAfter(/databases/$(database)/documents/Index/$(path))
        }
    
        function indexExistsBefore(path) {
          return exists(/databases/$(database)/documents/Index/$(path))
        }
    
    
        // User collection helper methods
    
        function getUserAfter(id) {
          return getAfter(/databases/$(database)/documents/User/$(id))
        }
    
        function getUserBefore(id) {
          return get(/databases/$(database)/documents/User/$(id))
        }
    
        function userExistsAfter(id) {
          return existsAfter(/databases/$(database)/documents/User/$(id))
        }
    
    
        match /User/{id} {
          allow read: true;
    
          allow create: if
            getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id;
    
          allow update: if
            getIndexAfter(/User/username/$(getUserAfter(id).data.username)).data.value == id &&
            !indexExistsBefore(/User/username/$(getUserAfter(id).data.username));
    
          allow delete: if
            !indexExistsAfter(/User/username/$(getUserBefore(id).data.username));
        }
    
        match /Index/User/username/{username} {
          allow read: if true;
    
          allow create: if
            getUserAfter(getIndexAfter(/User/username/$(username)).data.value).data.username == username;
    
          allow delete: if 
            !userExistsAfter(getIndexBefore(/User/username/$(username)).data.value) || 
            getUserAfter(getIndexBefore(/User/username/$(username)).data.value).data.username != username;
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-30 08:29

    Based on the documentation from this section https://cloud.google.com/firestore/docs/manage-data/add-data#set_a_document

    You can simply add a custom identifier when adding document object to a collection as shown below:

    const data = {
      name: 'Los Angeles',
      state: 'CA',
      country: 'USA'
    };
    
    // Add a new document in collection "cities" with ID 'LA'
    const res = await db.collection('cities').doc('LA').set(data);
    

    Using this https://cloud.google.com/firestore/docs/manage-data/add-data#node.js_4 as a reference when you use set as a method on your collection you can be able to specify an id for such document when you need to auto-generate an id you simply use the add method on your collection

    0 讨论(0)
  • 2020-11-30 08:30

    [Its not a perfect solution but working]

    I have done this unique key using key... I want my table to be having unique date value. so i made it key of my document. Any way i am able to get all documents

    db.collection('sensors').doc(sensorId).collection("data").doc(date).set(dataObj).then(() => {
        response.send(dataObj);
    });
    
    0 讨论(0)
提交回复
热议问题