diff --git a/mod/quiz/doc/databasetables.html b/mod/quiz/doc/databasetables.html
new file mode 100644
index 00000000000..65a413f3d21
--- /dev/null
+++ b/mod/quiz/doc/databasetables.html
@@ -0,0 +1,35 @@
+
+
+ Database tables
+
+
+
+
+
+Database tables
+
+quiz_newest_states
+
+This table exists only for efficiency reasons:
+
+
+ - Via its 'newest' and 'newgraded' fields it gives attempt.php
+ a way to quickly find the newest state
+ and the newest graded state for an attempt. It allows the
+ construction of SQL to select all the states that need to be loaded
+ on attempt.php or review.php.
+ - Via its 'sumpenalty' field it gives quiz_apply_penalty() a quick
+ way for getting at the accummulated penalty that needs to be applied.
+ Without this field the penalties from all previous graded states
+ would have to be added up each time. Not a big deal actually because
+ this could be achieved with a single SQL query (using SUM) but this
+ field was introduced when we still had the multiplicative penalty
+ scheme around which would have been more difficult to recompute.
+
+
+This table was introduced in Moodle 1.5 and is not populated for all
+states during the upgrade because on sites with a lot of existing
+states that could take too long. Rather it is done whenever needed
+by quiz_upgrade_states().
+
+
diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php
index 199e7a13ac9..d8781091312 100644
--- a/mod/quiz/locallib.php
+++ b/mod/quiz/locallib.php
@@ -2525,13 +2525,28 @@ function quiz_get_reviewoptions($quiz, $attempt, $isteacher=false) {
/**
* Upgrade states for an attempt to Moodle 1.5 model
*
+* Any state that does not yet have its timestamp set to nonzero has not yet been upgraded from Moodle 1.4
+* The reason these are still around is that for large sites it would have taken too long to
+* upgrade all states at once. This function sets the timestamp field and creates an entry in the
+* quiz_newest_states table.
* @param object $attempt The attempt whose states need upgrading
*/
function quiz_upgrade_states($attempt) {
global $CFG;
+ // The old quiz model only allowed a single response per quiz attempt so that there will be
+ // only one state record per question for this attempt.
+
+ // We set the timestamp of all states to the timemodified field of the attempt.
execute_sql("UPDATE {$CFG->prefix}quiz_states SET timestamp = '$attempt->timemodified' WHERE attempt = '$attempt->id'", false);
+
+ // For each state we create an entry in the quiz_newest_states table, with both newest and
+ // newgraded pointing to this state.
+ // Actually we only do this for states whose question is actually listed in $attempt->layout.
+ // We do not do it for states associated to wrapped questions like for example the questions
+ // used by a RANDOM question
$newest->attemptid = $attempt->id;
- if ($states = get_records('quiz_states', 'attempt', $attempt->id)) {
+ $questionlist = quiz_questions_in_quiz($attempt->layout);
+ if ($states = get_records_select('quiz_states', "attempt = '$attempt->id' AND question IN ($questionlist)")) {
foreach ($states as $state) {
$newest->newgraded = $state->id;
$newest->newest = $state->id;