Index: /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html
===================================================================
--- /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.html	(revision 61474)
@@ -946,5 +946,5 @@
 <li><p class="first">If the testbox was marked as disabled, respond with an IDLE command to the
 testbox [done]. (Note! Must do this after TestBoxStatuses maintainance from
-point 2, or abandond tests won't be cleaned up after a testbox is disabled.)</p>
+point 2, or abandoned tests won't be cleaned up after a testbox is disabled.)</p>
 </li>
 <li><p class="first">Consider testcases in the scheduling queue, pick the first one which the
@@ -1099,6 +1099,6 @@
 <p>TODO</p>
 </div>
-<div class="section" id="cleaning-up-abandond-testcase">
-<h2>#9 - Cleaning Up Abandond Testcase</h2>
+<div class="section" id="cleaning-up-abandoned-testcase">
+<h2>#9 - Cleaning Up Abandoned Testcase</h2>
 <p>This is a subfunction of scenario #1 and #2.  The actions taken are the same in
 both situations.  The precondition for taking this path is that the row in the
@@ -1110,5 +1110,5 @@
 <dd><ol class="first last loweralpha simple">
 <li>Add a message to the root TestResults row, creating one if necesary,
-that explains that the test was abandond.  This is done
+that explains that the test was abandoned.  This is done
 by inserting/finding the string into/in TestResultStrTab and adding
 a row to TestResultMsgs with idStrMsg set to that string id and
@@ -1129,5 +1129,5 @@
 <h2>#10 - Cleaning Up a Disabled/Dead TestBox</h2>
 <p>The UI needs to be able to clean up the remains of a testbox which for some
-reason is out of action.  Normal cleaning up of abandond testcases requires
+reason is out of action.  Normal cleaning up of abandoned testcases requires
 that the testbox signs on or asks for work, but if the testbox is dead or
 in some way indisposed, it won't be doing any of that.  So, the testbox
Index: /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt
===================================================================
--- /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/docs/AutomaticTestingRevamp.txt	(revision 61474)
@@ -635,5 +635,5 @@
 3. If the testbox was marked as disabled, respond with an IDLE command to the
    testbox [done]. (Note! Must do this after TestBoxStatuses maintainance from
-   point 2, or abandond tests won't be cleaned up after a testbox is disabled.)
+   point 2, or abandoned tests won't be cleaned up after a testbox is disabled.)
 
 4. Consider testcases in the scheduling queue, pick the first one which the
@@ -784,6 +784,6 @@
 
 
-#9 - Cleaning Up Abandond Testcase
-----------------------------------
+#9 - Cleaning Up Abandoned Testcase
+-----------------------------------
 
 This is a subfunction of scenario #1 and #2.  The actions taken are the same in
@@ -796,5 +796,5 @@
 1. If the testset is incomplete, we need to completed:
         a) Add a message to the root TestResults row, creating one if necesary,
-           that explains that the test was abandond.  This is done
+           that explains that the test was abandoned.  This is done
            by inserting/finding the string into/in TestResultStrTab and adding
            a row to TestResultMsgs with idStrMsg set to that string id and
@@ -813,5 +813,5 @@
 
 The UI needs to be able to clean up the remains of a testbox which for some
-reason is out of action.  Normal cleaning up of abandond testcases requires
+reason is out of action.  Normal cleaning up of abandoned testcases requires
 that the testbox signs on or asks for work, but if the testbox is dead or
 in some way indisposed, it won't be doing any of that.  So, the testbox
Index: /trunk/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/batch/close_orphaned_testsets.py	(revision 61474)
@@ -72,9 +72,9 @@
             # Complete them.
             if self.oConfig.fJustDoIt:
-                print 'Completing %u test sets as abandond:' % (len(aoOrphans),);
+                print 'Completing %u test sets as abandoned:' % (len(aoOrphans),);
                 for oTestSet in aoOrphans:
                     print '#%-7u: idTestBox=%-3u tsCreated=%s tsDone=%s' \
                         % (oTestSet.idTestSet, oTestSet.idTestBox, oTestSet.tsCreated, oTestSet.tsDone);
-                    oLogic.completeAsAbandond(oTestSet.idTestSet);
+                    oLogic.completeAsAbandoned(oTestSet.idTestSet);
                 print 'Committing...';
                 oDb.commit();
Index: /trunk/src/VBox/ValidationKit/testmanager/core/schedulerbase.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/schedulerbase.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/schedulerbase.py	(revision 61474)
@@ -884,4 +884,5 @@
                           '             idGenTestBox,\n'
                           '             idTestBox,\n'
+                          '             idSchedGroup,\n'
                           '             idTestGroup,\n'
                           '             idGenTestCase,\n'
@@ -900,4 +901,5 @@
                           '             %s,\n'      # idGenTestBox
                           '             %s,\n'      # idTestBox
+                          '             %s,\n'      # idSchedGroup
                           '             %s,\n'      # idTestGroup
                           '             %s,\n'      # idGenTestCase
@@ -916,4 +918,5 @@
                               oTestBoxData.idGenTestBox,
                               oTestBoxData.idTestBox,
+                              oTestBoxData.idSchedGroup,
                               oTask.idTestGroup,
                               oTestEx.oTestCase.idGenTestCase,
@@ -1044,9 +1047,8 @@
                 fDecision = oEntry.getPreReqDecision(sPreReqSet);
                 if fDecision is None:
-                    ## @todo DB Tuning
                     # Check for missing prereqs.
                     self._oDb.execute('SELECT   COUNT(*)\n'
                                       'FROM     (VALUES ' + sPreReqSet + ') AS PreReqs(idTestCase)\n'
-                                      'LEFT OUTER JOIN (SELECT  *\n'
+                                      'LEFT OUTER JOIN (SELECT  idTestSet\n'
                                       '                 FROM    TestSets\n'
                                       '                 WHERE   enmStatus IN (%s, %s)\n'
Index: /trunk/src/VBox/ValidationKit/testmanager/core/systemlog.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/systemlog.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/systemlog.py	(revision 61474)
@@ -46,5 +46,5 @@
     ksEvent_CmdNacked           = 'CmdNack ';
     ksEvent_TestBoxUnknown      = 'TBoxUnkn';
-    ksEvent_TestSetAbandond     = 'TSetAbdd';
+    ksEvent_TestSetAbandoned    = 'TSetAbdd';
     ksEvent_UserAccountUnknown  = 'TAccUnkn';
     ksEvent_XmlResultMalformed  = 'XmlRMalf';
@@ -57,5 +57,5 @@
         ksEvent_CmdNacked,
         ksEvent_TestBoxUnknown,
-        ksEvent_TestSetAbandond,
+        ksEvent_TestSetAbandoned,
         ksEvent_UserAccountUnknown,
         ksEvent_XmlResultMalformed,
Index: /trunk/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/testboxcontroller.py	(revision 61474)
@@ -273,17 +273,17 @@
         Cleans up any old test set that may be left behind and changes the
         state to 'idle'.  See scenario #9:
-        file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandond-testcase
+        file://../../docs/AutomaticTestingRevamp.html#cleaning-up-abandoned-testcase
 
         Note. oStatusData.enmState is set to idle, but tsUpdated is not changed.
         """
 
-        # Cleanup any abandond test.
+        # Cleanup any abandoned test.
         if oStatusData.idTestSet is not None:
-            SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandond,
+            SystemLogLogic(oDb).addEntry(SystemLogData.ksEvent_TestSetAbandoned,
                                          "idTestSet=%u idTestBox=%u enmState=%s %s"
                                          % (oStatusData.idTestSet, oStatusData.idTestBox,
                                             oStatusData.enmState, self._sAction),
                                          fCommit = False);
-            TestSetLogic(oDb).completeAsAbandond(oStatusData.idTestSet, fCommit = False);
+            TestSetLogic(oDb).completeAsAbandoned(oStatusData.idTestSet, fCommit = False);
             GlobalResourceLogic(oDb).freeGlobalResourcesByTestBox(self._idTestBox, fCommit = False);
 
Index: /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/testresults.py	(revision 61474)
@@ -799,43 +799,5 @@
             { ksResultsSortByBuildRevision: ( '', None,  ' Builds.iRevision DESC' ), }
         ),
-        ksResultsGroupingTypeSchedGroup: (
-            ', TestBoxesWithStrings',
-            'TestBoxesWithStrings.idSchedGroup',
-            ' AND TestSets.idGenTestBox = TestBoxesWithStrings.idGenTestBox',
-            {
-
-              ksResultsSortByTestBoxName: (
-                  # Sorting tables.
-                  '',
-                  # Sorting table join(s).
-                  None,
-                  # Start of ORDER BY statement.
-                  ' TestBoxesWithStrings.sName DESC',
-                  # Extra columns to fetch for the above ORDER BY to work in a SELECT DISTINCT statement.
-                  '',
-                  # Columns for the GROUP BY
-                  '' ),
-              ksResultsSortByTestBoxOsArch:     ( '', None, ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sCpuArch', '', '' ),
-              ksResultsSortByTestBoxOs:         ( '', None, ' TestBoxesWithStrings.sOs', '', ''  ),
-              ksResultsSortByTestBoxOsVersion:  ( '', None, ' TestBoxesWithStrings.sOs, TestBoxesWithStrings.sOsVersion DESC',
-                                                  '', '' ),
-              ksResultsSortByTestBoxArch:       ( '', None, ' TestBoxesWithStrings.sCpuArch', '', ''  ),
-              ksResultsSortByTestBoxCpuVendor:  ( '', None, ' TestBoxesWithStrings.sCpuVendor', '', ''  ),
-              ksResultsSortByTestBoxCpuName:    ( '', None, ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.sCpuName',
-                                                  '', '' ),
-              ksResultsSortByTestBoxCpuRev: (
-                  '',
-                  None,
-                  ' TestBoxesWithStrings.sCpuVendor, TestBoxesWithStrings.lCpuRevision DESC',
-                  ', TestBoxesWithStrings.lCpuRevision',
-                  ', TestBoxesWithStrings.lCpuRevision' ),
-              ksResultsSortByTestBoxCpuFeatures: (
-                  '',
-                  None,
-                  ' TestBoxesWithStrings.fCpuHwVirt DESC, TestBoxesWithStrings.fCpuNestedPaging DESC, '
-                  +'TestBoxesWithStrings.fCpu64BitGuest DESC, TestBoxesWithStrings.cCpus DESC',
-                  '',
-                  '' ), }
-        ),
+        ksResultsGroupingTypeSchedGroup: ( '', 'TestSets.idSchedGroup',   None,                      {},),
     };
 
@@ -1228,10 +1190,8 @@
 
         self._oDb.execute('SELECT SchedGroups.*\n'
-                          'FROM   ( SELECT TestBoxes.idSchedGroup  AS idSchedGroup,\n'
+                          'FROM   ( SELECT idSchedGroup,\n'
                           '                MAX(TestSets.tsCreated) AS tsNow\n'
-                          '         FROM   TestSets,\n'
-                          '                TestBoxes\n'
-                          '         WHERE  TestSets.idGenTestBox = TestBoxes.idGenTestBox\n'
-                          '            AND ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '         ') +
+                          '         FROM   TestSets\n'
+                          '         WHERE  ' + self._getTimePeriodQueryPart(tsNow, sPeriod, '         ') +
                           '         GROUP BY idSchedGroup\n'
                           '       ) AS SchedGroupIDs\n'
Index: /trunk/src/VBox/ValidationKit/testmanager/core/testset.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/core/testset.py	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/core/testset.py	(revision 61474)
@@ -79,4 +79,5 @@
     ksParam_idGenTestBox        = 'TestSet_idGenTestBox';
     ksParam_idTestBox           = 'TestSet_idTestBox';
+    ksParam_idSchedGroup        = 'TestSet_idSchedGroup';
     ksParam_idTestGroup         = 'TestSet_idTestGroup';
     ksParam_idGenTestCase       = 'TestSet_idGenTestCase';
@@ -89,5 +90,5 @@
     ksParam_idTestSetGangLeader = 'TestSet_idTestSetGangLeader';
 
-    kasAllowNullAttributes      = ['tsDone', 'idBuildTestSuite', 'idTestSetGangLeader' ];
+    kasAllowNullAttributes      = [ 'tsDone', 'idBuildTestSuite', 'idTestSetGangLeader' ];
     kasValidValues_enmStatus    = [
         ksTestStatus_Running,
@@ -104,4 +105,6 @@
 
 
+    kcDbColumns                 = 20;
+
     def __init__(self):
         ModelDataBase.__init__(self);
@@ -121,4 +124,5 @@
         self.idGenTestBox           = None;
         self.idTestBox              = None;
+        self.idSchedGroup           = None;
         self.idTestGroup            = None;
         self.idGenTestCase          = None;
@@ -150,13 +154,14 @@
         self.idGenTestBox           = aoRow[8];
         self.idTestBox              = aoRow[9];
-        self.idTestGroup            = aoRow[10];
-        self.idGenTestCase          = aoRow[11];
-        self.idTestCase             = aoRow[12];
-        self.idGenTestCaseArgs      = aoRow[13];
-        self.idTestCaseArgs         = aoRow[14];
-        self.idTestResult           = aoRow[15];
-        self.sBaseFilename          = aoRow[16];
-        self.iGangMemberNo          = aoRow[17];
-        self.idTestSetGangLeader    = aoRow[18];
+        self.idSchedGroup           = aoRow[10];
+        self.idTestGroup            = aoRow[11];
+        self.idGenTestCase          = aoRow[12];
+        self.idTestCase             = aoRow[13];
+        self.idGenTestCaseArgs      = aoRow[14];
+        self.idTestCaseArgs         = aoRow[15];
+        self.idTestResult           = aoRow[16];
+        self.sBaseFilename          = aoRow[17];
+        self.iGangMemberNo          = aoRow[18];
+        self.idTestSetGangLeader    = aoRow[19];
         return self;
 
@@ -443,5 +448,5 @@
         return oData.idTestSetGangLeader;
 
-    def completeAsAbandond(self, idTestSet, fCommit = False):
+    def completeAsAbandoned(self, idTestSet, fCommit = False):
         """
         Completes the testset as abandoned if necessary.
Index: /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql	(revision 61473)
+++ /trunk/src/VBox/ValidationKit/testmanager/db/TestManagerDatabaseInit.pgsql	(revision 61474)
@@ -59,6 +59,6 @@
 --                  AND sEvent = 'TBoxUnkn'
 --                  AND sLogText = :sNewLogText;
---      - When cleaning up an abandond testcase (scenario #9), log which
---        testbox abandond which testset.
+--      - When cleaning up an abandoned testcase (scenario #9), log which
+--        testbox abandoned which testset.
 --
 -- The Web UI will have some way of displaying the log.
@@ -1605,4 +1605,7 @@
     -- Non-unique foreign key: TestBoxes(idTestBox)
     idTestBox           INTEGER     NOT NULL,
+    --- The scheduling group ID the test was scheduled thru (valid: tsStarted).
+    -- Non-unique foreign key: SchedGroups(idSchedGroup)
+    idSchedGroup        INTEGER     NOT NULL,
 
     --- The testgroup (valid: tsConfig).
@@ -1664,9 +1667,9 @@
 CREATE INDEX TestSetsGraphBoxIdx    ON TestSets (idTestBox, tsCreated DESC, tsDone ASC NULLS LAST, idBuildCategory, idTestCase);
 
-ALTER TABLE TestResults      ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
-ALTER TABLE TestResultValues ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
-ALTER TABLE TestResultFiles  ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
-ALTER TABLE TestResultMsgs   ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
-ALTER TABLE TestResultFailures ADD CONSTRAINT idTestSetFk FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResults        ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultValues   ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultFiles    ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultMsgs     ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultFailures ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
 
 
Index: /trunk/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql	(revision 61474)
+++ /trunk/src/VBox/ValidationKit/testmanager/db/tmdb-r21-testsets-4.pgsql	(revision 61474)
@@ -0,0 +1,275 @@
+-- $Id$
+--- @file
+-- VBox Test Manager Database - Adds an idSchedGroup to TestSets in
+-- preparation for testboxes belonging to multiple scheduling queues.
+--
+
+--
+-- Copyright (C) 2013-2016 Oracle Corporation
+--
+-- This file is part of VirtualBox Open Source Edition (OSE), as
+-- available from http://www.virtualbox.org. This file is free software;
+-- you can redistribute it and/or modify it under the terms of the GNU
+-- General Public License (GPL) as published by the Free Software
+-- Foundation, in version 2 as it comes in the "COPYING" file of the
+-- VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+-- hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+--
+-- The contents of this file may alternatively be used under the terms
+-- of the Common Development and Distribution License Version 1.0
+-- (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+-- VirtualBox OSE distribution, in which case the provisions of the
+-- CDDL are applicable instead of those of the GPL.
+--
+-- You may elect to license modified versions of this file under the
+-- terms and conditions of either the GPL or the CDDL or both.
+--
+
+--
+-- Cleanup after failed runs.
+--
+DROP TABLE IF EXISTS OldTestSets;
+
+--
+-- Die on error from now on.
+--
+\set ON_ERROR_STOP 1
+\set AUTOCOMMIT 0
+
+
+-- Total grid lock (don't want to deadlock below).
+LOCK TABLE TestBoxStatuses      IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestSets             IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestBoxes            IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestResults          IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestResultFailures   IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestResultFiles      IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestResultMsgs       IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE TestResultValues     IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE SchedGroups          IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE SchedQueues          IN ACCESS EXCLUSIVE MODE;
+LOCK TABLE SchedGroupMembers    IN ACCESS EXCLUSIVE MODE;
+
+\d+ TestSets;
+
+--
+-- Rename the table, drop foreign keys refering to it, and drop constrains
+-- within the table itself.  The latter is mostly for naming and we do it
+-- up front in case the database we're running against has different names
+-- due to previous conversions.
+--
+ALTER TABLE TestSets RENAME TO OldTestSets;
+
+ALTER TABLE TestResultFailures  DROP CONSTRAINT IF EXISTS idtestsetfk;
+ALTER TABLE TestResultFailures  DROP CONSTRAINT IF EXISTS TestResultFailures_idTestSet_fkey;
+ALTER TABLE SchedQueues         DROP CONSTRAINT IF EXISTS SchedQueues_idTestSetGangLeader_fkey;
+ALTER TABLE TestBoxStatuses     DROP CONSTRAINT IF EXISTS TestBoxStatuses_idTestSet_fkey;
+ALTER TABLE TestResultFiles     DROP CONSTRAINT IF EXISTS TestResultFiles_idTestSet_fkey;
+ALTER TABLE TestResultMsgs      DROP CONSTRAINT IF EXISTS TestResultMsgs_idTestSet_fkey;
+ALTER TABLE TestResults         DROP CONSTRAINT IF EXISTS TestResults_idTestSet_fkey;
+ALTER TABLE TestResultValues    DROP CONSTRAINT IF EXISTS TestResultValues_idTestSet_fkey;
+
+ALTER TABLE OldTestSets     DROP CONSTRAINT testsets_igangmemberno_check;
+
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idBuildCategory_fkey;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idGenTestBox_fkey;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idGenTestCase_fkey;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idGenTestCaseArgs_fkey;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idTestResult_fkey;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_idTestSetGangLeader_fkey;
+
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_sBaseFilename_key;
+ALTER TABLE OldTestSets     DROP CONSTRAINT TestSets_pkey;
+
+DROP INDEX IF EXISTS TestSetsGangIdx;
+DROP INDEX IF EXISTS TestSetsBoxIdx;
+DROP INDEX IF EXISTS TestSetsBuildIdx;
+DROP INDEX IF EXISTS TestSetsTestCaseIdx;
+DROP INDEX IF EXISTS TestSetsTestVarIdx;
+DROP INDEX IF EXISTS TestSetsDoneCreatedBuildCatIdx;
+DROP INDEX IF EXISTS TestSetsGraphBoxIdx;
+
+
+-- This output should be free of indexes, constraints and references from other tables.
+\d+ OldTestSets;
+
+\prompt "Is the above table completely free of indexes, constraints and references? Ctrl-C if not."  dummy
+
+--
+-- Create the new table (no foreign keys).
+--
+CREATE TABLE TestSets (
+    --- The ID of this test set.
+    idTestSet           INTEGER   DEFAULT NEXTVAL('TestSetIdSeq')  NOT NULL,
+
+    --- The test config timestamp, used when reading test config.
+    tsConfig            TIMESTAMP WITH TIME ZONE  DEFAULT CURRENT_TIMESTAMP  NOT NULL,
+    --- When this test set was scheduled.
+    -- idGenTestBox is valid at this point.
+    tsCreated           TIMESTAMP WITH TIME ZONE  DEFAULT CURRENT_TIMESTAMP  NOT NULL,
+    --- When this test completed, i.e. testing stopped.  This should only be set once.
+    tsDone              TIMESTAMP WITH TIME ZONE  DEFAULT NULL,
+    --- The current status.
+    enmStatus           TestStatus_T  DEFAULT 'running'::TestStatus_T  NOT NULL,
+
+    --- The build we're testing.
+    -- Non-unique foreign key: Builds(idBuild)
+    idBuild             INTEGER     NOT NULL,
+    --- The build category of idBuild when the test started.
+    -- This is for speeding up graph data collection, i.e. avoid idBuild
+    -- the WHERE part of the selection.
+    idBuildCategory     INTEGER      NOT NULL,
+    --- The test suite build we're using to do the testing.
+    -- This is NULL if the test suite zip wasn't referred or if a test suite
+    -- build source wasn't configured.
+    -- Non-unique foreign key: Builds(idBuild)
+    idBuildTestSuite    INTEGER     DEFAULT NULL,
+
+    --- The exact testbox configuration.
+    idGenTestBox        INTEGER     NOT NULL,
+    --- The testbox ID for joining with (valid: tsStarted).
+    -- Non-unique foreign key: TestBoxes(idTestBox)
+    idTestBox           INTEGER     NOT NULL,
+    --- The scheduling group ID the test was scheduled thru (valid: tsStarted).
+    -- Non-unique foreign key: SchedGroups(idSchedGroup)
+    idSchedGroup        INTEGER     NOT NULL,
+
+    --- The testgroup (valid: tsConfig).
+    -- Non-unique foreign key: TestBoxes(idTestGroup)
+    -- Note! This also gives the member ship entry, since a testcase can only
+    --       have one membership per test group.
+    idTestGroup         INTEGER     NOT NULL,
+
+    --- The exact test case config we executed in this test run.
+    idGenTestCase       INTEGER     NOT NULL,
+    --- The test case ID for joining with (valid: tsConfig).
+    -- Non-unique foreign key: TestBoxes(idTestCase)
+    idTestCase          INTEGER     NOT NULL,
+
+    --- The arguments (and requirements++) we executed this test case with.
+    idGenTestCaseArgs   INTEGER     NOT NULL,
+    --- The argument variation ID (valid: tsConfig).
+    -- Non-unique foreign key: TestCaseArgs(idTestCaseArgs)
+    idTestCaseArgs      INTEGER     NOT NULL,
+
+    --- The root of the test result tree.
+    -- @note This will only be NULL early in the transaction setting up the testset.
+    -- @note If the test reports more than one top level test result, we'll
+    --       fail the whole test run and let the test developer fix it.
+    idTestResult        INTEGER     DEFAULT NULL,
+
+    --- The base filename used for storing files related to this test set.
+    -- This is a path relative to wherever TM is dumping log files.  In order
+    -- to not become a file system test case, we will try not to put too many
+    -- hundred thousand files in a directory.  A simple first approach would
+    -- be to just use the current date (tsCreated) like this:
+    --    TM_FILE_DIR/year/month/day/TestSets.idTestSet
+    --
+    -- The primary log file for the test is this name suffixed by '.log'.
+    --
+    -- The files in the testresultfile table gets their full names like this:
+    --    TM_FILE_DIR/sBaseFilename-testresultfile.id-TestResultStrTab(testresultfile.idStrFilename)
+    --
+    -- @remarks We store this explicitly in case we change the directly layout
+    --          at some later point.
+    sBaseFilename       text        NOT NULL,
+
+    --- The gang member number number, 0 is the leader.
+    iGangMemberNo       SMALLINT    DEFAULT 0  NOT NULL, --  CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024),
+    --- The test set of the gang leader, NULL if no gang involved.
+    -- @note This is set by the gang leader as well, so that we can find all
+    --       gang members by WHERE idTestSetGangLeader = :id.
+    idTestSetGangLeader INTEGER     DEFAULT NULL
+
+);
+
+-- Convert the data.
+INSERT INTO TestSets (
+            idTestSet,
+            tsConfig,
+            tsCreated,
+            tsDone,
+            enmStatus,
+            idBuild,
+            idBuildCategory,
+            idBuildTestSuite,
+            idGenTestBox,
+            idTestBox,
+            idSchedGroup,
+            idTestGroup,
+            idGenTestCase,
+            idTestCase,
+            idGenTestCaseArgs,
+            idTestCaseArgs,
+            idTestResult,
+            sBaseFilename,
+            iGangMemberNo,
+            idTestSetGangLeader
+            )
+SELECT      OldTestSets.idTestSet,
+            OldTestSets.tsConfig,
+            OldTestSets.tsCreated,
+            OldTestSets.tsDone,
+            OldTestSets.enmStatus,
+            OldTestSets.idBuild,
+            OldTestSets.idBuildCategory,
+            OldTestSets.idBuildTestSuite,
+            OldTestSets.idGenTestBox,
+            OldTestSets.idTestBox,
+            TestBoxes.idSchedGroup,
+            OldTestSets.idTestGroup,
+            OldTestSets.idGenTestCase,
+            OldTestSets.idTestCase,
+            OldTestSets.idGenTestCaseArgs,
+            OldTestSets.idTestCaseArgs,
+            OldTestSets.idTestResult,
+            OldTestSets.sBaseFilename,
+            OldTestSets.iGangMemberNo,
+            OldTestSets.idTestSetGangLeader
+FROM        OldTestSets
+            INNER JOIN TestBoxes
+                    ON OldTestSets.idGenTestBox = TestBoxes.idGenTestBox;
+
+-- Restore the primary key and unique constraints.
+ALTER TABLE TestSets ADD PRIMARY KEY (idTestSet);
+ALTER TABLE TestSets ADD UNIQUE (sBaseFilename);
+
+-- Restore check constraints.
+ALTER TABLE TestSets ADD CONSTRAINT TestSets_iGangMemberNo_Check CHECK (iGangMemberNo >= 0 AND iGangMemberNo < 1024);
+
+-- Restore foreign keys in the table.
+ALTER TABLE TestSets            ADD FOREIGN KEY (idBuildCategory)     REFERENCES BuildCategories(idBuildCategory);
+ALTER TABLE TestSets            ADD FOREIGN KEY (idGenTestBox)        REFERENCES TestBoxes(idGenTestBox);
+ALTER TABLE TestSets            ADD FOREIGN KEY (idGenTestCase)       REFERENCES TestCases(idGenTestCase);
+ALTER TABLE TestSets            ADD FOREIGN KEY (idGenTestCaseArgs)   REFERENCES TestCaseArgs(idGenTestCaseArgs);
+ALTER TABLE TestSets            ADD FOREIGN KEY (idTestResult)        REFERENCES TestResults(idTestResult);
+ALTER TABLE TestSets            ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet);
+
+-- Restore indexes.
+CREATE INDEX TestSetsGangIdx        ON TestSets (idTestSetGangLeader);
+CREATE INDEX TestSetsBoxIdx         ON TestSets (idTestBox, idTestResult);
+CREATE INDEX TestSetsBuildIdx       ON TestSets (idBuild, idTestResult);
+CREATE INDEX TestSetsTestCaseIdx    ON TestSets (idTestCase, idTestResult);
+CREATE INDEX TestSetsTestVarIdx     ON TestSets (idTestCaseArgs, idTestResult);
+CREATE INDEX TestSetsDoneCreatedBuildCatIdx ON TestSets (tsDone DESC NULLS FIRST, tsCreated ASC, idBuildCategory);
+CREATE INDEX TestSetsGraphBoxIdx    ON TestSets (idTestBox, tsCreated DESC, tsDone ASC NULLS LAST, idBuildCategory, idTestCase);
+
+-- Restore foreign key references to the table.
+ALTER TABLE TestResults         ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultValues    ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultFiles     ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultMsgs      ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE TestResultFailures  ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+
+ALTER TABLE TestBoxStatuses     ADD FOREIGN KEY (idTestSet) REFERENCES TestSets(idTestSet) MATCH FULL;
+ALTER TABLE SchedQueues         ADD FOREIGN KEY (idTestSetGangLeader) REFERENCES TestSets(idTestSet) MATCH FULL;
+
+-- Drop the old table.
+DROP TABLE OldTestSets;
+
+\prompt "Update python files while everything is locked. Hurry!"  dummy
+
+COMMIT;
+
+\d TestSets;
+
