1: <?php
2: /**
3: * Copyright 2007 Maintainable Software, LLC
4: * Copyright 2008-2012 Horde LLC (http://www.horde.org/)
5: *
6: * @author Mike Naberezny <mike@maintainable.com>
7: * @author Derek DeVries <derek@maintainable.com>
8: * @author Chuck Hagenbuch <chuck@horde.org>
9: * @author Michael J. Rubinsky <mrubinsk@horde.org>
10: * @license http://www.horde.org/licenses/bsd
11: * @category Horde
12: * @package Db
13: * @subpackage Adapter
14: */
15:
16: /**
17: * The Horde_Db_Adapter_SplitRead:: class wraps two individual adapters to
18: * provide support for split read/write database setups.
19: *
20: * @author Mike Naberezny <mike@maintainable.com>
21: * @author Derek DeVries <derek@maintainable.com>
22: * @author Chuck Hagenbuch <chuck@horde.org>
23: * @author Michael J. Rubinsky <mrubinsk@horde.org>
24: * @license http://www.horde.org/licenses/bsd
25: * @category Horde
26: * @package Db
27: * @subpackage Adapter
28: */
29: class Horde_Db_Adapter_SplitRead implements Horde_Db_Adapter
30: {
31: /**
32: * The read adapter
33: *
34: * @var Horde_Db_Adapter
35: */
36: private $_read;
37:
38: /**
39: * The write adapter
40: *
41: * @var Horde_Db_Adapter
42: */
43: private $_write;
44:
45: /**
46: * Const'r
47: *
48: * @param Horde_Db_Adapter $read
49: * @param Horde_Db_Adapter $write
50: */
51: public function __construct(Horde_Db_Adapter $read, Horde_Db_Adapter $write)
52: {
53: $this->_read = $read;
54: $this->_write = $write;
55: }
56:
57: /**
58: * Delegate unknown methods to the _write adapter.
59: *
60: * @param string $method
61: * @param array $args
62: */
63: public function __call($method, $args)
64: {
65: $result = call_user_func_array(array($this->_write, $method), $args);
66: $this->_lastQuery = $this->_write->getLastQuery();
67: return $result;
68: }
69:
70: /**
71: * Returns the human-readable name of the adapter. Use mixed case - one
72: * can always use downcase if needed.
73: *
74: * @return string
75: */
76: public function adapterName()
77: {
78: return 'SplitRead';
79: }
80:
81: /**
82: * Does this adapter support migrations?
83: *
84: * @return boolean
85: */
86: public function supportsMigrations()
87: {
88: return $this->_write->supportsMigrations();
89: }
90:
91: /**
92: * Does this adapter support using DISTINCT within COUNT? This is +true+
93: * for all adapters except sqlite.
94: *
95: * @return boolean
96: */
97: public function supportsCountDistinct()
98: {
99: return $this->_read->supportsCountDistinct();
100: }
101:
102: /**
103: * Should primary key values be selected from their corresponding
104: * sequence before the insert statement? If true, next_sequence_value
105: * is called before each insert to set the record's primary key.
106: * This is false for all adapters but Firebird.
107: *
108: * @return boolean
109: */
110: public function prefetchPrimaryKey($tableName = null)
111: {
112: return $this->_write->prefetchPrimaryKey($tableName);
113: }
114:
115: /*##########################################################################
116: # Connection Management
117: ##########################################################################*/
118:
119: /**
120: * Connect to the db.
121: * @TODO: Lazy connect?
122: *
123: */
124: public function connect()
125: {
126: $this->_write->connect();
127: $this->_read->connect();
128: }
129:
130: /**
131: * Is the connection active?
132: *
133: * @return boolean
134: */
135: public function isActive()
136: {
137: return ($this->_read->isActive() && $this->_write->isActive());
138: }
139:
140: /**
141: * Reconnect to the db.
142: */
143: public function reconnect()
144: {
145: $this->disconnect();
146: $this->connect();
147: }
148:
149: /**
150: * Disconnect from db.
151: */
152: public function disconnect()
153: {
154: $this->_read->disconnect();
155: $this->_write->disconnect();
156: }
157:
158: /**
159: * Provides access to the underlying database connection. Useful for when
160: * you need to call a proprietary method such as postgresql's
161: * lo_* methods.
162: *
163: * @return resource
164: */
165: public function rawConnection()
166: {
167: return $this->_write->rawConnection();
168: }
169:
170:
171: /*##########################################################################
172: # Quoting
173: ##########################################################################*/
174:
175: /**
176: * Quotes a string, escaping any special characters.
177: *
178: * @param string $string
179: * @return string
180: */
181: public function quoteString($string)
182: {
183: return $this->_read->quoteString($string);
184: }
185:
186:
187: /*##########################################################################
188: # Database Statements
189: ##########################################################################*/
190:
191: /**
192: * Returns an array of records with the column names as keys, and
193: * column values as values.
194: *
195: * @param string $sql SQL statement.
196: * @param mixed $arg1 Either an array of bound parameters or a query
197: * name.
198: * @param string $arg2 If $arg1 contains bound parameters, the query
199: * name.
200: *
201: * @return PDOStatement
202: * @throws Horde_Db_Exception
203: */
204: public function select($sql, $arg1 = null, $arg2 = null)
205: {
206: $result = $this->_read->select($sql, $arg1, $arg2);
207: $this->_lastQuery = $this->_read->getLastQuery();
208: return $result;
209: }
210:
211: /**
212: * Returns an array of record hashes with the column names as keys and
213: * column values as values.
214: *
215: * @param string $sql SQL statement.
216: * @param mixed $arg1 Either an array of bound parameters or a query
217: * name.
218: * @param string $arg2 If $arg1 contains bound parameters, the query
219: * name.
220: *
221: * @return array
222: * @throws Horde_Db_Exception
223: */
224: public function selectAll($sql, $arg1 = null, $arg2 = null)
225: {
226: $result = $this->_read->selectAll($sql, $arg1, $arg2);
227: $this->_lastQuery = $this->_read->getLastQuery();
228: return $result;
229: }
230:
231: /**
232: * Returns a record hash with the column names as keys and column values
233: * as values.
234: *
235: * @param string $sql SQL statement.
236: * @param mixed $arg1 Either an array of bound parameters or a query
237: * name.
238: * @param string $arg2 If $arg1 contains bound parameters, the query
239: * name.
240: *
241: * @return array
242: * @throws Horde_Db_Exception
243: */
244: public function selectOne($sql, $arg1 = null, $arg2 = null)
245: {
246: $result = $this->_read->selectOne($sql, $arg1, $arg2);
247: $this->_lastQuery = $this->_read->getLastQuery();
248: return $result;
249: }
250:
251: /**
252: * Returns a single value from a record
253: *
254: * @param string $sql SQL statement.
255: * @param mixed $arg1 Either an array of bound parameters or a query
256: * name.
257: * @param string $arg2 If $arg1 contains bound parameters, the query
258: * name.
259: *
260: * @return string
261: * @throws Horde_Db_Exception
262: */
263: public function selectValue($sql, $arg1 = null, $arg2 = null)
264: {
265: $result = $this->_read->selectValue($sql, $arg1, $arg2);
266: $this->_lastQuery = $this->_read->getLastQuery();
267: return $result;
268: }
269:
270: /**
271: * Returns an array of the values of the first column in a select:
272: * selectValues("SELECT id FROM companies LIMIT 3") => [1,2,3]
273: *
274: * @param string $sql SQL statement.
275: * @param mixed $arg1 Either an array of bound parameters or a query
276: * name.
277: * @param string $arg2 If $arg1 contains bound parameters, the query
278: * name.
279: *
280: * @return array
281: * @throws Horde_Db_Exception
282: */
283: public function selectValues($sql, $arg1 = null, $arg2 = null)
284: {
285: $result = $this->_read->selectValues($sql, $arg1, $arg2);
286: $this->_lastQuery = $this->_read->getLastQuery();
287: return $result;
288: }
289:
290: /**
291: * Returns an array where the keys are the first column of a select, and the
292: * values are the second column:
293: *
294: * selectAssoc("SELECT id, name FROM companies LIMIT 3") => [1 => 'Ford', 2 => 'GM', 3 => 'Chrysler']
295: *
296: * @param string $sql SQL statement.
297: * @param mixed $arg1 Either an array of bound parameters or a query
298: * name.
299: * @param string $arg2 If $arg1 contains bound parameters, the query
300: * name.
301: *
302: * @return array
303: * @throws Horde_Db_Exception
304: */
305: public function selectAssoc($sql, $arg1 = null, $arg2 = null)
306: {
307: $result = $this->_read->selectAssoc($sql, $arg1, $arg2);
308: $this->_lastQuery = $this->_read->getLastQuery();
309: return $result;
310: }
311:
312: /**
313: * Executes the SQL statement in the context of this connection.
314: *
315: * @param string $sql SQL statement.
316: * @param mixed $arg1 Either an array of bound parameters or a query
317: * name.
318: * @param string $arg2 If $arg1 contains bound parameters, the query
319: * name.
320: *
321: * @return PDOStatement
322: * @throws Horde_Db_Exception
323: */
324: public function execute($sql, $arg1 = null, $arg2 = null)
325: {
326: // Can't assume this will always be a read action, use _write.
327: $result = $this->_write->execute($sql, $arg1, $arg2);
328: $this->_lastQuery = $this->_write->getLastQuery();
329:
330: // Once doing writes, keep using the write backend even for reads
331: // at least during the same request, to help against stale data.
332: $this->_read = $this->_write;
333:
334: return $result;
335: }
336:
337: /**
338: * Returns the last auto-generated ID from the affected table.
339: *
340: * @param string $sql SQL statement.
341: * @param mixed $arg1 Either an array of bound parameters or a
342: * query name.
343: * @param string $arg2 If $arg1 contains bound parameters, the
344: * query name.
345: * @param string $pk TODO
346: * @param integer $idValue TODO
347: * @param string $sequenceName TODO
348: *
349: * @return integer Last inserted ID.
350: * @throws Horde_Db_Exception
351: */
352: public function insert($sql, $arg1 = null, $arg2 = null, $pk = null,
353: $idValue = null, $sequenceName = null)
354: {
355: $result = $this->_write->insert($sql, $arg1, $arg2, $pk, $idValue, $sequenceName);
356: $this->_lastQuery = $this->_write->getLastQuery();
357:
358: // Once doing writes, keep using the write backend even for reads
359: // at least during the same request, to help against stale data.
360: $this->_read = $this->_write;
361:
362: return $result;
363: }
364:
365: /**
366: * Executes the update statement and returns the number of rows affected.
367: *
368: * @param string $sql SQL statement.
369: * @param mixed $arg1 Either an array of bound parameters or a query
370: * name.
371: * @param string $arg2 If $arg1 contains bound parameters, the query
372: * name.
373: *
374: * @return integer Number of rows affected.
375: * @throws Horde_Db_Exception
376: */
377: public function update($sql, $arg1 = null, $arg2 = null)
378: {
379: $result = $this->_write->update($sql, $arg1, $arg2);
380: $this->_lastQuery = $this->_write->getLastQuery();
381:
382: // Once doing writes, keep using the write backend even for reads
383: // at least during the same request, to help against stale data.
384: $this->_read = $this->_write;
385:
386: return $result;
387: }
388:
389: /**
390: * Executes the delete statement and returns the number of rows affected.
391: *
392: * @param string $sql SQL statement.
393: * @param mixed $arg1 Either an array of bound parameters or a query
394: * name.
395: * @param string $arg2 If $arg1 contains bound parameters, the query
396: * name.
397: *
398: * @return integer Number of rows affected.
399: * @throws Horde_Db_Exception
400: */
401: public function delete($sql, $arg1 = null, $arg2 = null)
402: {
403: $result = $this->_write->delete($sql, $arg1, $arg2);
404: $this->_lastQuery = $this->_write->getLastQuery();
405:
406: // Once doing writes, keep using the write backend even for reads
407: // at least during the same request, to help against stale data.
408: $this->_read = $this->_write;
409:
410: return $result;
411: }
412:
413: /**
414: * Check if a transaction has been started.
415: *
416: * @return boolean True if transaction has been started.
417: */
418: public function transactionStarted()
419: {
420: $result = $this->_write->transactionStarted();
421: $this->_lastQuery = $this->_write->getLastQuery();
422: return $result;
423: }
424: /**
425: * Begins the transaction (and turns off auto-committing).
426: */
427: public function beginDbTransaction()
428: {
429: $result = $this->_write->beginDbTransaction();
430: $this->_lastQuery = $this->_write->getLastQuery();
431: return $result;
432: }
433:
434: /**
435: * Commits the transaction (and turns on auto-committing).
436: */
437: public function commitDbTransaction()
438: {
439: $result = $this->_write->commitDbTransaction();
440: $this->_lastQuery = $this->_write->getLastQuery();
441: return $result;
442: }
443:
444: /**
445: * Rolls back the transaction (and turns on auto-committing). Must be
446: * done if the transaction block raises an exception or returns false.
447: */
448: public function rollbackDbTransaction()
449: {
450: $result = $this->_write->rollbackDbTransaction();
451: $this->_lastQuery = $this->_write->getLastQuery();
452: return $result;
453: }
454:
455: /**
456: * Appends +LIMIT+ and +OFFSET+ options to a SQL statement.
457: *
458: * @param string $sql SQL statement.
459: * @param array $options TODO
460: *
461: * @return string
462: */
463: public function addLimitOffset($sql, $options)
464: {
465: $result = $this->_read->addLimitOffset($sql, $options);
466: $this->_lastQuery = $this->_write->getLastQuery();
467: return $result;
468: }
469:
470: /**
471: * Appends a locking clause to an SQL statement.
472: * This method *modifies* the +sql+ parameter.
473: *
474: * # SELECT * FROM suppliers FOR UPDATE
475: * add_lock! 'SELECT * FROM suppliers', :lock => true
476: * add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
477: *
478: * @param string &$sql SQL statment.
479: * @param array $options TODO.
480: */
481: public function addLock(&$sql, array $options = array())
482: {
483: $this->_write->addLock($sql, $options);
484: $this->_lastQuery = $this->_write->getLastQuery();
485: }
486: }
487: