@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * @task xaction Transaction Management
5 */
6abstract class AphrontDatabaseConnection
7 extends Phobject
8 implements PhutilQsprintfInterface {
9
10 private $transactionState;
11 private $readOnly;
12 private $queryTimeout;
13 private $locks = array();
14 private $lastActiveEpoch;
15 private $persistent;
16
17 abstract public function getInsertID();
18 abstract public function getAffectedRows();
19 abstract public function selectAllResults();
20 abstract public function executeQuery(PhutilQueryString $query);
21 abstract public function executeRawQueries(array $raw_queries);
22 abstract public function close();
23 abstract public function openConnection();
24
25 public function __destruct() {
26 // NOTE: This does not actually close persistent connections: PHP maintains
27 // them in the connection pool.
28 $this->close();
29 }
30
31 final public function setLastActiveEpoch($epoch) {
32 $this->lastActiveEpoch = $epoch;
33 return $this;
34 }
35
36 final public function getLastActiveEpoch() {
37 return $this->lastActiveEpoch;
38 }
39
40 final public function setPersistent($persistent) {
41 $this->persistent = $persistent;
42 return $this;
43 }
44
45 final public function getPersistent() {
46 return $this->persistent;
47 }
48
49 public function queryData($pattern/* , $arg, $arg, ... */) {
50 $args = func_get_args();
51 array_unshift($args, $this);
52 return call_user_func_array('queryfx_all', $args);
53 }
54
55 public function query($pattern/* , $arg, $arg, ... */) {
56 $args = func_get_args();
57 array_unshift($args, $this);
58 return call_user_func_array('queryfx', $args);
59 }
60
61 public function setReadOnly($read_only) {
62 $this->readOnly = $read_only;
63 return $this;
64 }
65
66 public function getReadOnly() {
67 return $this->readOnly;
68 }
69
70 public function setQueryTimeout($query_timeout) {
71 $this->queryTimeout = $query_timeout;
72 return $this;
73 }
74
75 public function getQueryTimeout() {
76 return $this->queryTimeout;
77 }
78
79 public function asyncQuery($raw_query) {
80 throw new Exception(pht('Async queries are not supported.'));
81 }
82
83 public static function resolveAsyncQueries(array $conns, array $asyncs) {
84 throw new Exception(pht('Async queries are not supported.'));
85 }
86
87 /**
88 * Is this connection idle and safe to close?
89 *
90 * A connection is "idle" if it can be safely closed without loss of state.
91 * Connections inside a transaction or holding locks are not idle, even
92 * though they may not actively be executing queries.
93 *
94 * @return bool True if the connection is idle and can be safely closed.
95 */
96 public function isIdle() {
97 if ($this->isInsideTransaction()) {
98 return false;
99 }
100
101 if ($this->isHoldingAnyLock()) {
102 return false;
103 }
104
105 return true;
106 }
107
108
109/* -( Global Locks )------------------------------------------------------- */
110
111
112 public function rememberLock($lock) {
113 if (isset($this->locks[$lock])) {
114 throw new Exception(
115 pht(
116 'Trying to remember lock "%s", but this lock has already been '.
117 'remembered.',
118 $lock));
119 }
120
121 $this->locks[$lock] = true;
122 return $this;
123 }
124
125
126 public function forgetLock($lock) {
127 if (empty($this->locks[$lock])) {
128 throw new Exception(
129 pht(
130 'Trying to forget lock "%s", but this connection does not remember '.
131 'that lock.',
132 $lock));
133 }
134
135 unset($this->locks[$lock]);
136 return $this;
137 }
138
139
140 public function forgetAllLocks() {
141 $this->locks = array();
142 return $this;
143 }
144
145
146 public function isHoldingAnyLock() {
147 return (bool)$this->locks;
148 }
149
150
151/* -( Transaction Management )--------------------------------------------- */
152
153
154 /**
155 * Begin a transaction, or set a savepoint if the connection is already
156 * transactional.
157 *
158 * @return $this
159 * @task xaction
160 */
161 public function openTransaction() {
162 $state = $this->getTransactionState();
163 $point = $state->getSavepointName();
164 $depth = $state->getDepth();
165
166 $new_transaction = ($depth == 0);
167 if ($new_transaction) {
168 $this->query('START TRANSACTION');
169 } else {
170 $this->query('SAVEPOINT '.$point);
171 }
172
173 $state->increaseDepth();
174
175 return $this;
176 }
177
178
179 /**
180 * Commit a transaction, or stage a savepoint for commit once the entire
181 * transaction completes if inside a transaction stack.
182 *
183 * @return $this
184 * @task xaction
185 */
186 public function saveTransaction() {
187 $state = $this->getTransactionState();
188 $depth = $state->decreaseDepth();
189
190 if ($depth == 0) {
191 $this->query('COMMIT');
192 }
193
194 return $this;
195 }
196
197
198 /**
199 * Rollback a transaction, or unstage the last savepoint if inside a
200 * transaction stack.
201 *
202 * @return $this
203 */
204 public function killTransaction() {
205 $state = $this->getTransactionState();
206 $depth = $state->decreaseDepth();
207
208 if ($depth == 0) {
209 $this->query('ROLLBACK');
210 } else {
211 $this->query('ROLLBACK TO SAVEPOINT '.$state->getSavepointName());
212 }
213
214 return $this;
215 }
216
217
218 /**
219 * Returns true if the connection is transactional.
220 *
221 * @return bool True if the connection is currently transactional.
222 * @task xaction
223 */
224 public function isInsideTransaction() {
225 $state = $this->getTransactionState();
226 return ($state->getDepth() > 0);
227 }
228
229
230 /**
231 * Get the current @{class:AphrontDatabaseTransactionState} object, or create
232 * one if none exists.
233 *
234 * @return AphrontDatabaseTransactionState Current transaction state.
235 * @task xaction
236 */
237 protected function getTransactionState() {
238 if (!$this->transactionState) {
239 $this->transactionState = new AphrontDatabaseTransactionState();
240 }
241 return $this->transactionState;
242 }
243
244
245 /**
246 * @task xaction
247 */
248 public function beginReadLocking() {
249 $this->getTransactionState()->beginReadLocking();
250 return $this;
251 }
252
253
254 /**
255 * @task xaction
256 */
257 public function endReadLocking() {
258 $this->getTransactionState()->endReadLocking();
259 return $this;
260 }
261
262
263 /**
264 * @task xaction
265 */
266 public function isReadLocking() {
267 return $this->getTransactionState()->isReadLocking();
268 }
269
270
271 /**
272 * @task xaction
273 */
274 public function beginWriteLocking() {
275 $this->getTransactionState()->beginWriteLocking();
276 return $this;
277 }
278
279
280 /**
281 * @task xaction
282 */
283 public function endWriteLocking() {
284 $this->getTransactionState()->endWriteLocking();
285 return $this;
286 }
287
288
289 /**
290 * @task xaction
291 */
292 public function isWriteLocking() {
293 return $this->getTransactionState()->isWriteLocking();
294 }
295
296}