EGOCMS  24.0
EGOTEC Content-Managament-System
Ego_Sql_Abstract.php
gehe zur Dokumentation dieser Datei
1 <?php
11 class Ego_Sql_Exception extends Exception
12 {
13  const QUERY_ERROR = 1;
14  const CONNECT_ERROR = 2;
15  const IMPORT_ERROR = 3;
16 }
17 
23 abstract class Ego_Sql_Abstract implements Iterator
24 {
25  protected $escapeChar = "`";
26  protected $_db;
27  public $currentQuery;
28  public $currentBind;
29  public $Record;
30  protected $_currentRow;
31  private $_cacheRecords;
32  private $_cacheIndex;
33  protected $_cached = false;
34  protected $_transaction = false;
72  abstract function connect($database='', $host='', $user='', $password='');
73 
98  function __construct($query=array(), $cache=false)
99  {
100  if (func_num_args() == 4)
101  {
102  // #206458 - über den Konstruktor andere Zugriffsdaten angeben
103  $args = array(
104  func_get_arg(0),
105  func_get_arg(1),
106  func_get_arg(2),
107  func_get_arg(3)
108  );
109  $this->connect($args[0], $args[1], $args[2], $args[3]);
110 
111  } else
112  {
113  $this->connect();
114  if ($query)
115  {
116  $this->select($query, $cache);
117  }
118  }
119  }
120 
121  function __toString()
122  {
123  return get_class($this).'('.$this->currentQuery.')';
124  }
125 
126 
143  function query($query, $appendCurrent = false)
144  {
145  if ($appendCurrent) {
146  $this->currentQuery .= $query;
147  } else {
148  $this->currentQuery = $query;
149  }
150  $this->_currentRow = -1;
151  $this->_stmt = $this->_db->query($query);
152  if ($this->_stmt === false) {
153  $this->rollback();
154  throw new Ego_Sql_Exception(implode(' ', $this->_db->errorInfo()), (int) $this->_db->errorCode());
155  }
156  return $this->_stmt;
157  }
158 
164  protected function _nextRecord()
165  {
167  $this->Record = $this->_stmt?$this->_stmt->fetch(PDO::FETCH_ASSOC):null;
168  if ($this->Record===false) {
169  $this->Record = null;
170  }
171  return $this->Record;
172  }
173 
177  protected function _rewind()
178  {
179  $this->_currentRow = -1;
180  $this->_stmt->closeCursor();
181  $this->_stmt->execute();
182  }
183 
211  function nextRecord()
212  {
213  set_time_limit(0); // Timeouts in Schleifen vermeiden.
214  if ($this->_cached)
215  { // Das Abfrageergebnis aus dem Cache laden.
217  ++$this->_cacheIndex;
218  if (is_array($this->_cacheRecords) && $this->_cacheIndex<sizeof($this->_cacheRecords))
219  {
220  return $this->Record = $this->_cacheRecords[$this->_cacheIndex];
221  }
222  return null;
223  }
224  return $this->_nextRecord();
225  }
226 
232  protected function _numRecords()
233  {
234  return $this->_stmt?$this->_stmt->rowCount():0;
235  }
236 
255  function numRecords()
256  {
257  return $this->_cached?sizeof($this->_cacheRecords):$this->_numRecords();
258  }
259 
260  protected function _selectString($query)
261  {
262  if ($query['bitand']) {
263  if (empty($query['where'])) {
264  $query['where'] = '1=1';
265  }
266  foreach ($query['bitand'] as $bit) {
267  $query['where'] .= ' AND '.$bit[0].'&'.$bit[1].'='.$bit[2];
268  }
269  }
270  return 'SELECT '.($query['no_cache']?'SQL_NO_CACHE ':'')
271  .($query['distinct']?'DISTINCT ':'')
272  .($query['fields']?:'*').(isset($query['fields2'])&&!empty($query['fields2'])?','.(is_array($query['fields2'])?implode(',',$query['fields2']):$query['fields2']):'')
273  .' FROM ('.($query['table']?:$query['from'])
274  .($query['table2']?($query['table']?',':'').$query['table2']:'').')'
275  .($query['inner']?' INNER JOIN '.(is_array($query['inner'])?
276  implode(' INNER JOIN ', $query['inner']):$query['inner']):'')
277  .($query['join']?' LEFT JOIN '.(is_array($query['join'])?
278  implode(' LEFT JOIN ', $query['join']):$query['join']):'')
279  .($query['where']?' WHERE '.$query['where']:'')
280  .($query['group']?' GROUP BY '.$query['group']:'')
281  .($query['having']?' HAVING '.$query['having']:'')
282  .($query['order']?' ORDER BY '.$query['order']:'');
283  }
284 
293  protected function _select($query)
294  {
295  if ($query['field_as'])
296  {
297  foreach ($query['field_as'] as $k=>$v)
298  {
299  if ($query['field_as_bitand'][$k]) {
300  foreach ($query['field_as_bitand'][$k] as $bit) {
301  $v .= ' AND '.$bit[0].'&'.$bit[1].'='.$bit[2];
302  }
303  }
304  if ($query['field_as_limit'][$k]) {
305  $v .= ' LIMIT '.$query['field_as_limit'][$k];
306  }
307  $query['fields2'][] = '('.$v.') AS '.$k;
308  }
309  unset($query['field_as']);
310  }
311  if ($query['proposed_order'])
312  {
313  $query['order'] = 'FIELD('.$query['proposed_order']['field'].','.implode(',', $query['proposed_order']['values']).')';
314  }
315  if ($query['union'])
316  {
317  $unions = array();
318  foreach ($query['union'] as $union)
319  {
320  $union = array_merge($query, $union);
321  $union['where'] = $query['where']?'('.$query['where'].') AND ('.$union['where'].')':$union['where'];
322  $union['join'] = $query['join']?array_merge_recursive($query['join'], $union['join']):$union['join'];
323  $unions[] = '('.$this->_selectString($union).')';
324  }
325  $s = implode(' UNION ', $unions).($query['order']?' order by'.preg_replace('/([, ])[^, .]+?\./', '$1', ' '.$query['order']):'');
326  } else {
327  $s = $this->_selectString($query);
328  }
329  if ($query['limit'])
330  {
331  $limit = explode(',', $query['limit']);
332  $s.= ' LIMIT '.$limit[0].(sizeof($limit)>1?','.$limit[1]:'');
333  }
334  if ($query['bind'])
335  { // Prepared Statements
336  $this->currentQuery = $s;
337  $this->currentBind = $query['bind'];
338  $this->_stmt = $this->_db->prepare($s);
339  if ($this->_stmt->execute($query['bind']) === false) {
340  $this->rollback();
341  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
342  }
343  return $this->_stmt;
344  } else {
345  return $this->query($s);
346  }
347  }
348 
405  function select($query, $cache=false)
406  {
407  assert(
408  !(bool)$query['limit'] || (bool)$query['order'],
409  'You have to set an order to use limit! Otherwise databases like MS SQL don`t work.'.
410  print_r($query, true));
411 
412  if ($cache && $this->_cacheInit($query, $cache))
413  {
414  return true;
415  }
416  $GLOBALS['stats']['db_select']++;
417  if ($GLOBALS['egotec_conf']['db']['log_select'])
418  { // Datenbank select Abfragen messen.
419  $start = microtime(true);
420  }
421  $return = $this->_select($query);
422  if ($GLOBALS['egotec_conf']['db']['log_select'] || $query['log'])
423  { // Datenbank select Abfragen aufzeichnen.
424  $time = microtime(true)-$start;
425  if ($time>$GLOBALS['egotec_conf']['db']['log_select_timeout'] || $query['log'])
426  {
427  $log = '=== '.date('Y-m-d H:i:s').' '.$_SERVER['REQUEST_URI'].' '.$time."\n".$this->currentQuery."\n".print_r($query, true);
428  if ($GLOBALS['egotec_conf']['db']['profiling'] || $query['profile'])
429  {
430  $log.= $this->getQueryInfo();
431  }
432  if ($GLOBALS['egotec_conf']['db']['log_select']==2)
433  { // Der Aufzeichnung ein Backtrace hinzufügen.
434  $log.= "\n".get_backtrace();
435  }
436  Ego_System::log('db_select-'.date('Y-m'), $log."\n"); // Es wird pro Monat eine Logdatei angelegt.
437  }
438  }
439  return $return;
440  }
441 
468  function delete($query)
469  {
470  assert($query['table']||$query['from']);
471  $s = 'DELETE '
472  .($query['fields']?$query['fields'].' ':'')
473  .'FROM '
474  .($query['table']?$query['table']:$query['from'])
475  .($query['inner']?' INNER JOIN '.(is_array($query['inner'])?
476  implode(' INNER JOIN ', $query['inner']):$query['inner']):'')
477  .($query['join']?' LEFT JOIN '.(is_array($query['join'])?
478  implode(' LEFT JOIN ', $query['join']):$query['join']):'')
479  .($query['where']?' WHERE '.$query['where']:'');
480  if ($query['bind'])
481  { // Prepared Statements
482  $this->currentQuery = $s;
483  $this->currentBind = $query['bind'];
484  $this->_stmt = $this->_db->prepare($s);
485  if ($this->_stmt->execute($query['bind']) === false) {
486  $this->rollback();
487  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
488  }
489  return $this->_stmt;
490  } else {
491  return $this->query($s);
492  }
493  }
494 
530  function update($query)
531  {
532  $field = array();
533  if (isset($query['key']))
534  {
535  $set = array_combine($query['key'], $query['val']);
536  unset($query['val']);
537  } elseif (isset($query['set'])) {
538  $query['key'] = array_keys($query['set']);
539  $set = $query['set'];
540  unset($query['set']);
541  }
542  if (is_array($query['key'])) {
543  foreach ($query['key'] as $k) { // prepared statement vorbereiten
544  $field[] = $this->escapeChar . $k . $this->escapeChar . '=:' . $k;
545  }
546  }
547  if (isset($query['set_f']))
548  {
549  foreach ($query['set_f'] as $k => $v)
550  {
551  $field[] = $this->escapeChar.$k.$this->escapeChar.'='.$v;
552  }
553  }
554  $s = 'UPDATE '.($query['ignore']?'IGNORE ':'').// Durch ein Update auftretende doppelte Einträge ignorieren.
555  $query['table'].
556  ' SET '.implode(',', $field).
557  ($query['where']?' WHERE '.$query['where']:'');
558  $this->currentQuery = array('query' => $s);
559  $this->_stmt = $this->_db->prepare($s);
560  $this->currentQuery['set'] = $set;
561  if ($query['bind']) {
562  $set = array_merge($set, $query['bind']);
563  }
564  if ($this->_stmt === false) {
565  $this->rollback();
566  throw new Ego_Sql_Exception(implode(' ', $this->_db->errorInfo()), (int) $this->_db->errorCode());
567  }
568  if ($this->_stmt->execute($set) === false) {
569  $this->rollback();
570  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
571  }
572  return $this->_stmt;
573  }
574 
603  function insert($query)
604  {
605  if (isset($query['key']))
606  {
607  $set = array_combine($query['key'], $query['val']);
608  unset($query['val']);
609  } else {
610  $query['key'] = array_keys($query['set']);
611  $set = $query['set'];
612  unset($query['set']);
613  }
614  $this->currentQuery = $query;
615  $insert_table = $query['table'].'-'.sizeof($query['key']).'-'.$query['replace'];
616  if ($this->_insertTable != $insert_table)
617  {
618  $s = ($query['replace']?'REPLACE':'INSERT')." INTO $query[table]".
619  " (".$this->escapeChar.implode($this->escapeChar.','.$this->escapeChar, $query['key']).$this->escapeChar.")".
620  " VALUES (:".implode(',:', $query['key']).")";
621  $this->_insertstmt = $this->_db->prepare($s);
622  $this->_insertTable = $insert_table;
623  }
624  $this->currentQuery['set'] = $set;
625  if ($this->_insertstmt === false) {
626  $this->rollback();
627  throw new Ego_Sql_Exception(implode(' ', $this->_db->errorInfo()), (int) $this->_db->errorCode());
628  }
629  if ($this->_insertstmt->execute($set) === false) {
630  $this->rollback();
631  throw new Ego_Sql_Exception(implode(' ', $this->_insertstmt->errorInfo()), (int) $this->_insertstmt->errorCode());
632  }
633  return $this->_insertstmt;
634  }
635 
656  function replace($query)
657  {
658  assert(!$query['replace'] || $query['primary']);
659  return $this->insert(array_merge($query, array('replace' => true)));
660  }
661 
674  abstract function showColumns($table);
675 
681  function current()
682  {
683  return $this->Record;
684  }
685 
691  function next()
692  {
693  return $this->nextRecord();
694  }
695 
701  function key()
702  {
703  return $this->_currentRow;
704  }
705 
709  function valid()
710  {
711  return isset($this->Record);
712  }
713 
719  function rewind()
720  {
721  if ($this->_cached)
722  {
723  $this->_currentRow = -1;
724  $this->_cacheIndex = -1;
725  } else {
726  $this->_rewind();
727  }
728  return $this->next();
729  }
730 
737  function numRows()
738  {
739  return $this->numRecords();
740  }
741 
750  protected function _cacheInit($query, $cache)
751  {
752  if (!isset($GLOBALS['egotec_conf']['no_query_cache']) || $GLOBALS['egotec_conf']['no_query_cache']!=1)
753  {
754  $query_hash = $query['hash']?$query['hash']:md5(serialize($query));
755  $this->_cacheRecords = $cache->get($query_hash);
756  if ($this->_cacheRecords === null)
757  { // Nicht im Cache, dann von der Datenbank erfragen.
758  $this->select($query);
759  $this->_cacheRecords = array();
760  while ($this->nextRecord())
761  {
762  $this->_cacheRecords[] = $this->Record;
763  }
764  $cache->set($query_hash, $this->_cacheRecords);
765  } else {
766  $GLOBALS['stats']['db_cache']++;
767  }
768  $this->_cached = true;
769  $this->_cacheIndex = -1;
770  $this->_currentRow = -1;
771  return true;
772  } else {
773  return false;
774  }
775  }
776 
790  function export($table)
791  {
792  $content = array();
793  $db = clone $this;
794  $db->select(array('table' => $table));
795  while ($db->nextRecord())
796  {
797  $content[] = $db->Record;
798  }
799  return $content;
800  }
801 
817  function import($table, $content, $primary=array(), $c_date_fieldname='', $c_date_value='')
818  {
819  if (!is_array($content))
820  {
821  throw new Ego_Sql_Exception('Der Parameter $content muss ein array sein!', Ego_Sql_Exception::IMPORT_ERROR);
822  }
823  if (!$primary)
824  {
825  $primary = $this->getPrimary($table);
826  }
827 
828  $db = clone $this;
829  $db->begin();
830  $db->select(array('table' => $table));
831  $old_content = array();
832  while ($db->nextRecord())
833  {
834  $old_content[md5(serialize($db->Record))] = $db->Record;
835  }
836  $insert = array();
837  foreach ($content as $record)
838  {
839  $md5 = md5(serialize($record));
840  if ($old_content[$md5])
841  { // Falls ein Eintrag schon vorhanden ist,
842  //egotec_error_log("=eq=".serialize($record));
843  unset($old_content[$md5]); // muss nichts gemacht werden.
844  } else { // Ist ein Eintrag noch nicht vorhanden,
845  if (sizeof($primary)==1)
846  {
847  $keys = array_keys($primary);
848  $insert[$record[$keys[0]]] = $record;
849  } else {
850  $insert[] = $record;
851  }
852  }
853  }
854  foreach ($old_content as $record)
855  {
856  if (!$c_date_fieldname || $record[$c_date_fieldname]<$c_date_value)
857  {
858  //egotec_error_log("=rm=".serialize($record));
859  $where = array();
860  $bind = array();
861  foreach ($record as $key=>$val)
862  {
863  if ($primary)
864  {
865  if ($primary[$key])
866  {
867  if ($primary[$key]=='string')
868  {
869  $where[] = $key.'=\''.$val.'\'';
870  } else {
871  $where[] = $key.'='.$val;
872  }
873  }
874  } else {
875  if (is_numeric($val))
876  {
877  $where[] = $key.'='.$val;
878  } else {
879  $where[] = $key.'=\''.$val.'\'';
880  }
881  }
882  }
883  $where = implode(' AND ', $where);
884  $db->delete(array(
885  'table' => $table,
886  'where' => $where
887  ));
888  } else {
889  $record[$c_date_fieldname] = date('Y-m-d H:i:s');
890  $db->replace(array(
891  'table' => $table,
892  'set' => $record,
893  'primary' => $primary
894  ));
895  if (sizeof($primary)==1)
896  {
897  $keys = array_keys($primary);
898  unset($insert[$record[$keys[0]]]);
899  }
900  }
901  }
902  foreach ($insert as $record)
903  {
904  try { // Um duplicate entries zu vermeiden, erfolgen die insertes erst nach den deletes.
905  $db->insert(array('set' => $record, 'table' => $table)); // wo wird dieser eingefügt.
906  } catch (Exception $e)
907  {
908  egotec_error_log($e->getMessage());
909  }
910  }
911  $db->commit();
912  }
913 
940  abstract function createTable($table, $struct, $drop_flag=true);
941 
966  abstract function alterTable($table, $struct);
967 
980  function dropTable($table)
981  {
982  $this->query('DROP TABLE '.$table);
983  }
984 
988  function begin()
989  {}
990 
994  function commit()
995  {}
996 
1000  function rollback()
1001  {}
1002 
1008  function repair($table)
1009  {
1010  ;
1011  }
1012 
1020  function getQueryInfo()
1021  {
1022  ;
1023  }
1024 
1030  function tableExists($table)
1031  {
1032  ;
1033  }
1034 
1038  function optimize($table)
1039  {
1040  ;
1041  }
1042 
1049  protected function onError($action) {
1050  $file = $GLOBALS['egotec_conf']['var_dir'].'lib/db.php';
1051  if (Ego_System::file_exists($file)) {
1052  require_once($file);
1053  $func = 'db_'.$action;
1054  if (function_exists($func)) {
1055  $func($this);
1056  }
1057  }
1058  egotec_error_log('Connection failed');
1059  return null;
1060  }
1061 
1068  public abstract function getPrimary($table);
1069 
1087  public function getTables($like = '')
1088  {
1089  if ($like) {
1090  $this->query("SHOW TABLES FROM ".$GLOBALS['egotec_conf']['db']['database']." LIKE '$like%'");
1091  } else {
1092  $this->query("SHOW TABLES FROM ".$GLOBALS['egotec_conf']['db']['database']);
1093  }
1094 
1095  $all_table = array();
1096  $count = 0;
1097 
1098  while($table = $this->nextRecord()) {
1099  $all_table[$count++] = array_pop($this->Record);
1100  }
1101  return $all_table;
1102  }
1103 
1104  public abstract function getVersion();
1105 }
1106 ?>
alterTable($table, $struct)
__construct($query=array(), $cache=false)
createTable($table, $struct, $drop_flag=true)
query($query, $appendCurrent=false)
connect($database='', $host='', $user='', $password='')
getPrimary($table)
_cacheInit($query, $cache)
select($query, $cache=false)
showColumns($table)
static file_exists($file)
static log($file, $message)