Delete Firestore documents based on where condtiion [duplicate] - firebase

var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_ref.delete();
Error thrown
jobskill_ref.delete is not a function

You can only delete a document once you have a DocumentReference to it. To get that you must first execute the query, then loop over the QuerySnapshot and finally delete each DocumentSnapshot based on its ref.
var jobskill_query = db.collection('job_skills').where('job_id','==',post.job_id);
jobskill_query.get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
doc.ref.delete();
});
});

I use batched writes for this. For example:
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id);
let batch = firestore.batch();
jobskill_ref
.get()
.then(snapshot => {
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit();
})
ES6 async/await:
const jobskills = await store
.collection('job_skills')
.where('job_id', '==', post.job_id)
.get();
const batch = store.batch();
jobskills.forEach(doc => {
batch.delete(doc.ref);
});
await batch.commit();

//The following code will find and delete the document from firestore
const doc = await this.noteRef.where('userId', '==', userId).get();
doc.forEach(element => {
element.ref.delete();
console.log(`deleted: ${element.id}`);
});

the key part of Frank's answer that fixed my issues was the .ref in doc.ref.delete()
I originally only had doc.delete() which gave a "not a function" error. now my code looks like this and works perfectly:
let fs = firebase.firestore();
let collectionRef = fs.collection(<your collection here>);
collectionRef.where("name", "==", name)
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
doc.ref.delete().then(() => {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});

or try this, but you must have the id beforehand
export const deleteDocument = (id) => {
return (dispatch) => {
firebase.firestore()
.collection("contracts")
.doc(id)
.delete()
}
}

You can now do this:
db.collection("cities").doc("DC").delete().then(function() {
console.log("Document successfully deleted!");
}).catch(function(error) {
console.error("Error removing document: ", error);
});

And of course, you can use await/async:
exports.delete = functions.https.onRequest(async (req, res) => {
try {
var jobskill_ref = db.collection('job_skills').where('job_id','==',post.job_id).get();
jobskill_ref.forEach((doc) => {
doc.ref.delete();
});
} catch (error) {
return res.json({
status: 'error', msg: 'Error while deleting', data: error,
});
}
});
I have no idea why you have to get() them and loop on them, then delete() them, while you can prepare one query with where to delete in one step like any SQL statement, but Google decided to do it like that. so, for now, this is the only option.

If you're using Cloud Firestore on the Client side, you can use a Unique key generator package/module like uuid to generate an ID. Then you set the ID of the document to the ID generated from uuid and store a reference to the ID on the object you're storing in Firestore.
For example:
If you wanted to save a person object to Firestore, first, you'll use uuid to generate an ID for the person, before saving like below.
const uuid = require('uuid')
const person = { name: "Adebola Adeniran", age: 19}
const id = uuid() //generates a unique random ID of type string
const personObjWithId = {person, id}
export const sendToFireStore = async (person) => {
await db.collection("people").doc(id).set(personObjWithId);
};
// To delete, get the ID you've stored with the object and call // the following firestore query
export const deleteFromFireStore = async (id) => {
await db.collection("people").doc(id).delete();
};
Hope this helps anyone using firestore on the Client side.

The way I resolved this is by giving each document a uniqueID, querying on that field, getting the documentID of the returned document, and using that in the delete. Like so:
(Swift)
func rejectFriendRequest(request: Request) {
DispatchQueue.global().async {
self.db.collection("requests")
.whereField("uniqueID", isEqualTo: request.uniqueID)
.getDocuments { querySnapshot, error in
if let e = error {
print("There was an error fetching that document: \(e)")
} else {
self.db.collection("requests")
.document(querySnapshot!.documents.first!.documentID)
.delete() { err in
if let e = err {
print("There was an error deleting that document: \(e)")
} else {
print("Document successfully deleted!")
}
}
}
}
}
}
The code could be cleaned up a bit, but this is the solution I came up with. Hope it can help someone in the future!

const firestoreCollection = db.collection('job_skills')
var docIds = (await firestoreCollection.where("folderId", "==", folderId).get()).docs.map((doc => doc.id))
// for single result
await firestoreCollection.doc(docIds[0]).delete()
// for multiple result
await Promise.all(
docIds.map(
async(docId) => await firestoreCollection.doc(docId).delete()
)
)

delete(seccion: string, subseccion: string)
{
const deletlist = this.db.collection('seccionesclass', ref => ref.where('seccion', '==', seccion).where('subseccion', '==' , subseccion))
deletlist.get().subscribe(delitems => delitems.forEach( doc=> doc.ref.delete()));
alert('record erased');
}

