Fixed primary key-related (nullability) issues in DB schema

This commit is contained in:
2025-06-10 15:18:13 +02:00
parent 3a47154969
commit 29d23f8400
5 changed files with 40 additions and 23 deletions

View File

@ -56,6 +56,7 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline") val deadline = datetime("deadline")
val globalCriterion = reference("global_crit", GroupAssignmentCriteria.id)
} }
object GroupAssignmentCriteria : UUIDTable("grpAsCr") { object GroupAssignmentCriteria : UUIDTable("grpAsCr") {
@ -70,6 +71,7 @@ object SoloAssignments : UUIDTable("soloAssgmts") {
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline") val deadline = datetime("deadline")
val globalCriterion = reference("global_crit", SoloAssignmentCriteria.id)
} }
object SoloAssignmentCriteria : UUIDTable("soloAsCr") { object SoloAssignmentCriteria : UUIDTable("soloAsCr") {
@ -86,7 +88,7 @@ object PeerEvaluations : UUIDTable("peerEvals") {
object GroupFeedbacks : CompositeIdTable("grpFdbks") { object GroupFeedbacks : CompositeIdTable("grpFdbks") {
val assignmentId = reference("group_assignment_id", GroupAssignments.id) 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 groupId = reference("group_id", Groups.id)
val feedback = text("feedback") val feedback = text("feedback")
val grade = varchar("grade", 32) val grade = varchar("grade", 32)
@ -96,7 +98,7 @@ object GroupFeedbacks : CompositeIdTable("grpFdbks") {
object IndividualFeedbacks : CompositeIdTable("indivFdbks") { object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
val assignmentId = reference("group_assignment_id", GroupAssignments.id) 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 groupId = reference("group_id", Groups.id)
val studentId = reference("student_id", Students.id) val studentId = reference("student_id", Students.id)
val feedback = text("feedback") val feedback = text("feedback")
@ -107,7 +109,7 @@ object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
object SoloFeedbacks : CompositeIdTable("soloFdbks") { object SoloFeedbacks : CompositeIdTable("soloFdbks") {
val assignmentId = reference("solo_assignment_id", SoloAssignments.id) 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 studentId = reference("student_id", Students.id)
val feedback = text("feedback") val feedback = text("feedback")
val grade = varchar("grade", 32) val grade = varchar("grade", 32)

View File

@ -3,7 +3,10 @@ package com.jaytux.grader.data
import MigrationUtils import MigrationUtils
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils 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.transactions.transaction
import org.jetbrains.exposed.sql.update
object Database { object Database {
val db by lazy { val db by lazy {

View File

@ -60,6 +60,7 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by GroupAssignments.name var name by GroupAssignments.name
var assignment by GroupAssignments.assignment var assignment by GroupAssignments.assignment
var deadline by GroupAssignments.deadline var deadline by GroupAssignments.deadline
var globalCriterion by GroupAssignmentCriterion referencedOn GroupAssignments.globalCriterion
val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId
} }
@ -98,6 +99,7 @@ class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by SoloAssignments.name var name by SoloAssignments.name
var assignment by SoloAssignments.assignment var assignment by SoloAssignments.assignment
var deadline by SoloAssignments.deadline var deadline by SoloAssignments.deadline
var globalCriterion by SoloAssignmentCriterion referencedOn SoloAssignments.globalCriterion
val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId
} }

View File

@ -475,7 +475,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable() private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
val criteria = RawDbState { 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() } val feedback = RawDbState { loadFeedback() }
@ -494,7 +494,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> { private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
val allCrit = GroupAssignmentCriterion.find { val allCrit = GroupAssignmentCriterion.find {
GroupAssignmentCriteria.assignmentId eq assignment.id GroupAssignmentCriteria.assignmentId eq assignment.id
} }.filter { it.id != assignment.globalCriterion.id }
return Group.find { return Group.find {
(Groups.editionId eq assignment.edition.id) (Groups.editionId eq assignment.edition.id)
@ -502,16 +502,19 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where { val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where {
(GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id) (GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id)
}.map { row -> }.map { row ->
val crit = row[GroupFeedbacks.criterionId]?.let { GroupAssignmentCriterion[it] } val crit = GroupAssignmentCriterion[row[GroupFeedbacks.criterionId]]
val fdbk = row[GroupFeedbacks.feedback] val fdbk = row[GroupFeedbacks.feedback]
val grade = row[GroupFeedbacks.grade] val grade = row[GroupFeedbacks.grade]
crit to FeedbackEntry(fdbk, grade) crit to FeedbackEntry(fdbk, grade)
} }
val global = forGroup.firstOrNull { it.first == null }?.second val global = forGroup.firstOrNull { it.first.id == assignment.globalCriterion.id }?.second
val byCrit_ = forGroup.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } val byCrit_ = forGroup
.filterNotNull().associateBy { it.criterion.id } .filter{ it.first.id != assignment.globalCriterion.id }
.map { LocalCriterionFeedback(it.first, it.second) }
.associateBy { it.criterion.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: LocalCriterionFeedback(c, null) byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
} }
@ -527,16 +530,19 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
(IndividualFeedbacks.assignmentId eq assignment.id) and (IndividualFeedbacks.assignmentId eq assignment.id) and
(GroupStudents.studentId eq student.id) and (Groups.id eq group.id) (GroupStudents.studentId eq student.id) and (Groups.id eq group.id)
}.map { row -> }.map { row ->
val crit = row[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] } val crit = GroupAssignmentCriterion[row[IndividualFeedbacks.criterionId]]
val fdbk = row[IndividualFeedbacks.feedback] val fdbk = row[IndividualFeedbacks.feedback]
val grade = row[IndividualFeedbacks.grade] val grade = row[IndividualFeedbacks.grade]
crit to FeedbackEntry(fdbk, grade) crit to FeedbackEntry(fdbk, grade)
} }
val global = forSt.firstOrNull { it.first == null }?.second val global = forSt.firstOrNull { it.first == assignment.globalCriterion.id }?.second
val byCrit_ = forSt.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } val byCrit_ = forSt
.filterNotNull().associateBy { it.criterion.id } .filter { it.first != assignment.globalCriterion.id }
.map { LocalCriterionFeedback(it.first, it.second) }
.associateBy { it.criterion.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: LocalCriterionFeedback(c, null) byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
} }
@ -556,7 +562,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
it[groupId] = group.id it[groupId] = group.id
it[this.feedback] = msg it[this.feedback] = msg
it[this.grade] = grd it[this.grade] = grd
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() feedback.refresh(); autofill.refresh()
@ -570,7 +576,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
it[studentId] = student.id it[studentId] = student.id
it[this.feedback] = msg it[this.feedback] = msg
it[this.grade] = grd it[this.grade] = grd
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() 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 _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
val criteria = RawDbState { 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() } val feedback = RawDbState { loadFeedback() }
@ -641,22 +647,25 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> { private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> {
val allCrit = SoloAssignmentCriterion.find { val allCrit = SoloAssignmentCriterion.find {
SoloAssignmentCriteria.assignmentId eq assignment.id SoloAssignmentCriteria.assignmentId eq assignment.id
} }.filter { it.id != assignment.globalCriterion.id }
return editionCourse.second.soloStudents.sortAsc(Students.name).map { student -> return editionCourse.second.soloStudents.sortAsc(Students.name).map { student ->
val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where { val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where {
(IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id) (IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id)
}.map { row -> }.map { row ->
val crit = row[IndividualFeedbacks.criterionId]?.let { SoloAssignmentCriterion[it] } val crit = SoloAssignmentCriterion[row[IndividualFeedbacks.criterionId]]
val fdbk = row[IndividualFeedbacks.feedback] val fdbk = row[IndividualFeedbacks.feedback]
val grade = row[IndividualFeedbacks.grade] val grade = row[IndividualFeedbacks.grade]
crit to LocalFeedback(fdbk, grade) crit to LocalFeedback(fdbk, grade)
} }
val global = forStudent.firstOrNull { it.first == null }?.second val global = forStudent.firstOrNull { it.first == assignment.globalCriterion.id }?.second
val byCrit_ = forStudent.map { it.first?.let { k -> Pair(k, it.second) } } val byCrit_ = forStudent
.filterNotNull().associateBy { it.first.id } .filter { it.first != assignment.globalCriterion.id }
.map { Pair(it.first, it.second) }
.associateBy { it.first.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: Pair(c, null) byCrit_[c.id] ?: Pair(c, null)
} }
@ -672,7 +681,7 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
it[studentId] = student.id it[studentId] = student.id
it[this.feedback] = msg ?: "" it[this.feedback] = msg ?: ""
it[this.grade] = grd ?: "" it[this.grade] = grd ?: ""
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() feedback.refresh(); autofill.refresh()

View File

@ -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-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposed" }
exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", 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-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" } 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" } sqlite = { group = "org.xerial", name = "sqlite-jdbc", version = "3.34.0" }
sl4j = { group = "org.slf4j", name = "slf4j-simple", version = "2.0.12" } sl4j = { group = "org.slf4j", name = "slf4j-simple", version = "2.0.12" }