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