| 1 |
lars |
1 |
<?php
|
|
|
2 |
/*
|
|
|
3 |
* $Id: PropelPDO.php 1262 2009-10-26 20:54:39Z francois $
|
|
|
4 |
*
|
|
|
5 |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
6 |
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
7 |
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
8 |
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
9 |
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
10 |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
11 |
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
12 |
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
13 |
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
14 |
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
15 |
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
16 |
*
|
|
|
17 |
* This software consists of voluntary contributions made by many individuals
|
|
|
18 |
* and is licensed under the LGPL. For more information please see
|
|
|
19 |
* <http://propel.phpdb.org>.
|
|
|
20 |
*/
|
|
|
21 |
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
* PDO connection subclass that provides the basic fixes to PDO that are required by Propel.
|
|
|
25 |
*
|
|
|
26 |
* This class was designed to work around the limitation in PDO where attempting to begin
|
|
|
27 |
* a transaction when one has already been begun will trigger a PDOException. Propel
|
|
|
28 |
* relies on the ability to create nested transactions, even if the underlying layer
|
|
|
29 |
* simply ignores these (because it doesn't support nested transactions).
|
|
|
30 |
*
|
|
|
31 |
* The changes that this class makes to the underlying API include the addition of the
|
|
|
32 |
* getNestedTransactionDepth() and isInTransaction() and the fact that beginTransaction()
|
|
|
33 |
* will no longer throw a PDOException (or trigger an error) if a transaction is already
|
|
|
34 |
* in-progress.
|
|
|
35 |
*
|
|
|
36 |
* @author Cameron Brunner <cameron.brunner@gmail.com>
|
|
|
37 |
* @author Hans Lellelid <hans@xmpl.org>
|
|
|
38 |
* @author Christian Abegg <abegg.ch@gmail.com>
|
|
|
39 |
* @since 2006-09-22
|
|
|
40 |
* @package propel.util
|
|
|
41 |
*/
|
|
|
42 |
class PropelPDO extends PDO {
|
|
|
43 |
|
|
|
44 |
/**
|
|
|
45 |
* Attribute to use to set whether to cache prepared statements.
|
|
|
46 |
*/
|
|
|
47 |
const PROPEL_ATTR_CACHE_PREPARES = -1;
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* The current transaction depth.
|
|
|
51 |
* @var int
|
|
|
52 |
*/
|
|
|
53 |
protected $nestedTransactionCount = 0;
|
|
|
54 |
|
|
|
55 |
/**
|
|
|
56 |
* Cache of prepared statements (PDOStatement) keyed by md5 of SQL.
|
|
|
57 |
*
|
|
|
58 |
* @var array [md5(sql) => PDOStatement]
|
|
|
59 |
*/
|
|
|
60 |
protected $preparedStatements = array();
|
|
|
61 |
|
|
|
62 |
/**
|
|
|
63 |
* Whether to cache prepared statements.
|
|
|
64 |
*
|
|
|
65 |
* @var boolean
|
|
|
66 |
*/
|
|
|
67 |
protected $cachePreparedStatements = false;
|
|
|
68 |
|
|
|
69 |
/**
|
|
|
70 |
* Whether the final commit is possible
|
|
|
71 |
* Is false if a nested transaction is rolled back
|
|
|
72 |
*/
|
|
|
73 |
protected $isUncommitable = false;
|
|
|
74 |
|
|
|
75 |
/**
|
|
|
76 |
* Gets the current transaction depth.
|
|
|
77 |
* @return int
|
|
|
78 |
*/
|
|
|
79 |
public function getNestedTransactionCount()
|
|
|
80 |
{
|
|
|
81 |
return $this->nestedTransactionCount;
|
|
|
82 |
}
|
|
|
83 |
|
|
|
84 |
/**
|
|
|
85 |
* Set the current transaction depth.
|
|
|
86 |
* @param int $v The new depth.
|
|
|
87 |
*/
|
|
|
88 |
protected function setNestedTransactionCount($v)
|
|
|
89 |
{
|
|
|
90 |
$this->nestedTransactionCount = $v;
|
|
|
91 |
}
|
|
|
92 |
|
|
|
93 |
/**
|
|
|
94 |
* Decrements the current transaction depth by one.
|
|
|
95 |
*/
|
|
|
96 |
protected function decrementNestedTransactionCount()
|
|
|
97 |
{
|
|
|
98 |
$this->nestedTransactionCount--;
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
/**
|
|
|
102 |
* Increments the current transaction depth by one.
|
|
|
103 |
*/
|
|
|
104 |
protected function incrementNestedTransactionCount()
|
|
|
105 |
{
|
|
|
106 |
$this->nestedTransactionCount++;
|
|
|
107 |
}
|
|
|
108 |
|
|
|
109 |
/**
|
|
|
110 |
* Is this PDO connection currently in-transaction?
|
|
|
111 |
* This is equivalent to asking whether the current nested transaction count
|
|
|
112 |
* is greater than 0.
|
|
|
113 |
* @return boolean
|
|
|
114 |
*/
|
|
|
115 |
public function isInTransaction()
|
|
|
116 |
{
|
|
|
117 |
return ($this->getNestedTransactionCount() > 0);
|
|
|
118 |
}
|
|
|
119 |
|
|
|
120 |
/**
|
|
|
121 |
* Overrides PDO::beginTransaction() to prevent errors due to already-in-progress transaction.
|
|
|
122 |
*/
|
|
|
123 |
public function beginTransaction()
|
|
|
124 |
{
|
|
|
125 |
$return = true;
|
|
|
126 |
$opcount = $this->getNestedTransactionCount();
|
|
|
127 |
if ( $opcount === 0 ) {
|
|
|
128 |
$return = parent::beginTransaction();
|
|
|
129 |
$this->isUncommitable = false;
|
|
|
130 |
}
|
|
|
131 |
$this->incrementNestedTransactionCount();
|
|
|
132 |
return $return;
|
|
|
133 |
}
|
|
|
134 |
|
|
|
135 |
/**
|
|
|
136 |
* Overrides PDO::commit() to only commit the transaction if we are in the outermost
|
|
|
137 |
* transaction nesting level.
|
|
|
138 |
*/
|
|
|
139 |
public function commit()
|
|
|
140 |
{
|
|
|
141 |
$return = true;
|
|
|
142 |
$opcount = $this->getNestedTransactionCount();
|
|
|
143 |
if ($opcount > 0) {
|
|
|
144 |
if ($opcount === 1) {
|
|
|
145 |
if ($this->isUncommitable) {
|
|
|
146 |
throw new PropelException('Cannot commit because a nested transaction was rolled back');
|
|
|
147 |
} else {
|
|
|
148 |
$return = parent::commit();
|
|
|
149 |
}
|
|
|
150 |
}
|
|
|
151 |
$this->decrementNestedTransactionCount();
|
|
|
152 |
}
|
|
|
153 |
return $return;
|
|
|
154 |
}
|
|
|
155 |
|
|
|
156 |
/**
|
|
|
157 |
* Overrides PDO::rollBack() to only rollback the transaction if we are in the outermost
|
|
|
158 |
* transaction nesting level
|
|
|
159 |
* @return boolean Whether operation was successful.
|
|
|
160 |
*/
|
|
|
161 |
public function rollBack()
|
|
|
162 |
{
|
|
|
163 |
$return = true;
|
|
|
164 |
$opcount = $this->getNestedTransactionCount();
|
|
|
165 |
if ($opcount > 0) {
|
|
|
166 |
if ($opcount === 1) {
|
|
|
167 |
$return = parent::rollBack();
|
|
|
168 |
} else {
|
|
|
169 |
$this->isUncommitable = true;
|
|
|
170 |
}
|
|
|
171 |
$this->decrementNestedTransactionCount();
|
|
|
172 |
}
|
|
|
173 |
return $return;
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
/**
|
|
|
177 |
* Rollback the whole transaction, even if this is a nested rollback
|
|
|
178 |
* and reset the nested transaction count to 0.
|
|
|
179 |
* @return boolean Whether operation was successful.
|
|
|
180 |
*/
|
|
|
181 |
public function forceRollBack()
|
|
|
182 |
{
|
|
|
183 |
$return = true;
|
|
|
184 |
$opcount = $this->getNestedTransactionCount();
|
|
|
185 |
if ($opcount > 0) {
|
|
|
186 |
// If we're in a transaction, always roll it back
|
|
|
187 |
// regardless of nesting level.
|
|
|
188 |
$return = parent::rollBack();
|
|
|
189 |
|
|
|
190 |
// reset nested transaction count to 0 so that we don't
|
|
|
191 |
// try to commit (or rollback) the transaction outside this scope.
|
|
|
192 |
$this->nestedTransactionCount = 0;
|
|
|
193 |
}
|
|
|
194 |
return $return;
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
/**
|
|
|
198 |
* Sets a connection attribute.
|
|
|
199 |
*
|
|
|
200 |
* This is overridden here to provide support for setting Propel-specific attributes
|
|
|
201 |
* too.
|
|
|
202 |
*
|
|
|
203 |
* @param int $attribute The attribute to set (e.g. PropelPDO::PROPEL_ATTR_CACHE_PREPARES).
|
|
|
204 |
* @param mixed $value The attribute value.
|
|
|
205 |
*/
|
|
|
206 |
public function setAttribute($attribute, $value)
|
|
|
207 |
{
|
|
|
208 |
switch($attribute) {
|
|
|
209 |
case self::PROPEL_ATTR_CACHE_PREPARES:
|
|
|
210 |
$this->cachePreparedStatements = $value;
|
|
|
211 |
break;
|
|
|
212 |
default:
|
|
|
213 |
parent::setAttribute($attribute, $value);
|
|
|
214 |
}
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
/**
|
|
|
218 |
* Gets a connection attribute.
|
|
|
219 |
*
|
|
|
220 |
* This is overridden here to provide support for setting Propel-specific attributes
|
|
|
221 |
* too.
|
|
|
222 |
*
|
|
|
223 |
* @param int $attribute The attribute to get (e.g. PropelPDO::PROPEL_ATTR_CACHE_PREPARES).
|
|
|
224 |
*/
|
|
|
225 |
public function getAttribute($attribute)
|
|
|
226 |
{
|
|
|
227 |
switch($attribute) {
|
|
|
228 |
case self::PROPEL_ATTR_CACHE_PREPARES:
|
|
|
229 |
return $this->cachePreparedStatements;
|
|
|
230 |
break;
|
|
|
231 |
default:
|
|
|
232 |
return parent::getAttribute($attribute);
|
|
|
233 |
}
|
|
|
234 |
}
|
|
|
235 |
|
|
|
236 |
/**
|
|
|
237 |
* Overrides PDO::prepare() to add query caching support if the
|
|
|
238 |
* PropelPDO::PROPEL_ATTR_CACHE_PREPARES was set to true.
|
|
|
239 |
* .
|
|
|
240 |
* @param string $sql
|
|
|
241 |
* @param array
|
|
|
242 |
* @return PDOStatement
|
|
|
243 |
*/
|
|
|
244 |
public function prepare($sql, $driver_options = array())
|
|
|
245 |
{
|
|
|
246 |
if ($this->cachePreparedStatements) {
|
|
|
247 |
$key = $sql;
|
|
|
248 |
if (!isset($this->preparedStatements[$key])) {
|
|
|
249 |
$stmt = parent::prepare($sql, $driver_options);
|
|
|
250 |
$this->preparedStatements[$key] = $stmt;
|
|
|
251 |
return $stmt;
|
|
|
252 |
} else {
|
|
|
253 |
return $this->preparedStatements[$key];
|
|
|
254 |
}
|
|
|
255 |
} else {
|
|
|
256 |
return parent::prepare($sql, $driver_options);
|
|
|
257 |
}
|
|
|
258 |
}
|
|
|
259 |
|
|
|
260 |
/**
|
|
|
261 |
* Clears any stored prepared statements for this connection.
|
|
|
262 |
*/
|
|
|
263 |
public function clearStatementCache()
|
|
|
264 |
{
|
|
|
265 |
$this->preparedStatements = array();
|
|
|
266 |
}
|
|
|
267 |
|
|
|
268 |
}
|