blob: 4c911aa6e8f7e7f358e87dc54023d5413d2a8ef8 [file] [log] [blame]
Timothy Warren24f325c2011-10-07 10:03:01 -04001<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * CodeIgniter
4 *
5 * An open source application development framework for PHP 5.1.6 or newer
6 *
7 * @package CodeIgniter
8 * @author ExpressionEngine Dev Team
9 * @copyright Copyright (c) 2008 - 2011, EllisLab, Inc.
10 * @license http://codeigniter.com/user_guide/license.html
11 * @link http://codeigniter.com
12 * @since Version 2.1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * PDO Database Adapter Class
20 *
21 * Note: _DB is an extender class that the app controller
22 * creates dynamically based on whether the active record
23 * class is being used or not.
24 *
25 * @package CodeIgniter
26 * @subpackage Drivers
27 * @category Database
28 * @author ExpressionEngine Dev Team
29 * @link http://codeigniter.com/user_guide/database/
30 */
Timothy Warren9f5316e2011-09-14 12:25:14 -040031
Timothy Warren24f325c2011-10-07 10:03:01 -040032class CI_DB_pdo_driver extends CI_DB {
33
34 var $dbdriver = 'pdo';
35
36 // the character used to excape - not necessary for PDO
37 var $_escape_char = '';
38 var $_like_escape_str;
39 var $_like_escape_chr;
Timothy Warren9f5316e2011-09-14 12:25:14 -040040
Timothy Warren24f325c2011-10-07 10:03:01 -040041
42 /**
43 * The syntax to count rows is slightly different across different
44 * database engines, so this string appears in each driver and is
45 * used for the count_all() and count_all_results() functions.
46 */
47 var $_count_string = "SELECT COUNT(*) AS ";
48 var $_random_keyword;
49
50
51 function __construct($params)
52 {
53 parent::__construct($params);
Timothy Warren9f5316e2011-09-14 12:25:14 -040054
Timothy Warren24f325c2011-10-07 10:03:01 -040055 // clause and character used for LIKE escape sequences
56 if (strpos($this->hostname, 'mysql') !== FALSE)
57 {
58 $this->_like_escape_str = '';
59 $this->_like_escape_chr = '';
60 }
61 else if (strpos($this->hostname, 'odbc') !== FALSE)
62 {
63 $this->_like_escape_str = " {escape '%s'} ";
64 $this->_like_escape_chr = '!';
65 }
66 else
67 {
68 $this->_like_escape_str = " ESCAPE '%s' ";
69 $this->_like_escape_chr = '!';
70 }
Timothy Warren9f5316e2011-09-14 12:25:14 -040071
Timothy Warren24f325c2011-10-07 10:03:01 -040072 $this->hostname = $this->hostname . ";dbname=".$this->database;
73 $this->trans_enabled = FALSE;
74
75 $this->_random_keyword = ' RND('.time().')'; // database specific random keyword
76 }
77
78 /**
79 * Non-persistent database connection
80 *
81 * @access private called by the base class
82 * @return resource
83 */
84 function db_connect()
85 {
86 return new PDO($this->hostname,$this->username,$this->password, array(
87 PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT
88 ));
89 }
90
91 // --------------------------------------------------------------------
92
93 /**
94 * Persistent database connection
95 *
96 * @access private called by the base class
97 * @return resource
98 */
99 function db_pconnect()
100 {
101 return new PDO($this->hostname,$this->username,$this->password, array(
102 PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
103 PDO::ATTR_PERSISTENT => true
104 ));
105 }
106
107 // --------------------------------------------------------------------
108
109 /**
110 * Reconnect
111 *
112 * Keep / reestablish the db connection if no queries have been
113 * sent for a length of time exceeding the server's idle timeout
114 *
115 * @access public
116 * @return void
117 */
118 function reconnect()
119 {
120 if ($this->db->db_debug)
121 {
122 return $this->db->display_error('db_unsuported_feature');
123 }
124 return FALSE;
125 }
126
127 // --------------------------------------------------------------------
128
129 /**
130 * Select the database
131 *
132 * @access private called by the base class
133 * @return resource
134 */
135 function db_select()
136 {
137 // Not needed for PDO
138 return TRUE;
139 }
140
141 // --------------------------------------------------------------------
142
143 /**
144 * Set client character set
145 *
146 * @access public
147 * @param string
148 * @param string
149 * @return resource
150 */
151 function db_set_charset($charset, $collation)
152 {
153 // @todo - add support if needed
154 return TRUE;
155 }
156
157 // --------------------------------------------------------------------
158
159 /**
160 * Version number query string
161 *
162 * @access public
163 * @return string
164 */
165 function _version()
166 {
167 return $this->conn_id->getAttribute(PDO::ATTR_CLIENT_VERSION);
168 }
169
170 // --------------------------------------------------------------------
171
172 /**
173 * Execute the query
174 *
175 * @access private called by the base class
176 * @param string an SQL query
177 * @return object
178 */
179 function _execute($sql)
180 {
181 $sql = $this->_prep_query($sql);
182 $result_id = $this->conn_id->query($sql);
Timothy Warren9f5316e2011-09-14 12:25:14 -0400183
Timothy Warren24f325c2011-10-07 10:03:01 -0400184 if (is_object($result_id))
185 {
186 $this->affect_rows = $result_id->rowCount();
187 }
188 else
189 {
190 $this->affect_rows = 0;
191 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400192
Timothy Warren24f325c2011-10-07 10:03:01 -0400193 return $result_id;
194 }
195
196 // --------------------------------------------------------------------
197
198 /**
199 * Prep the query
200 *
201 * If needed, each database adapter can prep the query string
202 *
203 * @access private called by execute()
204 * @param string an SQL query
205 * @return string
206 */
207 function _prep_query($sql)
208 {
209 return $sql;
210 }
211
212 // --------------------------------------------------------------------
213
214 /**
215 * Begin Transaction
216 *
217 * @access public
218 * @return bool
219 */
220 function trans_begin($test_mode = FALSE)
221 {
222 if ( ! $this->trans_enabled)
223 {
224 return TRUE;
225 }
226
227 // When transactions are nested we only begin/commit/rollback the outermost ones
228 if ($this->_trans_depth > 0)
229 {
230 return TRUE;
231 }
232
233 // Reset the transaction failure flag.
234 // If the $test_mode flag is set to TRUE transactions will be rolled back
235 // even if the queries produce a successful result.
236 $this->_trans_failure = ($test_mode === TRUE) ? TRUE : FALSE;
237
238 return $this->conn_id->beginTransaction();
239 }
240
241 // --------------------------------------------------------------------
242
243 /**
244 * Commit Transaction
245 *
246 * @access public
247 * @return bool
248 */
249 function trans_commit()
250 {
251 if ( ! $this->trans_enabled)
252 {
253 return TRUE;
254 }
255
256 // When transactions are nested we only begin/commit/rollback the outermost ones
257 if ($this->_trans_depth > 0)
258 {
259 return TRUE;
260 }
261
262 $ret = $this->conn->commit();
263 return $ret;
264 }
265
266 // --------------------------------------------------------------------
267
268 /**
269 * Rollback Transaction
270 *
271 * @access public
272 * @return bool
273 */
274 function trans_rollback()
275 {
276 if ( ! $this->trans_enabled)
277 {
278 return TRUE;
279 }
280
281 // When transactions are nested we only begin/commit/rollback the outermost ones
282 if ($this->_trans_depth > 0)
283 {
284 return TRUE;
285 }
286
287 $ret = $this->conn_id->rollBack();
288 return $ret;
289 }
290
291 // --------------------------------------------------------------------
292
293 /**
294 * Escape String
295 *
296 * @access public
297 * @param string
298 * @param bool whether or not the string will be used in a LIKE condition
299 * @return string
300 */
301 function escape_str($str, $like = FALSE)
302 {
303 if (is_array($str))
304 {
305 foreach ($str as $key => $val)
306 {
307 $str[$key] = $this->escape_str($val, $like);
308 }
309
310 return $str;
311 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400312
Timothy Warren24f325c2011-10-07 10:03:01 -0400313 //Escape the string
314 $str = $this->conn_id->quote($str);
Timothy Warren9f5316e2011-09-14 12:25:14 -0400315
Timothy Warren24f325c2011-10-07 10:03:01 -0400316 //If there are duplicated quotes, trim them away
317 if (strpos($str, "'") === 0)
318 {
319 $str = substr($str, 1, -1);
320 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400321
Timothy Warren24f325c2011-10-07 10:03:01 -0400322 // escape LIKE condition wildcards
323 if ($like === TRUE)
324 {
325 $str = str_replace( array('%', '_', $this->_like_escape_chr),
326 array($this->_like_escape_chr.'%', $this->_like_escape_chr.'_', $this->_like_escape_chr.$this->_like_escape_chr),
327 $str);
328 }
329
330 return $str;
331 }
332
333 // --------------------------------------------------------------------
334
335 /**
336 * Affected Rows
337 *
338 * @access public
339 * @return integer
340 */
341 function affected_rows()
342 {
343 return $this->affect_rows;
344 }
345
346 // --------------------------------------------------------------------
347
348 /**
349 * Insert ID
350 *
351 * @access public
352 * @return integer
353 */
354 function insert_id($name=NULL)
355 {
356 //Convenience method for postgres insertid
357 if (strpos($this->hostname, 'pgsql') !== FALSE)
358 {
359 $v = $this->_version();
360
361 $table = func_num_args() > 0 ? func_get_arg(0) : NULL;
362
363 if ($table == NULL && $v >= '8.1')
364 {
365 $sql='SELECT LASTVAL() as ins_id';
366 }
367 $query = $this->query($sql);
368 $row = $query->row();
369 return $row->ins_id;
370 }
371 else
372 {
373 return $this->conn_id->lastInsertId($name);
374 }
375 }
376
377 // --------------------------------------------------------------------
378
379 /**
380 * "Count All" query
381 *
382 * Generates a platform-specific query string that counts all records in
383 * the specified database
384 *
385 * @access public
386 * @param string
387 * @return string
388 */
389 function count_all($table = '')
390 {
391 if ($table == '')
392 {
393 return 0;
394 }
395
396 $query = $this->query($this->_count_string . $this->_protect_identifiers('numrows') . " FROM " . $this->_protect_identifiers($table, TRUE, NULL, FALSE));
397
398 if ($query->num_rows() == 0)
399 {
400 return 0;
401 }
402
403 $row = $query->row();
404 $this->_reset_select();
405 return (int) $row->numrows;
406 }
407
408 // --------------------------------------------------------------------
409
410 /**
411 * Show table query
412 *
413 * Generates a platform-specific query string so that the table names can be fetched
414 *
415 * @access private
416 * @param boolean
417 * @return string
418 */
419 function _list_tables($prefix_limit = FALSE)
420 {
421 $sql = "SHOW TABLES FROM `".$this->database."`";
422
423 if ($prefix_limit !== FALSE AND $this->dbprefix != '')
424 {
425 //$sql .= " LIKE '".$this->escape_like_str($this->dbprefix)."%' ".sprintf($this->_like_escape_str, $this->_like_escape_chr);
426 return FALSE; // not currently supported
427 }
428
429 return $sql;
430 }
431
432 // --------------------------------------------------------------------
433
434 /**
435 * Show column query
436 *
437 * Generates a platform-specific query string so that the column names can be fetched
438 *
439 * @access public
440 * @param string the table name
441 * @return string
442 */
443 function _list_columns($table = '')
444 {
445 return "SHOW COLUMNS FROM ".$table;
446 }
447
448 // --------------------------------------------------------------------
449
450 /**
451 * Field data query
452 *
453 * Generates a platform-specific query so that the column data can be retrieved
454 *
455 * @access public
456 * @param string the table name
457 * @return object
458 */
459 function _field_data($table)
460 {
461 return "SELECT TOP 1 FROM ".$table;
462 }
463
464 // --------------------------------------------------------------------
465
466 /**
467 * The error message string
468 *
469 * @access private
470 * @return string
471 */
472 function _error_message()
473 {
474 $error_array = $this->conn_id->errorInfo();
475 return $error_array[2];
476 }
477
478 // --------------------------------------------------------------------
479
480 /**
481 * The error message number
482 *
483 * @access private
484 * @return integer
485 */
486 function _error_number()
487 {
488 return $this->conn_id->errorCode();
489 }
490
491 // --------------------------------------------------------------------
492
493 /**
494 * Escape the SQL Identifiers
495 *
496 * This function escapes column and table names
497 *
498 * @access private
499 * @param string
500 * @return string
501 */
502 function _escape_identifiers($item)
503 {
504 if ($this->_escape_char == '')
505 {
506 return $item;
507 }
508
509 foreach ($this->_reserved_identifiers as $id)
510 {
511 if (strpos($item, '.'.$id) !== FALSE)
512 {
513 $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item);
514
515 // remove duplicates if the user already included the escape
516 return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
517 }
518 }
519
520 if (strpos($item, '.') !== FALSE)
521 {
522 $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char;
Timothy Warren9f5316e2011-09-14 12:25:14 -0400523
Timothy Warren24f325c2011-10-07 10:03:01 -0400524 }
525 else
526 {
527 $str = $this->_escape_char.$item.$this->_escape_char;
528 }
529
530 // remove duplicates if the user already included the escape
531 return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
532 }
533
534 // --------------------------------------------------------------------
535
536 /**
537 * From Tables
538 *
539 * This function implicitly groups FROM tables so there is no confusion
540 * about operator precedence in harmony with SQL standards
541 *
542 * @access public
543 * @param type
544 * @return type
545 */
546 function _from_tables($tables)
547 {
548 if ( ! is_array($tables))
549 {
550 $tables = array($tables);
551 }
552
553 return (count($tables) == 1) ? $tables[0] : '('.implode(', ', $tables).')';
554 }
555
556 // --------------------------------------------------------------------
557
558 /**
559 * Insert statement
560 *
561 * Generates a platform-specific insert string from the supplied data
562 *
563 * @access public
564 * @param string the table name
565 * @param array the insert keys
566 * @param array the insert values
567 * @return string
568 */
569 function _insert($table, $keys, $values)
570 {
571 return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")";
572 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400573
Timothy Warren24f325c2011-10-07 10:03:01 -0400574 // --------------------------------------------------------------------
575
576 /**
577 * Insert_batch statement
578 *
579 * Generates a platform-specific insert string from the supplied data
580 *
581 * @access public
582 * @param string the table name
583 * @param array the insert keys
584 * @param array the insert values
585 * @return string
586 */
587 function _insert_batch($table, $keys, $values)
588 {
589 return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values);
590 }
591
592 // --------------------------------------------------------------------
593
594 /**
595 * Update statement
596 *
597 * Generates a platform-specific update string from the supplied data
598 *
599 * @access public
600 * @param string the table name
601 * @param array the update data
602 * @param array the where clause
603 * @param array the orderby clause
604 * @param array the limit clause
605 * @return string
606 */
607 function _update($table, $values, $where, $orderby = array(), $limit = FALSE)
608 {
609 foreach ($values as $key => $val)
610 {
611 $valstr[] = $key." = ".$val;
612 }
613
614 $limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
615
616 $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):'';
617
618 $sql = "UPDATE ".$table." SET ".implode(', ', $valstr);
619
620 $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : '';
621
622 $sql .= $orderby.$limit;
623
624 return $sql;
625 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400626
Timothy Warren24f325c2011-10-07 10:03:01 -0400627 // --------------------------------------------------------------------
628
629 /**
630 * Update_Batch statement
631 *
632 * Generates a platform-specific batch update string from the supplied data
633 *
634 * @access public
635 * @param string the table name
636 * @param array the update data
637 * @param array the where clause
638 * @return string
639 */
640 function _update_batch($table, $values, $index, $where = NULL)
641 {
642 $ids = array();
643 $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : '';
644
645 foreach ($values as $key => $val)
646 {
647 $ids[] = $val[$index];
648
649 foreach (array_keys($val) as $field)
650 {
651 if ($field != $index)
652 {
653 $final[$field][] = 'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
654 }
655 }
656 }
657
658 $sql = "UPDATE ".$table." SET ";
659 $cases = '';
660
661 foreach ($final as $k => $v)
662 {
663 $cases .= $k.' = CASE '."\n";
664 foreach ($v as $row)
665 {
666 $cases .= $row."\n";
667 }
668
669 $cases .= 'ELSE '.$k.' END, ';
670 }
671
672 $sql .= substr($cases, 0, -2);
673
674 $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';
675
676 return $sql;
677 }
678
679
680 // --------------------------------------------------------------------
681
682 /**
683 * Truncate statement
684 *
685 * Generates a platform-specific truncate string from the supplied data
686 * If the database does not support the truncate() command
687 * This function maps to "DELETE FROM table"
688 *
689 * @access public
690 * @param string the table name
691 * @return string
692 */
693 function _truncate($table)
694 {
695 return $this->_delete($table);
696 }
697
698 // --------------------------------------------------------------------
699
700 /**
701 * Delete statement
702 *
703 * Generates a platform-specific delete string from the supplied data
704 *
705 * @access public
706 * @param string the table name
707 * @param array the where clause
708 * @param string the limit clause
709 * @return string
710 */
711 function _delete($table, $where = array(), $like = array(), $limit = FALSE)
712 {
713 $conditions = '';
714
715 if (count($where) > 0 OR count($like) > 0)
716 {
717 $conditions = "\nWHERE ";
718 $conditions .= implode("\n", $this->ar_where);
719
720 if (count($where) > 0 && count($like) > 0)
721 {
722 $conditions .= " AND ";
723 }
724 $conditions .= implode("\n", $like);
725 }
726
727 $limit = ( ! $limit) ? '' : ' LIMIT '.$limit;
728
729 return "DELETE FROM ".$table.$conditions.$limit;
730 }
731
732 // --------------------------------------------------------------------
733
734 /**
735 * Limit string
736 *
737 * Generates a platform-specific LIMIT clause
738 *
739 * @access public
740 * @param string the sql query string
741 * @param integer the number of rows to limit the query to
742 * @param integer the offset value
743 * @return string
744 */
745 function _limit($sql, $limit, $offset)
746 {
747 if (strpos($this->hostname, 'cubrid') !== FALSE || strpos($this->hostname, 'sqlite') !== FALSE)
748 {
749 if ($offset == 0)
750 {
751 $offset = '';
752 }
753 else
754 {
755 $offset .= ", ";
756 }
757
758 return $sql."LIMIT ".$offset.$limit;
759 }
760 else
761 {
762 $sql .= "LIMIT ".$limit;
763
764 if ($offset > 0)
765 {
766 $sql .= " OFFSET ".$offset;
767 }
Timothy Warren9f5316e2011-09-14 12:25:14 -0400768
Timothy Warren24f325c2011-10-07 10:03:01 -0400769 return $sql;
770 }
771 }
772
773 // --------------------------------------------------------------------
774
775 /**
776 * Close DB Connection
777 *
778 * @access public
779 * @param resource
780 * @return void
781 */
782 function _close($conn_id)
783 {
784 $this->conn_id = null;
785 }
786
787
788}
789
790
791
792/* End of file pdo_driver.php */
793/* Location: ./system/database/drivers/pdo/pdo_driver.php */