New version submitted by TomB
[carbonphp.git] / Source / carbon / database / Database_active_record.php
blob0e76181fa11708ad96e83709d44f759d30e175c2
1 <?php
2 /*------------------------------------------------------------
3 * CarbonPHP framework (C) Tom Bell
4 * http://tombell.org.uk
5 *------------------------------------------------------------*/
7 if (!defined('CARBON_PATH'))
9 exit('Direct script access is not allowed.');
12 class Carbon_Database_active_record extends Carbon_Database_driver
14 protected $ar_select = array();
15 protected $ar_distinct = false;
16 protected $ar_from = array();
17 protected $ar_join = array();
18 protected $ar_where = array();
19 protected $ar_like = array();
20 protected $ar_groupby = array();
21 protected $ar_having = array();
22 protected $ar_limit = false;
23 protected $ar_offset = false;
24 protected $ar_order = false;
25 protected $ar_orderby = array();
26 protected $ar_set = array();
27 protected $ar_wherein = array();
28 protected $ar_aliased_tables = array();
29 protected $ar_store_array = array();
31 protected $ar_caching = false;
32 protected $ar_cache_select = array();
33 protected $ar_cache_from = array();
34 protected $ar_cache_join = array();
35 protected $ar_cache_where = array();
36 protected $ar_cache_like = array();
37 protected $ar_cache_groupby = array();
38 protected $ar_cache_having = array();
39 protected $ar_cache_limit = false;
40 protected $ar_cache_offset = false;
41 protected $ar_cache_order = false;
42 protected $ar_cache_orderby = array();
43 protected $ar_cache_set = array();
45 public function dbprefix($table = '')
47 if ($table == '')
49 $this->display_error('database_table_name_required');
52 return $this->dbprefix . $table;
55 public function select($select = '*', $protected_identifiers = true)
57 if (is_string($select))
59 $select = explode(', ', $select);
62 foreach ($select as $value)
64 $value = trim($value);
66 if ($value != '*' && $protected_identifiers !== false)
68 if (strpos($value, '.') !== false)
70 $value = $this->dbprefix . $value;
72 else
74 $value = $this->database_protect_identifiers($value);
78 if ($value != '')
80 $this->ar_select[] = $value;
82 if ($this->ar_caching === true)
84 $this->ar_cache_select[] = $value;
89 return $this;
92 public function select_max($select = '', $alias = '')
94 if (!is_string($select) || $select = '')
96 $this->display_error('database_invalid_query');
99 $alias = ($alias != '') ? $alias : $select;
101 $sql = 'MAX(' . $this->database_protect_identifiers(trim($select)) . ') AS ' . $this->database_protect_identifiers(trim($alias));
103 $this->ar_select[] = $sql;
105 if ($this->ar_caching === true)
107 $this->ar_cache_select[] = $sql;
110 return $this;
113 public function select_min($select = '', $alias = '')
115 if (!is_string($select) || $select = '')
117 $this->display_error('database_invalid_query');
120 $alias = ($alias != '') ? $alias : $select;
122 $sql = 'MIN(' . $this->database_protect_identifiers(trim($select)) . ') AS ' . $this->database_protect_identifiers(trim($alias));
124 $this->ar_select[] = $sql;
126 if ($this->ar_caching === true)
128 $this->ar_cache_select[] = $sql;
131 return $this;
134 public function select_avg($select = '', $alias = '')
136 if (!is_string($select) || $select = '')
138 $this->display_error('database_invalid_query');
141 $alias = ($alias != '') ? $alias : $select;
143 $sql = 'AVG(' . $this->database_protect_identifiers(trim($select)) . ') AS ' . $this->database_protect_identifiers(trim($alias));
145 $this->ar_select[] = $sql;
147 if ($this->ar_caching === true)
149 $this->ar_cache_select[] = $sql;
152 return $this;
155 public function select_sum($select = '', $alias = '')
157 if (!is_string($select) || $select = '')
159 $this->display_error('database_invalid_query');
162 $alias = ($alias != '') ? $alias : $select;
164 $sql = 'SUM(' . $this->database_protect_identifiers(trim($select)) . ') AS ' . $this->database_protect_identifiers(trim($alias));
166 $this->ar_select[] = $sql;
168 if ($this->ar_caching === true)
170 $this->ar_cache_select[] = $sql;
173 return $this;
176 public function distinct($value = true)
178 $this->ar_distinct = (is_bool($value)) ? $value : true;
179 return $this;
182 public function from($from)
184 foreach ((array) $from as $value)
186 $this->ar_from[] = $this->database_protect_identifiers($this->database_track_aliases($value));
188 if ($this->ar_caching === true)
190 $this->ar_caching_from[] = $this->database_protect_identifiers($value);
194 return $this;
197 public function join($table, $condition, $type = '')
199 if ($type != '')
201 $type = strtoupper(trim($type));
203 if (!in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), true))
205 $type = '';
207 else
209 $type .= ' ';
213 if ($this->dbprefix)
215 $this->database_track_aliases($table);
216 $condition = preg_replace('|(' . $this->dbprefix . ')([\w\.]+)([\W\s]+)|', "$2$3", $condition);
217 $condition = preg_replace('|([\w\.]+)([\W\s]+)(.+)|', $this->dbprefix . "$1$2" . $this->dbprefix . "$3", $condition);
220 $join = $type . 'JOIN' . $this->database_protect_identifiers($this->dbprefix . $table, true) . ' ON ' . $condition;
221 $this->ar_join[] = $join;
223 if ($this->ar_caching === true)
225 $this->ar_cache_join[] = $join;
228 return $this;
231 public function where($key, $value = null, $escape = true)
233 return $this->database_where($key, $value, 'AND ', $escape);
236 public function or_where($key, $value = null, $escape = true)
238 return $this->database_where($key, $value, 'OR ', $escape);
241 private function database_where($key, $value = null, $type = 'AND ', $escape = true)
243 if (!is_array($key))
245 $key = array($key => $value);
248 foreach ($key as $k => $v)
250 $prefix = (count($this->ar_where) == 0) ? '' : $type;
252 if (!$this->database_has_operator($k) && is_null($key[$k]))
254 $k .= ' IS NULL';
257 if (!is_null($v))
259 if ($escape === true)
261 if ($this->database_has_operator($k))
263 $k = preg_replace("/([A-Za-z_0-9]+)/", $this->database_protect_identifiers('$1'), $k);
265 else
267 $k = $this->database_protect_identifiers($k);
271 if (!$this->database_has_operator($k))
273 $k .= ' =';
276 $v = ' ' . $this->escape($v);
278 else
280 $k = $this->database_protect_identifiers($k, true);
283 $this->ar_where[] = $prefix . $k . $v;
285 if ($this->ar_caching === true)
287 $this->ar_cache_where[] = $prefix . $k . $v;
291 return $this;
294 public function where_in($key = null, $values = null)
296 return $this->database_where_in($key, $values);
299 public function where_in_or($key = null, $values = null)
301 return $this->database_where_in($key, $values, false, 'OR ');
304 public function where_not_in($key = null, $values = null)
306 return $this->database_where_in($key, $values, true);
309 public function or_where_not_in($key = null, $values = null)
311 return $this->database_where_in($key, $values, false, 'OR ');
314 public function database_where_in($key = null, $values = null, $not = false, $type = 'AND ')
316 if ($key === null || !is_array($values))
318 return;
321 $not = ($not) ? ' NOT ' : '';
323 foreach ($values as $value)
325 $this->ar_wherein[] = $this->escape($value);
328 $prefix = (count($this->ar_where) == 0) ? '' : $type;
330 $where_in = $prefix . $this->protect_identifiers($key) . $not . ' IN (' . implode(', ', $this->ar_wherein) . ') ';
332 $this->ar_where[] = $where_in;
334 if ($this->ar_caching === true)
336 $this->ar_cache_where[] = $where_in;
339 $this->ar_wherein = array();
340 return $this;
343 public function like($field, $match = '', $side = 'both')
345 return $this->database_like($field, $match, 'AND ', $side);
348 public function not_like($field, $match = '', $side = 'both')
350 return $this->database_like($field, $match, 'AND ', $side, ' NOT');
353 public function or_like($field, $match = '', $side = 'both')
355 return $this->database_like($field, $match, 'ON ', $side);
358 public function or_not_like($field, $match = '', $side = 'both')
360 return $this->database_like($field, $match, 'OR ', $side, 'NOT ');
363 private function database_like($field, $match = '', $type = 'AND ', $side = 'both', $not = '')
365 if (!is_array($field))
367 $field = array($field => $match);
370 foreach ($field as $k => $v)
372 $k = $this->database_protect_identifiers($k);
374 $prefix = (count($this->ar_like) == 0) ? '' : $type;
376 $v = $this->escape_string($v);
378 if ($side == 'before')
380 $like_statement = $prefix . " $k $not LIKE '%{$v}'";
382 else if ($side == 'after')
384 $like_statement = $prefix . " $k $not LIKE '{$v}%'";
386 else
388 $like_statement = $prefix . " $k $not LIKE '%{$v}%'";
391 $this->ar_like[] = $like_statement;
393 if ($this->ar_caching === true)
395 $this->ar_cache_like[] = $like_statement;
399 return $this;
402 public function group_by($by)
404 if (is_string($by))
406 $by = explode(',', $by);
409 foreach ($by as $val)
411 $val = trim($val);
413 if ($val != '')
415 $this->ar_groupby[] = $this->database_protect_identifiers($val);
417 if ($this->ar_caching === true)
419 $this->ar_cache_groupby[] = $this->database_protect_identifiers($val);
424 return $this;
427 public function having($key, $value = '')
429 return $this->database_having($key, $value, 'AND ');
432 public function or_having($key, $value = '')
434 return $this->database_having($key, $value, 'ON ');
437 private function database_having($key, $value, $type = 'AND ')
439 if (!is_array($key))
441 $key = array($key => $value);
444 foreach ($key as $k => $v)
446 $prefix = (count($this->ar_having) == 0) ? '' : $type;
447 $k = $this->database_protect_identifiers($k);
449 if ($v != '')
451 $v = ' ' . $this->escape($v);
454 $this->ar_having[] = $prefix . $k . $v;
456 if ($this->ar_caching === true)
458 $this->ar_cache_having[] = $prefix . $k . $v;
462 return $this;
465 public function order_by($orderby, $direction = '')
467 if (strtolower($direction) == 'random')
469 $orderby = '';
470 $direction = $this->random_keyword;
472 else if (trim($direction) != '')
474 $direction = (in_array(strtoupper(trim($direction)), array('ASC', 'DESC', 'RAND()'), true)) ? ' ' . $direction : ' ASC';
477 $orderby_statement = $this->database_protect_identifiers($orderby, true) . $direction;
478 $this->ar_orderby[] = $orderby_statement;
480 if ($this->ar_caching === true)
482 $this->ar_cache_orderby[] = $orderby_statement;
485 return $this;
488 public function limit($value, $offset = '')
490 $this->ar_limit = $value;
492 if ($this->ar_caching === true)
494 $this->ar_cache_limit[] = $value;
497 if ($offset != '')
499 $this->ar_offset = $offset;
501 if ($this->ar_caching === true)
503 $this->ar_cache_offset[] = $offset;
507 return $this;
510 public function offset($value)
512 $this->ar_offset = $value;
514 if ($this->ar_caching === true)
516 $this->ar_cache_offset[] = $value;
519 return $this;
522 public function set($key, $value = '', $escape = true)
524 $key = $this->database_object_to_array($key);
526 if (!is_array($key))
528 $key = array($key => $value);
531 foreach ($key as $k => $v)
533 if ($escape === false)
535 $this->ar_set[$this->database_protect_identifiers($k)] = $v;
537 if ($this->ar_caching === true)
539 $this->ar_cache_set[$this->database_protect_identifiers($k)] = $v;
542 else
544 $this->ar_set[$this->database_protect_identifiers($k)] = $this->escape($v);
546 if ($this->ar_caching === true)
548 $this->ar_cache_set[$this->database_protect_identifiers($k)] = $this->escape($v);
553 return $this;
556 public function get($table = '', $limit = null, $offset = null)
558 if ($table != '')
560 $this->database_track_aliases($table);
561 $this->from($table);
564 if (!is_null($limit))
566 $this->limit($limit, $offset);
569 $sql = $this->database_compile_select();
571 $result = $this->query($sql);
572 $this->database_reset_select();
574 return $result;
577 public function count_all_results($table = '')
579 if ($table != '')
581 $this->database_track_aliases($table);
582 $this->from($table);
585 $sql = $this->database_compile_select($this->count_string . $this->database_protect_identifiers('numrows'));
587 $query = $this->query($sql);
588 $this->database_reset_select();
590 if ($query->num_rows() == 0)
592 return '0';
595 $row = $query->row();
596 return $row->numrows;
599 public function get_where($table = '', $where = null, $limit = null, $offset = null)
601 if ($table != '')
603 $this->database_track_aliases($table);
604 $this->from($table);
607 if (!is_null($where))
609 $this->where($where);
612 if (!is_null($limit))
614 $this->limit($limit, $offset);
617 $sql = $this->database_compile_select();
619 $result = $this->query($sql);
620 $this->database_reset_select();
622 return $result;
625 public function insert($table = '', $set = null)
627 if (!is_null($set))
629 $this->set($set);
632 if (count($this->ar_set) == 0)
634 if ($this->db_debug)
636 return $this->display_error('database_must_use_set');
639 return false;
642 if ($table == '')
644 if (!isset($this->ar_from[0]))
646 if ($this->db_debug)
648 return $this->display_error('database_must_set_table');
651 return false;
654 $table = $this->ar_from[0];
657 $sql = $this->database_insert($this->database_protect_identifiers($this->dbprefix . $table), array_keys($this->ar_set), array_values($this->ar_set));
659 $this->database_reset_write();
660 return $this->query($sql);
663 public function update($table = '', $set = null, $where = null, $limit = null)
665 if (!is_null($set))
667 $this->set($set);
670 if (count($this->ar_set) == 0)
672 if ($this->db_debug)
674 return $this->display_error('database_must_use_set');
677 return false;
680 if ($table == '')
682 if (!isset($this->ar_from[0]))
684 if ($this->db_debug)
686 return $this->display_error('database_must_set_table');
689 return false;
692 $table = $this->ar_from[0];
695 if ($where != null)
697 $this->where($where);
700 if ($limit != null)
702 $this->limit($limit);
706 $sql = $this->database_update($this->database_protect_identifiers($this->dbprefix . $table), $this->ar_set, $this->ar_where, $this->ar_orderby, $this->ar_limit);
708 $this->database_reset_write();
709 return $this->query($sql);
712 public function empty_table($table = '')
714 if ($table == '')
716 if (!isset($this->ar_from[0]))
718 if ($this->db_debug)
720 return $this->display_error('database_must_set_table');
723 return false;
726 $table = $this->ar_from[0];
728 else
730 $table = $this->database_protect_identifiers($this->dbprefix . $table);
733 $sql = $this->database_delete($table);
734 $this->database_reset_write();
736 return $this->query($sql);
739 public function truncate($table = '')
741 if ($table == '')
743 if (!isset($this->ar_from[0]))
745 if ($this->db_debug)
747 return $this->display_error('database_must_set_table');
750 return false;
753 $table = $this->ar_from[0];
755 else
757 $table = $this->database_protect_identifiers($this->dbprefix . $table);
760 $sql = $this->database_truncate($table);
761 $this->database_reset_write();
763 return $this->query($sql);
766 public function delete($table = '', $where = '', $limit = null, $reset_data = true)
768 if ($table == '')
770 if (!isset($this->ar_from[0]))
772 if ($this->db_debug)
774 return $this->display_error('database_must_set_table');
777 return false;
780 $table = $this->ar_from[0];
782 else if (is_array($table))
784 foreach ($table as $single_table)
786 $this->delete($single_table, $where, $limit, false);
789 $this->database_reset_write();
790 return;
792 else
794 $table = $this->database_protect_identifiers($this->dbprefix . $table);
797 if ($where != '')
799 $this->where($where);
802 if ($limit != null)
804 $this->limit($limit);
807 if (count($this->ar_where) == 0 && count($this->ar_like) == 0)
809 if ($this->db_debug)
811 return $this->display_error('database_del_must_use_where');
814 return false;
817 $sql = $this->database_delete($this->dbprefix . $table, $this->ar_where, $this->ar_like, $this->ar_limit);
818 $this->database_reset_write();
820 if ($reset_data)
822 $this->database_reset_write();
825 return $this->query($sql);
828 public function database_has_operator($string)
830 $string = trim($string);
832 if (!preg_match("/(\s|<|>|!|=|is null|is not null)/i", $string))
834 return false;
837 return true;
840 private function database_track_aliases($table)
842 if (strpos($table, ' ') !== false)
844 $table = preg_replace('/ AS /i', ' ', $table);
845 $this->ar_aliased_tables[] = trim(strrchr($table, ' '));
848 return $this->dbprefix . $table;
851 private function database_filter_table_aliases($statements)
853 foreach ($statements as $k => $v)
855 foreach ($this->ar_aliased_tables as $table)
857 $statement = preg_replace('/(\w+\.\w+)/', $this->database_protect_identifiers('$0'), $v);
858 $statement = str_replace(array($this->dbprefix . $table, '.'), array($table, $this->database_protect_identifiers('.')), $statement);
861 $statements[$k] = $statement;
864 return $statements;
867 private function database_compile_select($select_override = false)
869 $this->database_merge_cache();
871 $sql = (!$this->ar_distinct) ? 'SELECT ' : 'SELECT DISTINCT ';
873 $sql .= (count($this->ar_select) == 0) ? '*' : implode(', ', $this->ar_select);
875 if ($select_override !== false)
877 $sql = $select_override;
880 if (count($this->ar_from) > 0)
882 $sql .= "\nFROM ";
883 $sql .= $this->database_from_tables($this->ar_from);
886 if (count($this->ar_join) > 0)
888 $sql .= "\n";
890 if (count($this->ar_aliased_table) > 0 && $this->dbprefix)
892 $sql .= implode("\n", $this->database_filter_table_aliases($this->ar_join));
894 else
896 $sql .= implode("\n", $this->ar_join);
900 if (count($this->ar_where) > 0 || count($this->ar_like) > 0)
902 $sql .= "\nWHERE ";
905 $sql .= implode("\n", $this->ar_where);
907 if (count($this->ar_like) > 0)
909 if (count($this->ar_where) > 0)
911 $sql .= " AND ";
914 $sql .= implode("\n", $this->ar_like);
917 if (count($this->ar_groupby) > 0)
919 $sql .= "\nGROUP BY ";
921 if (count($this->ar_aliased_tables) > 0 && $this->dbprefix)
923 $sql .= implode(', ', $this->database_filter_table_aliases($this->ar_groupby));
925 else
927 $sql .= implode(', ', $this->ar_groupby);
931 if (count($this->ar_having) > 0)
933 $sql .= "\nHAVING ";
934 $sql .= implode("\n", $this->ar_having);
937 if (count($this->ar_orderby) > 0)
939 $sql .= "\nORDER BY ";
940 $sql .= implode(', ', $this->ar_orderby);
942 if ($this->ar_order !== false)
944 $sql .= ($this->ar_order == 'desc') ? ' DESC' : ' ASC';
948 if (is_numeric($this->ar_limit))
950 $sql .= "\n";
951 $sql = $this->database_limit($sql, $this->ar_limit, $this->ar_offset);
954 return $sql;
957 private function database_object_to_array($object)
959 if (!is_object($object))
961 return $object;
964 $array = array();
966 foreach (get_object_vars($object) as $key => $val)
968 if (!is_object($val) && !is_array($val) && $key != 'c_scaffolding' && $key != 'c_scaff_table')
970 $array[$key] = $val;
974 return $array;
977 public function start_cache()
979 $this->ar_caching = true;
982 public function stop_cache()
984 $this->ar_caching = false;
987 public function flush_cache()
989 $ar_reset_items = array(
990 'ar_cache_select' => array(),
991 'ar_cache_from' => array(),
992 'ar_cache_join' => array(),
993 'ar_cache_where' => array(),
994 'ar_cache_like' => array(),
995 'ar_cache_groupby' => array(),
996 'ar_cache_having' => array(),
997 'ar_cache_orderby' => array(),
998 'ar_cache_set' => array()
1001 $this->database_reset_run($ar_reset_items);
1004 private function database_merge_cache()
1006 $ar_items = array('select', 'from', 'join', 'where', 'like', 'groupby', 'having', 'orderby', 'set');
1008 foreach ($ar_items as $ar_item)
1010 $ar_cache_item = 'ar_cache_' . $ar_item;
1011 $ar_item = 'ar_' . $ar_item;
1012 $this->$ar_item = array_unique(array_merge($this->$ar_item, $this->$ar_cache_item));
1016 private function database_reset_run($ar_reset_items)
1018 foreach ($ar_reset_items as $item => $default_value)
1020 if (!in_array($item, $this->ar_store_array))
1022 $this->$item = $default_value;
1027 private function database_reset_select()
1029 $ar_reset_items = array(
1030 'ar_select' => array(),
1031 'ar_from' => array(),
1032 'ar_join' => array(),
1033 'ar_where' => array(),
1034 'ar_like' => array(),
1035 'ar_groupby' => array(),
1036 'ar_having' => array(),
1037 'ar_orderby' => array(),
1038 'ar_wherein' => array(),
1039 'ar_aliased_tables' => array(),
1040 'ar_distinct' => false,
1041 'ar_limit' => false,
1042 'ar_offset' => false,
1043 'ar_order' => false
1046 $this->database_reset_run($ar_reset_items);
1049 private function database_reset_write()
1051 $ar_reset_items = array(
1052 'ar_set' => array(),
1053 'ar_from' => array(),
1054 'ar_where' => array(),
1055 'ar_like' => array(),
1056 'ar_orderby' => array(),
1057 'ar_limit' => false,
1058 'ar_order' => false
1061 $this->database_reset_run($ar_reset_items);