Transaction Is Not Working

Hello!

I am using a transaction in my action:


public function actionCreate()

    {

        $model_1 = new Model_1('somescenario');

        $model_2 = new Model_2('somescenario2');

        $model_3 = new Model_3();

        $model_4 = new Model_4('somescenario3');

        $model_5 = new Model_5();


        $transaction = $model_1->getDbConnection()->beginTransaction();


        try

        {

            if(isset($_POST['Model_1'], $_POST['Model_3'], $_POST['Model_3'], 

                    $_POST['Model_4']))

            {

                $model_1->attributes = $_POST['Model_1'];

                $model_2->attributes = $_POST['Model_2'];

                $model_3->attributes = $_POST['Model_3'];

                $model_4->attributes = $_POST['Model_4'];

                

                $validate = $model_1->validate();

                $validate = $model_2->validate() && $validate;

                $validate = $model_3->validate() && $validate;                     

                $validate = $model_4->validate() && $validate;

                $validate = $model_5->validate() && $validate;

                

                if($model_1->save())

                {

                    $model_4->id_tb_1 = $model_1->id;

                    $model_2->id_tb_1 = $model_1->id;

                    $model_3->id_tb_1 = $model_1->id;


                    if($model_4->save() && $model_2->save() && $model_3->save())

                    {                     

                        if(isset($_POST['model_5']))

                        {                       

                            foreach($_POST['model_5']

                                    ['id'] as $id)

                            {                             

                                $model_6 = new Model_6();

                                $model_6->id = $id;

                                $model_6->id = $model_1->model_1_relation->id;

                                $model_6->save();

                            }

                        }

                        $model_2->createDatabaseRole($model_2->username);

                        $transaction->commit();

                        $this->redirect(array('userIndex'));

                    }

                    else

                        throw new CHttpException(404, 

                                Yii::t('message', 'The specified model cannot be saved')); 

                }          

                else

                    throw new CHttpException(404, 

                        Yii::t('message', 'The specified model cannot be saved')); 

            }  

            else

                throw new CHttpException(404, 

                    Yii::t('message', 'The specified POST cannot be found')); 

        }

        

        catch(Exception $e)

        {

            $transaction->rollback();

        }

    }

But if I generate an exception after $model_1->save(), [color="#FF0000"]the data from the $model_1 still in the database[/color]. I generated an exception and looked at the database log. It says that the transaction started and rolled back:




LOG:  statement: BEGIN

LOG:  statement: SET NAMES 'utf8'

LOG:  statement: SELECT 1 FROM "tb_2" "t" WHERE "t"."username"='thejames' LIMIT 1

LOG:  statement: SET NAMES 'utf8'

LOG:  statement: INSERT INTO "tb_1" ("...", "...", "...", "...", "...", "...", "...") VALUES ('The James', 'The James Bond', NULL, NULL, NULL, NULL, NULL)

LOG:  execute <unnamed>: SELECT CURRVAL($1)

DETAIL:  parameters: $1 = 'seq_tb_1'

LOG:  statement: SET NAMES 'utf8'

LOG:  statement: INSERT INTO "tb_4" ("...", "...", "...", "...", "...", "...", "...", "id") VALUES (NULL, '..', NULL, NULL, NULL, NULL, NULL, 0)

ERROR:  insert or update on table "tb_4" violates foreign key constraint "tb_4_id_fkey"

DETAIL:  Key (id)=(0) is not present in table "tb_1".

STATEMENT:  INSERT INTO "tb_4" ("...", "...", "...", "...", "...", "...", "...", "id_1") VALUES (NULL, 'SP', NULL, NULL, NULL, NULL, NULL, 0)

LOG:  statement: ROLLBACK

I’m using PostgreSQL 9.2.4 and Yii framework 1.1.13

This looks really strange. PostgreSQL will do an automatic rollback when generating such error. Are you sure you didn’t save the model before?

Also, when calling save() without any arguments it calls validate() before actually saving, so you want to pass ‘false’ to it to avoid double validation.

At WebLogRouter, the insertion of the Model_1 is starting inside the transaction block:





12:24:02.860594	trace	system.db.CDbConnection	

Starting transaction

in /var/www/drmais/protected/controllers/C1Controller.php (59)

in /var/www/drmais/index.php (13)


12:24:02.860697	trace	system.db.CDbConnection	

Opening DB connection

in /var/www/drmais/protected/controllers/C1Controller.php (59)

in /var/www/drmais/index.php (13)


12:24:02.886655	trace	system.CModule	

Loading "messages" application component

in /var/www/drmais/protected/models/Model_1.php (75)

in /var/www/drmais/protected/controllers/C1Controller.php (66)

in /var/www/drmais/index.php (13)


12:24:02.899287	trace	system.db.ar.CActiveRecord	

User.exists()

in /var/www/drmais/protected/controllers/C1Controller.php (72)

in /var/www/drmais/index.php (13)


12:24:02.901856	trace	system.db.CDbConnection	

Opening DB connection

in /var/www/drmais/protected/controllers/C1Controller.php (72)

in /var/www/drmais/index.php (13)


12:24:02.927841	trace	system.db.CDbCommand	

Querying SQL: SELECT 1 FROM "tb_user" "t" WHERE "t"."username"=:ycp0 LIMIT

1

in /var/www/drmais/protected/controllers/C1Controller.php (72)

in /var/www/drmais/index.php (13)


12:24:02.932885	trace	system.CModule	

Loading "coreMessages" application component

in /var/www/drmais/protected/controllers/C1Controller.php (73)

in /var/www/drmais/index.php (13)


12:24:02.940749	trace	system.db.ar.CActiveRecord	

Model_1.insert()

in /var/www/drmais/protected/controllers/C1Controller.php (77)

in /var/www/drmais/index.php (13)


12:24:02.941178	trace	system.db.CDbConnection	

Opening DB connection

in /var/www/drmais/protected/controllers/C1Controller.php (77)

in /var/www/drmais/index.php (13)


12:24:02.966751	trace	system.db.CDbCommand	

Executing SQL: INSERT INTO "tb_1" ("...", "...",

"...", "...", "...", "...", "...")

VALUES (:yp0, :yp1, :yp2, :yp3, :yp4, :yp5, :yp6)

in /var/www/drmais/protected/controllers/C1Controller.php (77)

in /var/www/drmais/index.php (13)


12:24:03.27409	trace	system.db.ar.CActiveRecord	

Model_4.insert()

in /var/www/drmais/protected/controllers/C1Controller.php (84)

in /var/www/drmais/index.php (13)


12:24:03.28028	trace	system.db.CDbConnection	

Opening DB connection

in /var/www/drmais/protected/controllers/C1Controller.php (84)

in /var/www/drmais/index.php (13)


12:24:03.55090	trace	system.db.CDbCommand	

Executing SQL: INSERT INTO "tb_4" ("...", "...", "...",

"...", "...", "...", "...", "...") VALUES

(:yp0, :yp1, :yp2, :yp3, :yp4, :yp5, :yp6, :yp7)

in /var/www/drmais/protected/controllers/C1Controller.php (84)

in /var/www/drmais/index.php (13)


12:24:03.68584	error	system.db.CDbCommand SQLSTATE[23503]: Foreign key

violation: 7 ERROR:  insert or update on table "tb_4" violates

foreign key constraint "tb_4_id_fkey"

DETAIL:  Key (id)=(0) is not present in table "tb_1".. : INSERT INTO "tb_4" ("...",

"...", "...", "...", "...", "...", "...",

"id_tb_1") VALUES (:yp0, :yp1, :yp2, :yp3, :yp4, :yp5, :yp6, :yp7).

in /var/www/drmais/protected/controllers/C1Controller.php (84)

in /var/www/drmais/index.php (13)


12:24:03.84001	trace	system.db.CDbTransaction	

Rolling back transaction

in /var/www/drmais/protected/controllers/C1Controller.php (116)

in /var/www/drmais/index.php (13)




Also the database log reports ONLY one insertion.

About the parameter on function save, thanks for you advice.

The problem appear to be the multiple connections been opened, as there are multiple models involved in this action. Richard Huxton from Stack Overflow said that I should enable PID for each entry in PostgreSQL log and in fact the PID of BEGIN/COMMIT and INSERT statments are different. Thats why the data still in database.

stackoverflow dot com/q/18859261/2789172

So, after that, is there a way to make it work using Yii?

ActiveRecord uses only the same connection through whole single request processing so you must have some kind of middleware installed that creates a pool of connections and sends each query through a different connection.

If you can’t disable that feature or get a direct database connection you can’t use transactions.

That’s pretty weird because I’ve used pgPool II and pgBouncer and they don’t behave like this. Maybe someone configured them this way explicitly.

I’m not using a middleware. All I did related to database was that:

http://www.yiiframework.com/wiki/123/multiple-database-support-in-yii/

Are all your models in the same database? You start and commit/rollback the transaction on the same connection, so at least model1 should not be there after rolling back.

Yes. They are in the same database.

So why do you use multiple connections in your AR model classes?

I’ve overridden getDbConnection() on every model to connect to database using different user, so I can make use of that for audit.

Basically, if the user is authenticated, getDbConnection will return a connection with especific username. Else, return a default connection Yii::app()->db;

If you have a superuser account in your database you can issue a SET ROLE query to change the current user. Then you wouldn’t have to switch connections and avoid problems with transactions spanning multiple connections.

Just make sure to reset the user on each request.

I’ll undo all overridden getDbConnection and test if the problem persists. I’ll feedback tomorrow.

Thank you