The code for Kotlin, including failure listeners (both for the query and for the delete of each document):
fun deleteJobs(jobId: String) {
db.collection("jobs").whereEqualTo("job_id", jobId).get()
.addOnSuccessListener { documentSnapshots ->
for (documentSnapshot in documentSnapshots)
documentSnapshot.reference.delete().addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: failed to delete document ${documentSnapshot.reference.id}", e)
}
}.addOnFailureListener { e ->
Log.e(TAG, "deleteJobs: query failed", e)
}
}

Related

Firestore batch delete don't work while using emulator with react-native

I want to try some code with firestore emulator before using it in production, I want basically to retrieve a collection documents sort them and set them again in the collection:
I have this error while doing a batch delete :
[Error: [firestore/permission-denied] The caller does not have permission to execute the specified operation.]
the code:
useEffect(() => {
(async () => {
await admin_sortUserRanksDB()
})()
}, [])
const admin_sortUserRanksDB = async () => {
const usersData = await admin_getUserDataDBAndClean()
populateUserCollection(usersData)
}
const admin_getUserDataDBAndClean = async () => {
try {
const querySnapshot = await firestore()
.collection('users')
.orderBy('experience_amount', 'desc')
.get();
let rank = 1;
let newDataUsers = [];
for (const user of querySnapshot.docs) {
const userData = user.data();
userData.rank = rank;
newDataUsers.push(userData)
rank++
}
await deleteUserCollection(querySnapshot)
return newDataUsers;
} catch (error) {
if (!__DEV__) {
crashlytics().log(
`error getUserDataDB()
userActions.js ===>> ${error.message}`
);
}
console.log('error getUserDataDB ', error)
return null
}
}
const deleteUserCollection = async (usersQuerySnapshot) => {
// Create a new batch instance
const batch = firestore().batch();
usersQuerySnapshot.forEach(documentSnapshot => {
batch.delete(documentSnapshot.ref);
});
console.log('==============')
return batch.commit();
}
const populateUserCollection = usersData => {
if (usersData) {
const batch = firestore().batch();
usersData.forEach(doc => {
let docRef = firestore()
.collection('users')
.doc(); //automatically generate unique id
batch.set(docRef, doc);
});
batch
.commit()
.catch(error => {
console.log('error populating users', error)
});
}
}
After posting an issue to react-native-firebase repo i was suggested to modify my rules to be open (only locally) and the batch delete worked.
I used the allow read, write: if true in firestore.rules file
link to issue on GitHub

How to ensure that a cloud function is running every time a new document gets created?

