From 29d23f84007a34c678d410013947079191c996aa Mon Sep 17 00:00:00 2001 From: jay-tux Date: Tue, 10 Jun 2025 15:18:13 +0200 Subject: [PATCH] Fixed primary key-related (nullability) issues in DB schema --- .../kotlin/com/jaytux/grader/data/DSL.kt | 8 ++-- .../kotlin/com/jaytux/grader/data/Database.kt | 3 ++ .../kotlin/com/jaytux/grader/data/Entities.kt | 2 + .../com/jaytux/grader/viewmodel/DbState.kt | 47 +++++++++++-------- gradle/libs.versions.toml | 3 +- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt index f81fd01..6f68497 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/DSL.kt @@ -56,6 +56,7 @@ object GroupAssignments : UUIDTable("grpAssgmts") { val name = varchar("name", 50) val assignment = text("assignment") val deadline = datetime("deadline") + val globalCriterion = reference("global_crit", GroupAssignmentCriteria.id) } object GroupAssignmentCriteria : UUIDTable("grpAsCr") { @@ -70,6 +71,7 @@ object SoloAssignments : UUIDTable("soloAssgmts") { val name = varchar("name", 50) val assignment = text("assignment") val deadline = datetime("deadline") + val globalCriterion = reference("global_crit", SoloAssignmentCriteria.id) } object SoloAssignmentCriteria : UUIDTable("soloAsCr") { @@ -86,7 +88,7 @@ object PeerEvaluations : UUIDTable("peerEvals") { object GroupFeedbacks : CompositeIdTable("grpFdbks") { val assignmentId = reference("group_assignment_id", GroupAssignments.id) - val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable() + val criterionId = reference("criterion_id", GroupAssignmentCriteria.id) val groupId = reference("group_id", Groups.id) val feedback = text("feedback") val grade = varchar("grade", 32) @@ -96,7 +98,7 @@ object GroupFeedbacks : CompositeIdTable("grpFdbks") { object IndividualFeedbacks : CompositeIdTable("indivFdbks") { val assignmentId = reference("group_assignment_id", GroupAssignments.id) - val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable() + val criterionId = reference("criterion_id", GroupAssignmentCriteria.id) val groupId = reference("group_id", Groups.id) val studentId = reference("student_id", Students.id) val feedback = text("feedback") @@ -107,7 +109,7 @@ object IndividualFeedbacks : CompositeIdTable("indivFdbks") { object SoloFeedbacks : CompositeIdTable("soloFdbks") { val assignmentId = reference("solo_assignment_id", SoloAssignments.id) - val criterionId = reference("criterion_id", SoloAssignmentCriteria.id).nullable() + val criterionId = reference("criterion_id", SoloAssignmentCriteria.id) val studentId = reference("student_id", Students.id) val feedback = text("feedback") val grade = varchar("grade", 32) diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt index b3bbecc..f247bbe 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Database.kt @@ -3,7 +3,10 @@ package com.jaytux.grader.data import MigrationUtils import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.update object Database { val db by lazy { diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt index 80dccf9..b585d90 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/data/Entities.kt @@ -60,6 +60,7 @@ class GroupAssignment(id: EntityID) : Entity(id) { var name by GroupAssignments.name var assignment by GroupAssignments.assignment var deadline by GroupAssignments.deadline + var globalCriterion by GroupAssignmentCriterion referencedOn GroupAssignments.globalCriterion val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId } @@ -98,6 +99,7 @@ class SoloAssignment(id: EntityID) : Entity(id) { var name by SoloAssignments.name var assignment by SoloAssignments.assignment var deadline by SoloAssignments.deadline + var globalCriterion by SoloAssignmentCriterion referencedOn SoloAssignments.globalCriterion val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId } diff --git a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt index 0ad0483..a79a268 100644 --- a/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt +++ b/composeApp/src/desktopMain/kotlin/com/jaytux/grader/viewmodel/DbState.kt @@ -475,7 +475,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) { private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() val criteria = RawDbState { - assignment.criteria.orderBy(GroupAssignmentCriteria.name to SortOrder.ASC).toList() + assignment.criteria.orderBy(GroupAssignmentCriteria.name to SortOrder.ASC).filter { it.id != assignment.globalCriterion.id } } val feedback = RawDbState { loadFeedback() } @@ -494,7 +494,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) { private fun Transaction.loadFeedback(): List> { val allCrit = GroupAssignmentCriterion.find { GroupAssignmentCriteria.assignmentId eq assignment.id - } + }.filter { it.id != assignment.globalCriterion.id } return Group.find { (Groups.editionId eq assignment.edition.id) @@ -502,16 +502,19 @@ class GroupAssignmentState(val assignment: GroupAssignment) { val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where { (GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id) }.map { row -> - val crit = row[GroupFeedbacks.criterionId]?.let { GroupAssignmentCriterion[it] } + val crit = GroupAssignmentCriterion[row[GroupFeedbacks.criterionId]] val fdbk = row[GroupFeedbacks.feedback] val grade = row[GroupFeedbacks.grade] crit to FeedbackEntry(fdbk, grade) } - val global = forGroup.firstOrNull { it.first == null }?.second - val byCrit_ = forGroup.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } - .filterNotNull().associateBy { it.criterion.id } + val global = forGroup.firstOrNull { it.first.id == assignment.globalCriterion.id }?.second + val byCrit_ = forGroup + .filter{ it.first.id != assignment.globalCriterion.id } + .map { LocalCriterionFeedback(it.first, it.second) } + .associateBy { it.criterion.id } + val byCrit = allCrit.map { c -> byCrit_[c.id] ?: LocalCriterionFeedback(c, null) } @@ -527,16 +530,19 @@ class GroupAssignmentState(val assignment: GroupAssignment) { (IndividualFeedbacks.assignmentId eq assignment.id) and (GroupStudents.studentId eq student.id) and (Groups.id eq group.id) }.map { row -> - val crit = row[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] } + val crit = GroupAssignmentCriterion[row[IndividualFeedbacks.criterionId]] val fdbk = row[IndividualFeedbacks.feedback] val grade = row[IndividualFeedbacks.grade] crit to FeedbackEntry(fdbk, grade) } - val global = forSt.firstOrNull { it.first == null }?.second - val byCrit_ = forSt.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } - .filterNotNull().associateBy { it.criterion.id } + val global = forSt.firstOrNull { it.first == assignment.globalCriterion.id }?.second + val byCrit_ = forSt + .filter { it.first != assignment.globalCriterion.id } + .map { LocalCriterionFeedback(it.first, it.second) } + .associateBy { it.criterion.id } + val byCrit = allCrit.map { c -> byCrit_[c.id] ?: LocalCriterionFeedback(c, null) } @@ -556,7 +562,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) { it[groupId] = group.id it[this.feedback] = msg it[this.grade] = grd - it[criterionId] = criterion?.id + it[criterionId] = criterion?.id ?: assignment.globalCriterion.id } } feedback.refresh(); autofill.refresh() @@ -570,7 +576,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) { it[studentId] = student.id it[this.feedback] = msg it[this.grade] = grd - it[criterionId] = criterion?.id + it[criterionId] = criterion?.id ?: assignment.globalCriterion.id } } feedback.refresh(); autofill.refresh() @@ -628,7 +634,7 @@ class SoloAssignmentState(val assignment: SoloAssignment) { private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() val criteria = RawDbState { - assignment.criteria.orderBy(SoloAssignmentCriteria.name to SortOrder.ASC).toList() + assignment.criteria.orderBy(SoloAssignmentCriteria.name to SortOrder.ASC).filter { it.id != assignment.globalCriterion.id } } val feedback = RawDbState { loadFeedback() } @@ -641,22 +647,25 @@ class SoloAssignmentState(val assignment: SoloAssignment) { private fun Transaction.loadFeedback(): List> { val allCrit = SoloAssignmentCriterion.find { SoloAssignmentCriteria.assignmentId eq assignment.id - } + }.filter { it.id != assignment.globalCriterion.id } return editionCourse.second.soloStudents.sortAsc(Students.name).map { student -> val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where { (IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id) }.map { row -> - val crit = row[IndividualFeedbacks.criterionId]?.let { SoloAssignmentCriterion[it] } + val crit = SoloAssignmentCriterion[row[IndividualFeedbacks.criterionId]] val fdbk = row[IndividualFeedbacks.feedback] val grade = row[IndividualFeedbacks.grade] crit to LocalFeedback(fdbk, grade) } - val global = forStudent.firstOrNull { it.first == null }?.second - val byCrit_ = forStudent.map { it.first?.let { k -> Pair(k, it.second) } } - .filterNotNull().associateBy { it.first.id } + val global = forStudent.firstOrNull { it.first == assignment.globalCriterion.id }?.second + val byCrit_ = forStudent + .filter { it.first != assignment.globalCriterion.id } + .map { Pair(it.first, it.second) } + .associateBy { it.first.id } + val byCrit = allCrit.map { c -> byCrit_[c.id] ?: Pair(c, null) } @@ -672,7 +681,7 @@ class SoloAssignmentState(val assignment: SoloAssignment) { it[studentId] = student.id it[this.feedback] = msg ?: "" it[this.grade] = grd ?: "" - it[criterionId] = criterion?.id + it[criterionId] = criterion?.id ?: assignment.globalCriterion.id } } feedback.refresh(); autofill.refresh() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 46d35c3..0408ea0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,6 +20,7 @@ kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-co exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposed" } exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposed" } exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposed" } +exposed-migration = { group = "org.jetbrains.exposed", name = "exposed-migration", version.ref = "exposed" } exposed-kotlin-datetime = { group = "org.jetbrains.exposed", name = "exposed-kotlin-datetime", version.ref = "exposed" } sqlite = { group = "org.xerial", name = "sqlite-jdbc", version = "3.34.0" } sl4j = { group = "org.slf4j", name = "slf4j-simple", version = "2.0.12" } @@ -33,4 +34,4 @@ rtfield = { group = "com.mohamedrejeb.richeditor", name = "richeditor-compose", [plugins] composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }