Skip to content

Commit 041f8ee

Browse files
committed
Merge pull request #94 from AlexeyDsov/innerTransaction
InnerTransaction and savepoints
2 parents de8cb7f + d0063a7 commit 041f8ee

File tree

6 files changed

+583
-1
lines changed

6 files changed

+583
-1
lines changed

core/DB/DB.class.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ abstract class DB
3535
* flag to indicate whether we're in transaction
3636
**/
3737
private $transaction = false;
38+
/**
39+
* @var list of all started savepoints
40+
*/
41+
private $savepointList = array();
3842

3943
private $queue = array();
4044
private $toQueue = false;
@@ -140,6 +144,7 @@ public function commit()
140144
$this->queryRaw("commit;\n");
141145

142146
$this->transaction = false;
147+
$this->savepointList = array();
143148

144149
return $this;
145150
}
@@ -155,6 +160,7 @@ public function rollback()
155160
$this->queryRaw("rollback;\n");
156161

157162
$this->transaction = false;
163+
$this->savepointList = array();
158164

159165
return $this;
160166
}
@@ -222,6 +228,69 @@ public function isQueueActive()
222228
}
223229
//@}
224230

231+
/**
232+
* @param string $savepointName
233+
* @return DB
234+
*/
235+
public function savepointBegin($savepointName)
236+
{
237+
$this->assertSavePointName($savepointName);
238+
if (!$this->inTransaction())
239+
throw new DatabaseException('To use savepoint begin transaction first');
240+
241+
$query = 'savepoint '.$savepointName;
242+
if ($this->toQueue)
243+
$this->queue[] = $query;
244+
else
245+
$this->queryRaw("{$query};\n");
246+
247+
return $this->addSavepoint($savepointName);
248+
}
249+
250+
/**
251+
* @param string $savepointName
252+
* @return DB
253+
*/
254+
public function savepointRelease($savepointName)
255+
{
256+
$this->assertSavePointName($savepointName);
257+
if (!$this->inTransaction())
258+
throw new DatabaseException('To release savepoint need first begin transaction');
259+
260+
if (!$this->checkSavepointExist($savepointName))
261+
throw new DatabaseException("savepoint with name '{$savepointName}' nor registered");
262+
263+
$query = 'release savepoint '.$savepointName;
264+
if ($this->toQueue)
265+
$this->queue[] = $query;
266+
else
267+
$this->queryRaw("{$query};\n");
268+
269+
return $this->dropSavepoint($savepointName);
270+
}
271+
272+
/**
273+
* @param string $savepointName
274+
* @return DB
275+
*/
276+
public function savepointRollback($savepointName)
277+
{
278+
$this->assertSavePointName($savepointName);
279+
if (!$this->inTransaction())
280+
throw new DatabaseException('To rollback savepoint need first begin transaction');
281+
282+
if (!$this->checkSavepointExist($savepointName))
283+
throw new DatabaseException("savepoint with name '{$savepointName}' nor registered");
284+
285+
$query = 'rollback to savepoint '.$savepointName;
286+
if ($this->toQueue)
287+
$this->queue[] = $query;
288+
else
289+
$this->queryRaw("{$query};\n");
290+
291+
return $this->dropSavepoint($savepointName);
292+
}
293+
225294
/**
226295
* base queries
227296
**/
@@ -331,5 +400,41 @@ public function setEncoding($encoding)
331400

332401
return $this;
333402
}
403+
404+
/**
405+
* @param string $savepointName
406+
* @return DB
407+
*/
408+
private function addSavepoint($savepointName)
409+
{
410+
if ($this->checkSavepointExist($savepointName))
411+
throw new DatabaseException("savepoint with name '{$savepointName}' already marked");
412+
413+
$this->savepointList[$savepointName] = true;
414+
return $this;
415+
}
416+
417+
/**
418+
* @param string $savepointName
419+
* @return DB
420+
*/
421+
private function dropSavepoint($savepointName)
422+
{
423+
if (!$this->checkSavepointExist($savepointName))
424+
throw new DatabaseException("savepoint with name '{$savepointName}' nor registered");
425+
426+
unset($this->savepointList[$savepointName]);
427+
return $this;
428+
}
429+
430+
private function checkSavepointExist($savepointName)
431+
{
432+
return isset($this->savepointList[$savepointName]);
433+
}
434+
435+
private function assertSavePointName($savepointName)
436+
{
437+
Assert::isEqual(1, preg_match('~^[A-Za-z][A-Za-z0-9]*$~iu', $savepointName));
438+
}
334439
}
335440
?>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
/***************************************************************************
3+
* Copyright (C) 2012 by Alexey S. Denisov *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify *
6+
* it under the terms of the GNU Lesser General Public License as *
7+
* published by the Free Software Foundation; either version 3 of the *
8+
* License, or (at your option) any later version. *
9+
* *
10+
***************************************************************************/
11+
12+
/**
13+
* Utility to create transaction and not think about current nested level
14+
*
15+
* @ingroup Transaction
16+
**/
17+
final class InnerTransaction
18+
{
19+
/**
20+
* @var DB
21+
**/
22+
private $db = null;
23+
private $savepointName = null;
24+
private $finished = false;
25+
26+
/**
27+
* @param DB|GenericDAO $database
28+
* @param IsolationLevel $level
29+
* @param AccessMode $mode
30+
* @return InnerTransaction
31+
**/
32+
public static function begin(
33+
$database,
34+
IsolationLevel $level = null,
35+
AccessMode $mode = null
36+
)
37+
{
38+
return new self($database, $level, $mode);
39+
}
40+
41+
/**
42+
* @param DB|GenericDAO $database
43+
* @param IsolationLevel $level
44+
* @param AccessMode $mode
45+
**/
46+
public function __construct(
47+
$database,
48+
IsolationLevel $level = null,
49+
AccessMode $mode = null
50+
)
51+
{
52+
if ($database instanceof DB) {
53+
$this->db = $database;
54+
} elseif ($database instanceof GenericDAO) {
55+
$this->db = DBPool::getByDao($database);
56+
} else {
57+
throw new WrongStateException(
58+
'$database must be instance of DB or GenericDAO'
59+
);
60+
}
61+
62+
$this->beginTransaction($level, $mode);
63+
}
64+
65+
public function commit()
66+
{
67+
$this->assertFinished();
68+
$this->finished = true;
69+
if (!$this->savepointName) {
70+
$this->db->commit();
71+
} else {
72+
$this->db->savepointRelease($this->savepointName);
73+
}
74+
}
75+
76+
public function rollback()
77+
{
78+
$this->assertFinished();
79+
$this->finished = true;
80+
if (!$this->savepointName) {
81+
$this->db->rollback();
82+
} else {
83+
$this->db->savepointRollback($this->savepointName);
84+
}
85+
}
86+
87+
private function beginTransaction(
88+
IsolationLevel $level = null,
89+
AccessMode $mode = null
90+
)
91+
{
92+
$this->assertFinished();
93+
if (!$this->db->inTransaction()) {
94+
$this->db->begin($level, $mode);
95+
} else {
96+
$this->savepointName = $this->createSavepointName();
97+
$this->db->savepointBegin($this->savepointName);
98+
}
99+
}
100+
101+
private function assertFinished()
102+
{
103+
if ($this->finished)
104+
throw new WrongStateException('This Transaction already finished');
105+
}
106+
107+
private static function createSavepointName()
108+
{
109+
static $i = 1;
110+
return 'innerSavepoint'.($i++);
111+
}
112+
}
113+
?>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
/***************************************************************************
3+
* Copyright (C) 2012 by Alexey S. Denisov *
4+
* *
5+
* This program is free software; you can redistribute it and/or modify *
6+
* it under the terms of the GNU Lesser General Public License as *
7+
* published by the Free Software Foundation; either version 3 of the *
8+
* License, or (at your option) any later version. *
9+
* *
10+
***************************************************************************/
11+
12+
/**
13+
* Utility to wrap function into transaction
14+
*
15+
* @ingroup Transaction
16+
**/
17+
final class InnerTransactionWrapper
18+
{
19+
/**
20+
* @var DB
21+
*/
22+
private $db = null;
23+
/**
24+
* @var StorableDAO
25+
*/
26+
private $dao = null;
27+
private $function = null;
28+
private $exceptionFunction = null;
29+
/**
30+
* @var IsolationLevel
31+
*/
32+
private $level = null;
33+
/**
34+
* @var AccessMode
35+
*/
36+
private $mode = null;
37+
38+
/**
39+
* @return InnerTransactionWrapper
40+
*/
41+
public static function create()
42+
{
43+
return new self;
44+
}
45+
46+
/**
47+
* @param DB $db
48+
* @return InnerTransactionWrapper
49+
*/
50+
public function setDB(DB $db)
51+
{
52+
$this->db = $db;
53+
return $this;
54+
}
55+
56+
/**
57+
* @param StorableDAO $dao
58+
* @return InnerTransactionWrapper
59+
*/
60+
public function setDao(StorableDAO $dao)
61+
{
62+
$this->dao = $dao;
63+
return $this;
64+
}
65+
66+
/**
67+
* @param collable $function
68+
* @return InnerTransactionWrapper
69+
*/
70+
public function setFunction($function)
71+
{
72+
Assert::isTrue(is_callable($function, false), '$function must be callable');
73+
$this->function = $function;
74+
return $this;
75+
}
76+
77+
/**
78+
* @param collable $function
79+
* @return InnerTransactionWrapper
80+
*/
81+
public function setExceptionFunction($function)
82+
{
83+
Assert::isTrue(is_callable($function, false), '$function must be callable');
84+
$this->exceptionFunction = $function;
85+
return $this;
86+
}
87+
88+
/**
89+
* @param IsolationLevel $level
90+
* @return InnerTransactionWrapper
91+
*/
92+
public function setLevel(IsolationLevel $level)
93+
{
94+
$this->level = $level;
95+
return $this;
96+
}
97+
98+
/**
99+
* @param AccessMode $mode
100+
* @return InnerTransactionWrapper
101+
*/
102+
public function setMode(AccessMode $mode)
103+
{
104+
$this->mode = $mode;
105+
return $this;
106+
}
107+
108+
public function run()
109+
{
110+
Assert::isTrue(!is_null($this->dao) || !is_null($this->db), 'set first dao or db');
111+
Assert::isNotNull($this->function, 'set first function');
112+
113+
$transaction = InnerTransaction::begin(
114+
$this->dao ?: $this->db,
115+
$this->level,
116+
$this->mode
117+
);
118+
119+
try {
120+
$result = call_user_func_array($this->function, func_get_args());
121+
$transaction->commit();
122+
return $result;
123+
} catch (Exception $e) {
124+
$transaction->rollback();
125+
if ($this->exceptionFunction) {
126+
$args = func_get_args();
127+
array_unshift($args, $e);
128+
return call_user_func_array($this->exceptionFunction, $args);
129+
}
130+
throw $e;
131+
}
132+
}
133+
}
134+
?>

test/config.inc.php.tpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
'host' => '127.0.0.1',
3838
'base' => 'onphp'
3939
),
40-
'SQLite' => array(
40+
'SQLitePDO' => array(
4141
'user' => 'onphp',
4242
'pass' => 'onphp',
4343
'host' => '127.0.0.1',

0 commit comments

Comments
 (0)