I am uploading my questions and answers to my quiz to Firestore. For that I am using following function:
const firestore = admin.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
if (data && (typeof data === "object")) {
Object.keys(data).forEach(docKey => {
var data_to_push = data[docKey];
data_to_push['category'] = "Wirtschaft";
firestore.collection(collectionKey).add(data_to_push).then((res) => {
console.log("Document " + docKey + " successfully written!");
}).catch((error) => {
console.error("Error writing document: ", error);
});
});
This function works fine, all the documents I need are created but whenever a document get created I have another function that is running:
// This function adds the doc ids of newly created questions to an arrayList
exports.AddKeyToArray = functions.region('europe-west1').firestore.document('Questions/{nameId}').onCreate(async (snp, context) => {
console.log(snp.id);
console.log(context.params);
await db.collection("Questions_keys").doc(snp.data().category).update({ "questions": admin.firestore.FieldValue.arrayUnion(snp.id) }).then(() => {
return console.log("Key added");
}).catch(async (e) => {
console.log(e);
if (e.code === 5) {
await db.collection("Questions_keys").doc(snp.data().category).set({ "questions": admin.firestore.FieldValue.arrayUnion(snp.id) }).then(() => {
return console.log("First time key added");
}).catch(e => {
return console.log(e);
})
}
})
return "okay";
})
This function basically gets the document id of the previously added question/answer and creates an array with all the document ids of that quiz category (so I then later can get a random question without much reading operations). The problem is that not all document ids are added to the array so I wanted to know if there is a better way to ensure that all the document ids are added into the array.
I upload sometimes 500 documents at once, would be a solution to reduce the documents I upload at once to ensure a better performance of the second function?
Any help is much appreciated!
I suggest that rather than using cloud functions here is to create another collection in your database. This way you can add more questions to that collection easily. This design will increase performance as what you will need is only query the new collection directly and this way you will avoid all the complication needed to manage and work with Cloud Functions.
With help I found a solution: The following function uploads data to firestore and gets the ids of the documents and sets it to an array:
...
const firestore = admin.firestore();
const settings = { timestampsInSnapshots: true };
firestore.settings(settings);
if (data && (typeof data === "object")) {
Object.keys(data).forEach(async docKey => {
var data_to_push = data[docKey];
data_to_push['category'] = "Deutschland";
await firestore.collection(collectionKey).add(data_to_push).then(async (res) => {
var key = (res['_path']['segments'][1]);
await firestore.collection("Questions_keys").doc(data_to_push['category']).update({ "questions": admin.firestore.FieldValue.arrayUnion(key) }).then(() => {
console.log("Key added: " + key);
}).catch(async (e) => {
if (e.code === 5) {
await firestore.collection("Questions_keys").doc(data_to_push['category']).set({ "questions": admin.firestore.FieldValue.arrayUnion(key) }).then(() => {
return console.log("First time key added");
}).catch(e => {
return console.log(e);
})
}
console.log(e);
})
}).catch((error) => {
console.error("Error writing document: ", error);
});
});
}

Firestore: slow query

I have a query that gets all the messages from my messages collection for a particular chatId.
It then sorts it in descending order on the createdAt timestamp and has a limit of 20 messages applied to that.. My queries take ~2500 ms... which I feel is slow.. I don't even have many messages in my collection so I feel like it could get slower once I have thousands of them..
I'm new to firestore, so perhaps I'm doing something wrong? I appreciate the help!
Here is the code for the query:
exports.getMessages = functions.https.onCall(async (data, context) => {
try {
const startAt = data.startAt;
const increment = 20;
const limit = startAt + increment;
const chatId = data.chatId;
const snapshot = await firestore
.collection('messages')
.where('chatId', '==', chatId)
.orderBy('createdAt', 'desc')
.limit(limit)
.get();
const promises = [];
snapshot.forEach(doc => {
const message = {id: doc.id, ...doc.data()};
const promise = combineUserAndMessage({
message,
userId: message.user._id,
});
promises.push(promise);
});
const messages = await Promise.all(promises);
return {data: messages, startAt: limit};
} catch (err) {
console.log('GET MESSAGES ERROR: ', err);
throw err;
}
});
const combineUserAndMessage = async ({userId, message}) => {
try {
const userSnapshot = await firestore.doc(`users/${userId}`).get();
const user = userSnapshot.data();
const createdAt = new admin.firestore.Timestamp(
message.createdAt._seconds,
message.createdAt._nanoseconds,
).toDate();
return {
...message,
user: {
_id: userId,
name: user.displayName,
avatar: user.profilePicture,
},
createdAt: createdAt.toString(),
};
} catch (err) {
console.log('COMBINE USER AND MESSAGE ERROR: ', err);
throw err;
}
};
EDIT: I took the benchmark from the function logs. I understand that there can be cold start costs included in that benchmark so I'll try to benchmark it using my own logs. That said, if anyone sees something wrong with the query, or knows of a way to make it faster/better, I'd still appreciate it!

How can I get specific document data from firestore querysnapshot?

I got a querysnapshot in a function.
And want to bring the whole querysnapshot to another function (functionTwo).
In functionTwo, I want to get a specific document in the querysnapshot WITHOUT forEach. The specific doc can be changed by different cases.
ref_serial_setting.get()
.then(querysnapshot => {
return functionTwo(querysnapshot)
})
.catch(err => {
console.log('Error getting documents', err)
})
let functionTwo = (querysnapshot) => {
// getting value
const dataKey_1 = "dataKey_1"
// Tried 1
const value = querysnapshot.doc(dataKey_1).data()
// Tried 2
const value = querysnapshot.document(dataKey_1).data()
// Tried 3 (Put 'data_name': dataKey_1 in that doc)
const value = querysnapshot.where('data_name', '==', dataKey_1).data()
}
The result are all these trying are not a function.
How can I get specific document data from querysnapshot??
or
Is there any easy method to change the querysnapshot to JSON?
You can get an array of the document snapshots by using the docs property of a QuerySnapshot. After that you'll have to loop through getting the data of the doc snapshots looking for your doc.
const docSnapshots = querysnapshot.docs;
for (var i in docSnapshots) {
const doc = docSnapshots[i].data();
// Check for your document data here and break when you find it
}
Or if you don't actually need the full QuerySnapshot, you can apply the filter using the where function before calling get on the query object:
const dataKey_1 = "dataKey_1";
const initialQuery = ref_serial_setting;
const filteredQuery = initialQuery.where('data_name', '==', dataKey_1);
filteredQuery.get()
.then(querySnapshot => {
// If your data is unique in that document collection, you should
// get a query snapshot containing only 1 document snapshot here
})
.catch(error => {
// Catch errors
});
Theres an easy way to do this, each QuerySnapshot has a property docs which returns an array of QueryDocumentSnapshots. See QuerySnapshot documentation.
let citiesRef = db.collection('cities');
let query = citiesRef.where('capital', '==', true).get().then(snapshot => {
snapshot.docs[0]; // => returns first document
});
let citiesRef = db.collection('cities');
let query = citiesRef.where('capital', '==', true).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
from https://firebase.google.com/docs/firestore/query-data/get-data
you can use this code :
const querySnapshot = await getDocs(collection(db, "collectionNaame"));
const docSnapshots = querySnapshot.docs;
for (var i in docSnapshots) {
console.log(i)
const doc = docSnapshots[i].data();
console.log(doc)
Just do
db.doc(<<ref>>).get()
this returns a promise
[here ]: https://firebase.google.com/docs/firestore/query-data/get-data#get_a_document is the link to the docs

What's the best way to check if a Firestore record exists if its path is known?

Given a given Firestore path what's the easiest and most elegant way to check if that record exists or not short of creating a document observable and subscribing to it?
Taking a look at this question it looks like .exists can still be used just like with the standard Firebase database. Additionally, you can find some more people talking about this issue on github here
The documentation states
NEW EXAMPLE
var docRef = db.collection("cities").doc("SF");
docRef.get().then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
OLD EXAMPLE
const cityRef = db.collection('cities').doc('SF');
const doc = await cityRef.get();
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
Note: If there is no document at the location referenced by docRef, the resulting document will be empty and calling exists on it will return false.
OLD EXAMPLE 2
var cityRef = db.collection('cities').doc('SF');
var getDoc = cityRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
console.log('Document data:', doc.data());
}
})
.catch(err => {
console.log('Error getting document', err);
});
If the model contains too much fields, would be a better idea to apply a field mask on the CollectionReference::get() result (let's save more google cloud traffic plan, \o/). So would be a good idea choose to use the CollectionReference::select() + CollectionReference::where() to select only what we want to get from the firestore.
Supposing we have the same collection schema as firestore cities example, but with an id field in our doc with the same value of the doc::id. Then you can do:
var docRef = db.collection("cities").select("id").where("id", "==", "SF");
docRef.get().then(function(doc) {
if (!doc.empty) {
console.log("Document data:", doc[0].data());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
Now we download just the city::id instead of download entire doc just to check if it exists.
Check this :)
var doc = firestore.collection('some_collection').doc('some_doc');
doc.get().then((docData) => {
if (docData.exists) {
// document exists (online/offline)
} else {
// document does not exist (only on online)
}
}).catch((fail) => {
// Either
// 1. failed to read due to some reason such as permission denied ( online )
// 2. failed because document does not exists on local storage ( offline )
});
2022 answer: You can now use the count() aggregation to check if a document exists without downloading it.
Here is a TypeScript example:
import { getCountFromServer, query, collection, documentId } from '#firebase/firestore'
const db = // ...
async function userExists(id: string): Promise<boolean> {
const snap = await getCountFromServer(query(
collection(db, 'users'), where(documentId(), '==', id)
))
return !!snap.data().count
}
I Encountered Same Problem recently while using Firebase Firestore and i used following approach to overcome it.
mDb.collection("Users").document(mAuth.getUid()).collection("tasks").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
if (task.getResult().isEmpty()){
Log.d("Test","Empty Data");
}else{
//Documents Found . add your Business logic here
}
}
}
});
task.getResult().isEmpty() provides solution that if documents against our query was found or not
Depending on which library you are using, it may be an observable instead of a promise. Only a promise will have the 'then' statement. You can use the 'doc' method instead of the collection.doc method, or toPromise() etc. Here is an example with the doc method:
let userRef = this.afs.firestore.doc(`users/${uid}`)
.get()
.then((doc) => {
if (!doc.exists) {
} else {
}
});
})
Hope this helps...
If for whatever reason you wanted to use an observable and rxjs in angular instead of a promise:
this.afs.doc('cities', "SF")
.valueChanges()
.pipe(
take(1),
tap((doc: any) => {
if (doc) {
console.log("exists");
return;
}
console.log("nope")
}));

Resources