| Metadata | Value |
|---|---|
| Status | Active |
| Version | 1.1.0 |
| Last Updated | 2026-09-10 |
| Author | Sangeetha Grantha Team |
| Document Type | Design reference |
[!NOTE] Design/reference material: this page may include proposals or earlier implementation assumptions. Use current feature map for implemented behavior and current operating steps.
When updating only temple_id for a Krithi:
UPDATE krithis SET
title='...', title_normalized='...', incipit='...',
incipit_normalized='...', composer_id='...',
musical_form='KRITHI'::musical_form_enum,
primary_language='sa'::language_code_enum,
tala_id='...', temple_id='...', -- Only this changed!
is_ragamalika=FALSE, workflow_state='draft'::workflow_state_enum,
sahitya_summary='...', notes='...', updated_at='...'
WHERE krithis.id = '...'
ragaIds parameter is provided:
SELECT krithi_ragas.krithi_id, krithi_ragas.raga_id,
krithi_ragas.order_index, krithi_ragas."section",
krithi_ragas.notes
FROM krithi_ragas
WHERE krithi_ragas.krithi_id = '...'
SELECT krithis.id, krithis.title, krithis.incipit, ...
FROM krithis
WHERE krithis.id = '...'
SELECT krithi_tags.krithi_id, krithi_tags.tag_id, ...
FROM krithi_tags
WHERE krithi_tags.krithi_id = '...'
Problem: The UPDATE statement includes ALL non-null fields from the request, even if they haven’t changed.
Impact:
Root Cause: Exposed’s update method includes all fields set in the lambda, regardless of actual changes.
Problem: After updating, a separate SELECT is executed to fetch the updated row.
Impact:
Better Approach: Use PostgreSQL’s RETURNING clause to get updated row in same query.
Problem:
krithi_ragas is queried even when ragaIds parameter is null (not being updated)Impact:
Problem: No comparison with existing values before updating.
Impact:
Current:
val updated = KrithisTable.update(...) { ... }
// ... raga updates ...
KrithisTable.selectAll().where { ... }.map { it.toKrithiDto() }.singleOrNull()
Optimized:
val updatedRow = KrithisTable.update({ ... }) { ... }
.returning()
.singleOrNull()
?.let { it.toKrithiDto() }
Benefits:
Note: Confirmed Exposed v1 supports RETURNING.
Current:
KrithisTable.update({ KrithisTable.id eq id.toJavaUuid() }) {
title?.let { value -> it[KrithisTable.title] = value }
templeId?.let { value -> it[KrithisTable.templeId] = value }
// ... all fields included if non-null
}
Optimized:
// Fetch existing row first (or cache from previous query)
val existing = KrithisTable.selectAll()
.where { KrithisTable.id eq id.toJavaUuid() }
.singleOrNull() ?: return@dbQuery null
// Only update changed fields
KrithisTable.update({ KrithisTable.id eq id.toJavaUuid() }) {
if (title != null && title != existing[KrithisTable.title]) {
it[KrithisTable.title] = title
}
if (templeId != null && templeId != existing[KrithisTable.templeId]) {
it[KrithisTable.templeId] = templeId
}
// ... only include changed fields
it[KrithisTable.updatedAt] = now
}
Trade-off: Adds one SELECT upfront, but:
Alternative: If frontend sends only changed fields, this becomes easier.
Current:
ragaIds?.let { ragas ->
// Always queries existing ragas
val existingRagas = KrithiRagasTable.selectAll()...
}
Optimized:
// Only query if ragas are actually being updated
if (ragaIds != null) {
val existingRagas = KrithiRagasTable.selectAll()...
// ... update logic
}
Note: Current code already does this with ?.let, but ensure it’s not being called unnecessarily.
Current:
toDelete.forEach { (ragaId, orderIndex) ->
KrithiRagasTable.deleteWhere {
(KrithiRagasTable.krithiId eq javaKrithiId) and
(KrithiRagasTable.ragaId eq ragaId) and
(KrithiRagasTable.orderIndex eq orderIndex)
}
}
Optimized:
if (toDelete.isNotEmpty()) {
// Single DELETE with IN clause for composite key
// Note: PostgreSQL supports composite IN, but Exposed may need workaround
KrithiRagasTable.deleteWhere {
(KrithiRagasTable.krithiId eq javaKrithiId) and
// Use OR conditions for composite key deletes
// Or use raw SQL if Exposed doesn't support efficiently
}
}
Challenge: Exposed may not support efficient composite key batch deletes. May need raw SQL:
DELETE FROM krithi_ragas
WHERE krithi_id = ?
AND (raga_id, order_index) IN ((?, ?), (?, ?), ...)
If the frontend needs ragas/tags in the response, consider:
Current: Separate queries Optimized: Single query with LEFT JOINs (if Exposed supports efficiently)
Trade-off: More complex query, but fewer round-trips. Only beneficial if data is always needed.
RETURNING clause - Implemented across all repositoriessuspend fun update(
id: Uuid,
// ... parameters ...
): KrithiDto? = DatabaseFactory.dbQuery {
val now = OffsetDateTime.now(ZoneOffset.UTC)
val javaId = id.toJavaUuid()
// Use Exposed 1.0.0 updateReturning to update and fetch the row in one round-trip
val updatedKrithi = KrithisTable
.updateReturning(
where = { KrithisTable.id eq javaId }
) {
title?.let { value -> it[KrithisTable.title] = value }
titleNormalized?.let { value -> it[KrithisTable.titleNormalized] = value }
// ... other fields
it[KrithisTable.updatedAt] = now
}
.singleOrNull()
?.toKrithiDto()
if (updatedKrithi == null) {
return@dbQuery null
}
// Handle ragas with smart diffing (only if provided)
ragaIds?.let { ragas ->
// Smart diffing algorithm for raga updates
// ... existing raga update logic with delta updates ...
}
updatedKrithi
}
Key Improvements:
updateReturning instead of UPDATE + SELECTRETURNING clause via updateReturning and resultedValuesStatus: ✅ COMPLETED (2025-01-27)
Repositories Optimized:
Related Documentation: