Firefoo를 이용하여 Javascript query로 firestore data 핸들링 해보자.
Admin ADK는 비동기 명령을 하기 위해 Javascript Promise를 사용하며, default run function은 async이다.
Collection.add
를 사용한다. 각 요청은 한줄씩 순차적으로 실행되므로 요청이 많다면 오래 걸릴 수 있다.
async function run() {
await db.collection("companies").add({name: "Google"})
await db.collection("companies").add({name: "Apple"})
await db.collection("companies").add({name: "Microsoft"})
}
병렬 실행하려면 Collection.add의 요청 결과를 배열로 저장하고 Promise.all
로 모든 요청이 끝나기를 기다린다.
async function run() {
// add docs parellel - faster
const promises = []
promises.push( db.collection("companies").add({ name: "Google" }) )
promises.push( db.collection("companies").add({ name: "Apple" }) )
promises.push( db.collection("companies").add({ name: "Microsoft" }) )
return await Promise.all(promises)
}
Array.map
을 사용하면 식이 좀 더 간결해진다.
async function run() {
const companies = [{name: "Google"}, {name: "Apple"}, {name: "Microsoft"}]
const promises = companies.map((data) => db.collection("companies").add(data))
return await Promise.all(promises)
}
필터링과 정렬을 위해 where
과 orderBy
를 사용할 수 있다. Timestamp
값을 사용하게 될때 Firestore Timestamp 인스턴스로 변환해야하며, Javascript Date나 unix timestamp는 작동하지 않는다.
const minDate = new Date("1995-12-17T03:24:00")
const maxDate = new Date("2022-04-13T20:24:00")
// Firestore Timestamp 인스턴스로 변환
const minTimestamp = admin.firestore.Timestamp.fromDate(minDate)
const maxTimestamp = admin.firestore.Timestamp.fromDate(maxDate)
async function run() {
const query = await db.collection("books")
.where("rdt", ">", minTimestamp) // 생성일(rdt)이 minDate보다 크고
.where("rdt", "<", maxTimestamp) // 생성일(rdt)이 maxDate보다 작고
.orderBy("rdt", "desc") // 생성일(rdt)로 정렬
.limit(30) //30개까지
.get(); // 읽기
return query
}
로그는 console.log
를 사용한다.
async function run() {
const query = await db.collection("users").where("lastname", "==", "Kim")
for (const doc of query.docs) {
console.log("result is : " + doc.data().lastname)
}
}
콜렉션의 전체 다큐먼트의 username 필드를 user 필드로 변경한다면, 모든 document를 순차적으로 가져와 하나씩 업데이트 하는 것이 가장 쉬운 방법이지만 데이터가 많다면 느리다.
async function run() {
const query = await db.collection("users").get(); // users collection의 전체 데이터 읽기
for (const doc of query.docs) {
const name = doc.data().name ?? "unnamed"
await doc.ref.update({
username: name, //새로운 필드 생성 및 값 주입
name: admin.firestore.FieldValue.delete() // 필드 삭제
})
}
}
빠르게 사용하려면 병렬로 업데이트해야 한다. Promise.all
을 사용하면 전체 update가 끝날 때까지 프로세스가 죽지 않는다.
async function run() {
const query = await db.collection("users").get()
const promises = query.docs.map((doc) => {
const fieldUpdates = {
username: doc.data().name ?? "unnamed", //새로운 필드 생성 및 값 주입
name: admin.firestore.FieldValue.delete() // 필드 삭제
}
return doc.ref.update(fieldUpdates)
})
return await Promises.all(promises)
}
요청의 수를 최소화하기 위해 최대 500개의 명령을 batched writes
로 사용할 수 있다. 배치는 여러 write 명령을 가질 수 있지만 read 명령은 하지 못한다.
async function run() {
const query = await db.collection("users").get();
const docChunks = _.chunk(query.docs, 500);
for (const docChunk of docChunks) {
const batch = db.batch();
for (const doc of docChunk) {
const fieldUpdates = {
username: doc.data().name ?? "unnamed",
name: admin.firestore.FieldValue.delete(),
}
batch.update(doc.ref, fieldUpdates);
}
console.log(`committing chunk of ${docChunk.length} updates`)
await batch.commit();
}
}
Firestore는 join collection을 지원하지 않는다. 아래의 쿼리를 통해 몇개의 collection에서 join 동작을 수행할 수 있다.
// employees, companies collection 2개의 데이터를 읽어와 필터링하여 join한 것처럼 사용한다
// companies의 경우 수익이 10K 이상인 회사만 추린다
async function run() {
const employeePromise = db.collection("employees").get();
const companyPromise = db.collection("companies").where("revenue", ">", 10000).get(); // 수익이 10K 이상인 회사만 필터
const [employeeQuery, companyQuery] = await Promise.all([employeePromise, companyPromise]);
// 필터링된 회사의 id를 추출
const companyDocsById = _.keyBy(companyQuery.docs, (doc) => doc.id);
for (const employeeDoc of employeeQuery.docs) {
const employee = employeeDoc.data();
const companyDoc = companyDocsById[employee.companyId];
// 사원을 하나씩 확인하며 해당 사원의 회사가 필터링된 회사 리스트에 있다면 로그를 찍고 없으면 다음 사원으로 넘어간다
if (companyDoc != null) {
const company = companyDoc.data();
console.log(`${employee.name} works at ${company.name} (${company.revenue} revenue)`);
}
}
}