Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions packages/data-ops/src/queries/report-cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,35 @@ async function validateReportCardPayloads(
),
]

const [_, classResults, termResults] = await Promise.all([
Promise.all(uniqueStudentIds.map(studentId => getScopedStudent(schoolId, studentId))),
Promise.all(uniqueClassIds.map(async classId => [classId, await getScopedClass(schoolId, classId)] as const)),
Promise.all(uniqueTermIds.map(async termId => [termId, await getScopedTerm(schoolId, termId)] as const)),
Promise.all(uniqueSchoolYearIds.map(schoolYearId => getScopedSchoolYear(schoolId, schoolYearId))),
Promise.all(uniqueTemplateIds.map(templateId => getScopedTemplate(schoolId, templateId))),
const db = getDb()

// ⚑ Bolt Optimization: Replace Promise.all/N+1 queries with parallelized bulk Drizzle queries
const [studentResults, classResults, termResults, schoolYearResults, templateResults] = await Promise.all([
uniqueStudentIds.length ? db.select({ id: students.id }).from(students).where(and(inArray(students.id, uniqueStudentIds), eq(students.schoolId, schoolId))) : Promise.resolve([]),
uniqueClassIds.length ? db.select({ id: classes.id, schoolYearId: classes.schoolYearId }).from(classes).where(and(inArray(classes.id, uniqueClassIds), eq(classes.schoolId, schoolId))) : Promise.resolve([]),
uniqueTermIds.length ? db.select({ id: terms.id, schoolYearId: terms.schoolYearId }).from(terms).innerJoin(schoolYears, eq(terms.schoolYearId, schoolYears.id)).where(and(inArray(terms.id, uniqueTermIds), eq(schoolYears.schoolId, schoolId))) : Promise.resolve([]),
uniqueSchoolYearIds.length ? db.select({ id: schoolYears.id }).from(schoolYears).where(and(inArray(schoolYears.id, uniqueSchoolYearIds), eq(schoolYears.schoolId, schoolId))) : Promise.resolve([]),
uniqueTemplateIds.length ? db.select({ id: reportCardTemplates.id }).from(reportCardTemplates).where(and(inArray(reportCardTemplates.id, uniqueTemplateIds), eq(reportCardTemplates.schoolId, schoolId))) : Promise.resolve([]),
])

const scopedClasses = new Map(classResults)
const scopedTerms = new Map(termResults)
if (studentResults.length !== uniqueStudentIds.length) {
throw dbError('NOT_FOUND', 'One or more students not found or access denied')
}
if (classResults.length !== uniqueClassIds.length) {
throw dbError('NOT_FOUND', 'One or more classes not found or access denied')
}
if (termResults.length !== uniqueTermIds.length) {
throw dbError('NOT_FOUND', 'One or more terms not found or access denied')
}
if (schoolYearResults.length !== uniqueSchoolYearIds.length) {
throw dbError('NOT_FOUND', 'One or more school years not found or access denied')
}
if (templateResults.length !== uniqueTemplateIds.length) {
throw dbError('NOT_FOUND', 'One or more templates not found or access denied')
}

const scopedClasses = new Map(classResults.map(c => [c.id, c]))
const scopedTerms = new Map(termResults.map(t => [t.id, t]))

for (const item of data) {
const scopedClass = scopedClasses.get(item.classId)
Expand Down
11 changes: 11 additions & 0 deletions pr_body.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
πŸ’‘ What:
Replaced the `Promise.all` logic inside `validateReportCardPayloads` that sequentially mapped over each ID array with single parallelized Drizzle bulk operations leveraging the `inArray()` condition.

🎯 Why:
The original approach suffered from an N+1 query vulnerability where for a large input payload of report cards, multiple single rows were queried instead of bulk retrieving everything. This led to high DB connection limits and heavy I/O operations for larger imports.

πŸ“Š Impact:
Significantly reduces query load from `O(N)` queries to an exact parallelization of `O(1)` query per unique ID group (5 queries total, run simultaneously via `Promise.all()`).

πŸ”¬ Measurement:
Run `pnpm --filter @repo/data-ops test` locally and check test passing results. The execution time of report card seeding routines should drop substantially.
Loading