diff --git a/packages/data-ops/src/queries/report-cards.ts b/packages/data-ops/src/queries/report-cards.ts index bee9dbf1..e94db8e7 100644 --- a/packages/data-ops/src/queries/report-cards.ts +++ b/packages/data-ops/src/queries/report-cards.ts @@ -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) diff --git a/pr_body.md b/pr_body.md new file mode 100644 index 00000000..0ebe630d --- /dev/null +++ b/pr_body.md @@ -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.