OK, I have a first (rubbish, but seems to be working) solution:
Override the following 2 fixture manager functions:
/**
* Prepares the fixtures for the whole test.
* This method is invoked in {@link init}. It executes the database init script
* if it exists. Otherwise, it will load all available fixtures.
*/
public function prepare()
{
$initFile=$this->basePath . DIRECTORY_SEPARATOR . $this->initScript;
$dbconn=$this->getDbConnection();
// Begin transaction and only check constraints at the end
$transaction = $dbconn->beginTransaction();
$dbconn->createCommand("SET CONSTRAINTS ALL DEFERRED")->execute();
//$schema->checkIntegrity(false);
if(is_file($initFile))
require($initFile);
else
{
foreach($this->getFixtures() as $tableName=>$fixturePath)
{
$this->resetTable($tableName);
$this->loadFixture($tableName);
}
}
// Commit transaction
$transaction->commit();
//$schema->checkIntegrity(true);
}
/**
* Loads the specified fixtures.
* For each fixture, the corresponding table will be reset first by calling
* {@link resetTable} and then be populated with the fixture data.
* The loaded fixture data may be later retrieved using {@link getRows}
* and {@link getRecord}.
* Note, if a table does not have fixture data, {@link resetTable} will still
* be called to reset the table.
* @param array fixtures to be loaded. The array keys are fixture names,
* and the array values are either AR class names or table names.
* If table names, they must begin with a colon character (e.g. 'Post'
* means an AR class, while ':Post' means a table name).
*/
public function load($fixtures)
{
$dbconn=$this->getDbConnection();
$schema=$dbconn->getSchema();
// Begin transaction and only check constraints at the end
$transaction = $dbconn->beginTransaction();
$dbconn->createCommand("SET CONSTRAINTS ALL DEFERRED")->execute();
//$schema->checkIntegrity(false);
$this->_rows=array();
$this->_records=array();
foreach($fixtures as $fixtureName=>$tableName)
{
if($tableName[0]===':')
{
$tableName=substr($tableName,1);
unset($modelClass);
}
else
{
$modelClass=Yii::import($tableName,true);
$tableName=CActiveRecord::model($modelClass)->tableName();
if(($prefix=$this->getDbConnection()->tablePrefix)!='')
$tableName=preg_replace('/{{(.*?)}}/',$prefix.'\1',$tableName);
}
$this->resetTable($tableName);
$rows=$this->loadFixture($tableName);
if(is_array($rows) && is_string($fixtureName))
{
$this->_rows[$fixtureName]=$rows;
if(isset($modelClass))
{
foreach(array_keys($rows) as $alias)
$this->_records[$fixtureName][$alias]=$modelClass;
}
}
}
// Commit transaction
$transaction->commit();
//$schema->checkIntegrity(true);
}
The basic difference is that I don't turn on or off integrity checking, instead I wrap the whole thing in a transaction. Immediately after starting the transaction I tell it to defer integrity checking.
I did this by defining a new FixtureManager class in my app/components directory, and telling the test suite to use it instead of CDbFixtureManager.
Problem is, I think this is postgres specific, so by doing this, I have broken the database abstraction layer. Would be good if I could do this in CPgsqlSchema.php instead so that it would continue to work with other databases. It would then be a genuine bug fix rather than a workaround.
Trouble is, using a transaction instead of disabling integrity checking is a completely different approach. Anyone got any ideas?