This is an old revision of the document!
This is the underlying XCend schema of the whole STAT System. Everything persistent is represented in the schema. The schema directly splits into the three main groups User Accounts and Roles, Exam Management and Grades and Exercise Management and Sheets.
element stats {
element account * username { attribute lastName { string } attribute firstName { string } attribute email { string } attribute password { string } attribute code ? { string } attribute reset ? { string } element admin ? { } element examiner * exam {[ exists /exam[./exam]/examiner[../username] ]} element assistant * exercise {[ exists /exercise[./exercise]/assistant[../username] ]} element tutor * exercise { element group * id {[ exists /exercise[../exercise]/group[./id]/tutor[..account/username] ]} } element student ? { attribute id { ident [ count(., /account/student/id) = 1 ]} } } [ size(./account/admin) > 0 ]
element exercise * id { attribute lecture { string } attribute term { string } attribute open { boolean } element assistant * account {[ exists /account[./account]/assistant[..exercise/id] ]}
element group * id { [ count(./id, ..exercise/student/group) <= ./maxSize ] attribute day { "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday" } attribute time { string } attribute location { string } attribute curSize { integer [ . = count(../id, ..exercise/student/group) ]} attribute maxSize { integer [ . >= 0 ]} element tutor * account {[ exists /account[./account]/tutor[..exercise/id]/group[..group/id] ]} }
element sheet * id { attribute maxPoints { double [ . >= 0 ]} }
element student * id { [ count (./id, /account/student/id) = 1 ] attribute group ? { ident [ exists ..exercise/group[.] ]} attribute team ? { ident [ exists ../group ]} element result * sheet { [ exists ..exercise/sheet[./sheet] ] attribute points { double [ . >= 0 && . <= ..exercise/sheet[../sheet]/maxPoints ]} } }
}
element exam * id { attribute title { string } attribute date { string } attribute time { string } attribute location { string } attribute free { boolean } attribute published { boolean } attribute exercise { ident [ exists /exercise[.] ]} element examiner * account {[ /account[./account]/examiner[..exam/id] ]} element task * id { attribute maxPoints { double [ . >= 0 ]} } element grade * id { attribute name { string } [count(./name, ../grade/name) = 1] attribute value { double } attribute minPoints { double } } [ {exists ./grade[x], exists ./grade[y], x != y} ./grade[x]/value != ./grade[y]/value ] [ {exists ./grade[x], exists ./grade[y], x != y} ./grade[x]/minPoints != ./grade[y]/minPoints ] [ {exists ./grade[x], exists ./grade[y], ./grade[x]/value < ./grade[y]/value} ./grade[x]/minPoints > ./grade[y]/minPoints ] element participant * id { [ count (./id, /account/student/id) = 1 ] element result * task { [ exists ..exam/task[./task] ] attribute points { double [ . >= 0 && . <= ..exam/task[../task]/maxPoints ]} } } }
}
This section lists all atomic manipulations associated with the schema.
These are the associated procedures for User Accounts and Roles, i.e. one of their parameters is a username
and they will most likely be offered in a corresponding Account
class in an OO setting. Many of them, however, will also have other ident
parameters, which makes it possible to offer them in these classes, too.
# the current implementation also changes the username, i.e. it would need an additional parameter, # I can't support it and I would forbid it anyway. changeAttributes(ident uid, ident username, string firstName, string lastName) { assume exists /account[uid]/admin || uid = username; assume exists /account[username]; # implicitly true in OO implementations update /account[username]/firstName firstName; update /account[username]/lastName lastName; }
changePassword(ident uid, ident username, string password) { assume exists /account[uid]/admin || uid = username; assume exists /account[username]; # implicitly true in OO implementations update /account[username]/password password; }
requestReset(ident username, string reset) { assume exists /account[username]; assume not exists /account[username]/code; # account is already validated if not exists /account[username]/reset then insert /account[username]/reset; fi update /account[username]/reset reset; }
resetPassword(ident username, string reset, string password) { assume exists /account[username]/reset; # reset code was requested before, implies account exists assume /account[username]/reset = reset; remove /account[username]/reset; update /account[username]/password password; }
The students in the Exercise Management and Sheets section, as well as the participants in the Exam Management and Grades section, depend on the student role. This restricts the deletion of the role, i.e. the host language first has to delete these dependencies with corresponding procedures.
addStudentId(ident uid, ident username, ident id) { assume exists /account[uid]/admin; assume exists /account[username]; # implicitly true in OO implementations assume not exists /account[username]/student; assume count(id, /account/student/id) = 0; insert /account[username] <student id=[id] />; }
removeStudentId(ident uid, ident username) { assume exists /account[uid]/admin; assume exists /account[username]/student; # implies the account exists, which is implicitly true anyway assume size(/exercise/student[/account[username]/student/id]) = 0; assume size(/exam/participant[/account[username]/student/id]) = 0; remove /account[username]/student; }
The admin role is pretty much just a flag, without any constraints at all. The precondition is therefore only concerned with uniqueness and trivial stuff.
grantAdminRights(ident uid, ident username) { assume exists /account[uid]/admin; assume exists /account[username]; # implicitly true in OO implementations assume not exists /account[username]/admin; insert /account[username] <admin />; }
revokeAdminRights(ident uid, ident username) { assume exists /account[uid]/admin; assume exists /account[username]/admin; # implies the account exists, which is implicitly true anyway assume size(/account/admin) > 1; # kind of a practical constraint remove /account[username]/admin; }
The examiner role has to fulfill integrity constraints and has a counterpart in the Exam Management and Grades section of the document.
grantExaminerRights(ident uid, ident username, ident examId) { assume exists /account[uid]/admin; assume exists /account[username]; # implicitly true in OO implementations assume exists /exam[examId]; assume not exists /account[username]/examiner[examId]); assume not exists /exam[examId]/examiner[username]); # implied by integrity and the assumption before insert /account[username] <examiner exam=[examId] />; insert /exam[examId] <examiner account=[username] />; }
revokeExaminerRights(ident uid, ident username, ident examId) { assume exists /account[uid]/admin; assume exists /account[username]/examiner[examId]; # implies the account exists, which is implicitly true anyway assume exists /exam[examId]/examiner[username]; # implied by integrity and the assumption before remove /account[username]/examiner[examId]; remove /exam[examId]/examiner[username]; }
The assistant role relates to exercises like the examiner role relates to exams, so it also has to fulfill integrity constraints and has a counterpart in Exercise Management and Sheets.
grantAssistantRights(ident uid, ident username, ident exerciseId) { assume exists /account[uid]/admin; assume exists /account[username]; # implicitly true in OO implementations assume exists /exercise[exerciseId]; assume not exists /account[username]/assistant[exerciseId]); assume not exists /exercise[exerciseId]/assistant[username]; # implied by integrity and the assumption before insert /account[username] <assistant exercise=[exerciseId] />; insert /exercise[exerciseId] <assistant account=[username] />; }
revokeAssistantRights(ident uid, ident username, ident exerciseId) { assume exists /account[uid]/admin; assume exists /account[username]/assistant[exerciseId]; # implies the account exists, which is implicitly true anyway assume exists /exercise[exerciseId]/assistant[username]; # implied by integrity and the assumption before remove /account[username]/assistant[exerciseId]; remove /exercise[exerciseId]/assistant[username]; }
The tutor role also behaves similarly, but this time it references two identifiers in the Exercise Management and Sheets section, where it also has a counterpart.
This modeling of tutor rights is different from the current implementation. It might also be a nice design, if you can be tutor for an exercise, without (yet) having a group. This would grant some access rights already, I think.
grantTutorRights(ident uid, ident username, ident exerciseId, ident groupId) { assume exists /account[uid]/assistant[exerciseId]; assume exists /account[username]; # implicitly true in OO implementations assume exists /exercise[exerciseId]/group[groupId]; assume not exists /account[username]/tutor[exerciseId]/group[groupId]); assume not exists /exercise[exerciseId]/group[groupId]/tutor[username]; # implied by integrity and the assumption before if not exists /account[username]/tutor[exerciseId] then # we could split the tutor rights here insert /account[username] <tutor exercise=[exerciseId] />; fi insert /account[username]/tutor[exerciseId] <group id=[groupId] />; insert /exercise[exerciseId]/group[groupId] <tutor account=[account] />; }
revokeTutorRights(ident uid, ident username, ident exerciseId, ident groupId) { assume exists /account[uid]/assistant[exerciseId]; assume exists /account[username]/tutor[exerciseId]/group[groupId]; assume exists /exercise[exerciseId]/group[groupId]/tutor[username]; # implied by integrity and the assumption before remove /account[username]/tutor[exerciseId]/group[groupId]; if size(/account[username]/tutor[exerciseId]/group) = 0 then # should this be an additional procedure? remove /account[username]/tutor[exerciseId]; fi remove /exercise[exerciseId]/group[groupId]/tutor[username]; }
These are the associated procedures for Exam Management and Grades, i.e. one of their parameters is an id
of an exam and they will most likely be offered in a corresponding Exam
class in an OO setting. Again, these procedures might be offered in other classes, too.
changeAttributes(ident uid, ident id, string title, string date, string time, string location) { assume exists /account[uid]/admin || exists /account[uid]/examiner[id]; assume exists /exam[id]; # implicitly true in OO implementations update /exam[id]/title title; update /exam[id]/date date; update /exam[id]/time time; update /exam[id]/location location; }
grantExaminerRights(ident uid, ident username, ident examId) { assume exists /account[uid]/admin; assume exists /account[username]; # implicitly true in OO implementations assume exists /exam[examId]; assume not exists /account[username]/examiner[examId]); assume not exists /exam[examId]/examiner[username]); # implied by integrity and the assumption before insert /account[username] <examiner exam=[examId] />; insert /exam[examId] <examiner account=[username] />; }
revokeExaminerRights(ident uid, ident username, ident examId) { assume exists /account[uid]/admin; assume exists /account[username]/examiner[examId]; # implies the account exists, which is implicitly true anyway assume exists /exam[examId]/examiner[username]; # implied by integrity and the assumption before remove /account[username]/examiner[examId]; remove /exam[examId]/examiner[username]; }
Results depend on tasks, so deleting them is impossible, if they are still referenced. If this is really necessary, the host language has to use the deleteResult
procedures first.
createTask(ident uid, ident id, ident taskId, double maxPoints) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; # implicitly true in OO implementations assume not exists /exam[id]/task[taskId]; assume maxPoints >= 0; insert /exam[id] <task id=[taskId] maxPoints=[maxPoints] />; }
deleteTask(ident uid, ident id, ident taskId) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]/task[taskId]; # implies the exam exists, which is implicitly true anyway assume size(/exam[id]/participant/result[taskId]) = 0; remove /exam[id]/task[taskId]; }
The grades only have to be consistent within themselves, nothing depends on them from the outside.
Changing the values of a grade is a method currently present in the implementation, which can be done by removing the grade and adding it. An additional procedure for this might make sense though.
createGrade(ident uid, ident id, ident gradeId, string name, double value, double minPoints) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; # implicitly true in OO implementations assume not exists /exam[id]/grade[gradeId]; assume count(value, /exam[id]/grade/value) = 0; assume count(minPoints, /exam[id]/grade/minPoints) = 0; assume exists /exam[id]/grade[x] && /exam[id]/grade[x]/value < value -> /exam[id]/grade[x]/minPoints > minPoints; assume exists /exam[id]/grade[x] && value < /exam[id]/grade[x]/value -> minPoints > /exam[id]/grade[x]/minPoints; insert /exam[id] <grade id=[gradeId] name=[name] value=[value] minPoints=[minPoints] />; }
deleteGrade(ident uid, ident id, ident gradeId) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]/grade[gradeId]; # implies the exam exists, which is implicitly true anyway remove /exam[id]/grade[gradeId]; }
Currently, the participants are not hardwired to an account, I just changed that fact for the schema. Besides that, participants stand for themselves, so deleting one does not affect anything.
openRegistration(ident uid, ident id) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; assume not /exam[id]/free; update /exam[id]/free true; }
closeRegistration(ident uid, ident id) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; assume /exam[id]/free; update /exam[id]/free false; }
addParticipant(ident uid, ident id, ident studentId) { assume exists /account[uid]/student && /account[uid]/student/id = studentId && /exam[id]/free || exists /account[uid]/examiner[id]; assume exists /exam[id]; # implicitly true in OO implementations assume count (studentId, /account/student/id) = 1; assume not exists /exam[id]/participant[studentId]; insert /exam[id]/participant[studentId]; }
removeParticipant(ident uid, ident id, ident studentId) { assume exists /account[uid]/student && /account[uid]/student/id = studentId && /exam[id]/free || exists /account[uid]/examiner[id]; assume exists /exam[id]/participant[studentId]; # implies the exam exists, which is implicitly true anyway assume size(/exam[id]/participant[studentId]/result) = 0; # don't allow to remove if results are there remove /exam[id]/participant[studentId]; }
Results depend on tasks and have numeric constraints. The procedures can be (and currently are) associated with the Participant
class, rather than the Exam
class. Basically this means we now have two actually implicit parameters.
Again, changing the values can be done by adding and removing, which also invokes most of the checks. One could offer a change Method of course, which can actually make sense, as the precondition will be easier.
addResult(ident uid, ident id, ident studentId, ident taskId, double points) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]/participant[studentId]; # can also be implicitly true in an OO language assume exists /exam[id]/task[taskId]; assume not exists /exam[id]/participant[studentId]/result[taskId]; assume points >= 0 && points <= /exam[id]/task[taskId]/maxPoints; insert /exam[id]/participant[studentId] <result task=[taskId] points=[points] />; }
changeResult(ident uid, ident id, ident studentId, ident taskId, double points) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]/participant[studentId]; assume exists /exam[id]/task[taskId]; assume points >= 0 && points <= /exam[id]/task[taskId]/maxPoints; assume exists /exam[id]/participant[studentId]/result[taskId]; update /exam[id]/participant[studentId]/result[taskId]/points points; }
removeResult(ident uid, ident id, ident studentId, ident taskId) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]/participant[studentId]/result[taskId]; # implies the exam, account, student role and task exist by integrity remove /exam[id]/participant[studentId]/result[taskId]; }
publishResults(ident uid, ident id) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; assume not /exam[id]/published; update /exam[id]/published true; }
hideResults(ident uid, ident id) { assume exists /account[uid]/examiner[id]; assume exists /exam[id]; assume /exam[id]/published; update /exam[id]/published false; }