EGOCMS  18.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  {
166  ++$this->_currentRow;
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.
216  ++$this->_currentRow;
217  ++$this->_cacheIndex;
218  if ($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']?$query['fields']:'*').($query['fields2']?','.$query['fields2']:'')
273  .' FROM ('.($query['table']?$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  $query['fields2'] = ($query['fields2']?$query['fields2'].',':'').'('.$v.') AS '.$k;
305  }
306  unset($query['field_as']);
307  }
308  if ($query['proposed_order'])
309  {
310  $query['order'] = 'FIELD('.$query['proposed_order']['field'].','.implode(',', $query['proposed_order']['values']).')';
311  }
312  if ($query['union'])
313  {
314  $unions = array();
315  foreach ($query['union'] as $union)
316  {
317  $union = array_merge($query, $union);
318  $union['where'] = $query['where']?'('.$query['where'].') AND ('.$union['where'].')':$union['where'];
319  $union['join'] = $query['join']?array_merge_recursive($query['join'], $union['join']):$union['join'];
320  $unions[] = '('.$this->_selectString($union).')';
321  }
322  $s = implode(' UNION ', $unions).($query['order']?' order by'.preg_replace('/([, ])[^, .]+?\./', '$1', ' '.$query['order']):'');
323  } else {
324  $s = $this->_selectString($query);
325  }
326  if ($query['limit'])
327  {
328  $limit = explode(',', $query['limit']);
329  $s.= ' LIMIT '.$limit[0].(sizeof($limit)>1?','.$limit[1]:'');
330  }
331  if ($query['bind'])
332  { // Prepared Statements
333  $this->currentQuery = $s;
334  $this->currentBind = $query['bind'];
335  $this->_stmt = $this->_db->prepare($s);
336  if ($this->_stmt->execute($query['bind']) === false) {
337  $this->rollback();
338  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
339  }
340  return $this->_stmt;
341  } else {
342  return $this->query($s);
343  }
344  }
345 
402  function select($query, $cache=false)
403  {
404  assert(
405  !(bool)$query['limit'] || (bool)$query['order'],
406  'You have to set an order to use limit! Otherwise databases like MS SQL don`t work.'.
407  print_r($query, true));
408 
409  if ($cache && $this->_cacheInit($query, $cache))
410  {
411  return true;
412  }
413  $GLOBALS['stats']['db_select']++;
414  if ($GLOBALS['egotec_conf']['db']['log_select'])
415  { // Datenbank select Abfragen messen.
416  $start = microtime(true);
417  }
418  $return = $this->_select($query);
419  if ($GLOBALS['egotec_conf']['db']['log_select'] || $query['log'])
420  { // Datenbank select Abfragen aufzeichnen.
421  $time = microtime(true)-$start;
422  if ($time>$GLOBALS['egotec_conf']['db']['log_select_timeout'] || $query['log'])
423  {
424  $log = '=== '.date('Y-m-d H:i:s').' '.$_SERVER['REQUEST_URI'].' '.$time."\n".$this->currentQuery."\n".print_r($query, true);
425  if ($GLOBALS['egotec_conf']['db']['profiling'] || $query['profile'])
426  {
427  $log.= $this->getQueryInfo();
428  }
429  if ($GLOBALS['egotec_conf']['db']['log_select']==2)
430  { // Der Aufzeichnung ein Backtrace hinzufügen.
431  $log.= "\n".get_backtrace();
432  }
433  Ego_System::log('db_select-'.date('Y-m'), $log."\n"); // Es wird pro Monat eine Logdatei angelegt.
434  }
435  }
436  return $return;
437  }
438 
465  function delete($query)
466  {
467  assert($query['table']||$query['from']);
468  $s = 'DELETE '
469  .($query['fields']?$query['fields'].' ':'')
470  .'FROM '
471  .($query['table']?$query['table']:$query['from'])
472  .($query['inner']?' INNER JOIN '.(is_array($query['inner'])?
473  implode(' INNER JOIN ', $query['inner']):$query['inner']):'')
474  .($query['join']?' LEFT JOIN '.(is_array($query['join'])?
475  implode(' LEFT JOIN ', $query['join']):$query['join']):'')
476  .($query['where']?' WHERE '.$query['where']:'');
477  if ($query['bind'])
478  { // Prepared Statements
479  $this->currentQuery = $s;
480  $this->currentBind = $query['bind'];
481  $this->_stmt = $this->_db->prepare($s);
482  if ($this->_stmt->execute($query['bind']) === false) {
483  $this->rollback();
484  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
485  }
486  return $this->_stmt;
487  } else {
488  return $this->query($s);
489  }
490  }
491 
527  function update($query)
528  {
529  $field = array();
530  if (isset($query['key']))
531  {
532  $set = array_combine($query['key'], $query['val']);
533  unset($query['val']);
534  } elseif (isset($query['set'])) {
535  $query['key'] = array_keys($query['set']);
536  $set = $query['set'];
537  unset($query['set']);
538  }
539  if (is_array($query['key'])) {
540  foreach ($query['key'] as $k) { // prepared statement vorbereiten
541  $field[] = $this->escapeChar . $k . $this->escapeChar . '=:' . $k;
542  }
543  }
544  if (isset($query['set_f']))
545  {
546  foreach ($query['set_f'] as $k => $v)
547  {
548  $field[] = $this->escapeChar.$k.$this->escapeChar.'='.$v;
549  }
550  }
551  $s = 'UPDATE '.($query['ignore']?'IGNORE ':'').// Durch ein Update auftretende doppelte Einträge ignorieren.
552  $query['table'].
553  ' SET '.implode(',', $field).
554  ($query['where']?' WHERE '.$query['where']:'');
555  $this->currentQuery = array('query' => $s);
556  $this->_stmt = $this->_db->prepare($s);
557  $this->currentQuery['set'] = $set;
558  if ($query['bind']) {
559  $set = array_merge($set, $query['bind']);
560  }
561  if ($this->_stmt === false) {
562  $this->rollback();
563  throw new Ego_Sql_Exception(implode(' ', $this->_db->errorInfo()), (int) $this->_db->errorCode());
564  }
565  if ($this->_stmt->execute($set) === false) {
566  $this->rollback();
567  throw new Ego_Sql_Exception(implode(' ', $this->_stmt->errorInfo()), (int) $this->_stmt->errorCode());
568  }
569  return $this->_stmt;
570  }
571 
600  function insert($query)
601  {
602  if (isset($query['key']))
603  {
604  $set = array_combine($query['key'], $query['val']);
605  unset($query['val']);
606  } else {
607  $query['key'] = array_keys($query['set']);
608  $set = $query['set'];
609  unset($query['set']);
610  }
611  $this->currentQuery = $query;
612  $insert_table = $query['table'].'-'.sizeof($query['key']).'-'.$query['replace'];
613  if ($this->_insertTable != $insert_table)
614  {
615  $s = ($query['replace']?'REPLACE':'INSERT')." INTO $query[table]".
616  " (".$this->escapeChar.implode($this->escapeChar.','.$this->escapeChar, $query['key']).$this->escapeChar.")".
617  " VALUES (:".implode(',:', $query['key']).")";
618  $this->_insertstmt = $this->_db->prepare($s);
619  $this->_insertTable = $insert_table;
620  }
621  $this->currentQuery['set'] = $set;
622  if ($this->_insertstmt === false) {
623  $this->rollback();
624  throw new Ego_Sql_Exception(implode(' ', $this->_db->errorInfo()), (int) $this->_db->errorCode());
625  }
626  if ($this->_insertstmt->execute($set) === false) {
627  $this->rollback();
628  throw new Ego_Sql_Exception(implode(' ', $this->_insertstmt->errorInfo()), (int) $this->_insertstmt->errorCode());
629  }
630  return $this->_insertstmt;
631  }
632 
653  function replace($query)
654  {
655  assert(!$query['replace'] || $query['primary']);
656  return $this->insert(array_merge($query, array('replace' => true)));
657  }
658 
671  abstract function showColumns($table);
672 
678  function current()
679  {
680  return $this->Record;
681  }
682 
688  function next()
689  {
690  return $this->nextRecord();
691  }
692 
698  function key()
699  {
700  return $this->_currentRow;
701  }
702 
706  function valid()
707  {
708  return isset($this->Record);
709  }
710 
716  function rewind()
717  {
718  if ($this->_cached)
719  {
720  $this->_currentRow = -1;
721  $this->_cacheIndex = -1;
722  } else {
723  $this->_rewind();
724  }
725  return $this->next();
726  }
727 
734  function numRows()
735  {
736  return $this->numRecords();
737  }
738 
747  protected function _cacheInit($query, $cache)
748  {
749  if (!isset($GLOBALS['egotec_conf']['no_query_cache']) || $GLOBALS['egotec_conf']['no_query_cache']!=1)
750  {
751  $query_hash = $query['hash']?$query['hash']:md5(serialize($query));
752  $this->_cacheRecords = $cache->get($query_hash);
753  if ($this->_cacheRecords === null)
754  { // Nicht im Cache, dann von der Datenbank erfragen.
755  $this->select($query);
756  $this->_cacheRecords = array();
757  while ($this->nextRecord())
758  {
759  $this->_cacheRecords[] = $this->Record;
760  }
761  $cache->set($query_hash, $this->_cacheRecords);
762  } else {
763  $GLOBALS['stats']['db_cache']++;
764  }
765  $this->_cached = true;
766  $this->_cacheIndex = -1;
767  $this->_currentRow = -1;
768  return true;
769  } else {
770  return false;
771  }
772  }
773 
787  function export($table)
788  {
789  $content = array();
790  $db = clone $this;
791  $db->select(array('table' => $table));
792  while ($db->nextRecord())
793  {
794  $content[] = $db->Record;
795  }
796  return $content;
797  }
798 
814  function import($table, $content, $primary=array(), $c_date_fieldname='', $c_date_value='')
815  {
816  if (!is_array($content))
817  {
818  throw new Ego_Sql_Exception('Der Parameter $content muss ein array sein!', Ego_Sql_Exception::IMPORT_ERROR);
819  }
820  if (!$primary)
821  {
822  $primary = $this->getPrimary($table);
823  }
824 
825  $db = clone $this;
826  $db->begin();
827  $db->select(array('table' => $table));
828  $old_content = array();
829  while ($db->nextRecord())
830  {
831  $old_content[md5(serialize($db->Record))] = $db->Record;
832  }
833  $insert = array();
834  foreach ($content as $record)
835  {
836  $md5 = md5(serialize($record));
837  if ($old_content[$md5])
838  { // Falls ein Eintrag schon vorhanden ist,
839  //egotec_error_log("=eq=".serialize($record));
840  unset($old_content[$md5]); // muss nichts gemacht werden.
841  } else { // Ist ein Eintrag noch nicht vorhanden,
842  if (sizeof($primary)==1)
843  {
844  $keys = array_keys($primary);
845  $insert[$record[$keys[0]]] = $record;
846  } else {
847  $insert[] = $record;
848  }
849  }
850  }
851  foreach ($old_content as $record)
852  {
853  if (!$c_date_fieldname || $record[$c_date_fieldname]<$c_date_value)
854  {
855  //egotec_error_log("=rm=".serialize($record));
856  $where = array();
857  $bind = array();
858  foreach ($record as $key=>$val)
859  {
860  if ($primary)
861  {
862  if ($primary[$key])
863  {
864  if ($primary[$key]=='string')
865  {
866  $where[] = $key.'=\''.$val.'\'';
867  } else {
868  $where[] = $key.'='.$val;
869  }
870  }
871  } else {
872  if (is_numeric($val))
873  {
874  $where[] = $key.'='.$val;
875  } else {
876  $where[] = $key.'=\''.$val.'\'';
877  }
878  }
879  }
880  $where = implode(' AND ', $where);
881  $db->delete(array(
882  'table' => $table,
883  'where' => $where
884  ));
885  } else {
886  $record[$c_date_fieldname] = date('Y-m-d H:i:s');
887  $db->replace(array(
888  'table' => $table,
889  'set' => $record,
890  'primary' => $primary
891  ));
892  if (sizeof($primary)==1)
893  {
894  $keys = array_keys($primary);
895  unset($insert[$record[$keys[0]]]);
896  }
897  }
898  }
899  foreach ($insert as $record)
900  {
901  try { // Um duplicate entries zu vermeiden, erfolgen die insertes erst nach den deletes.
902  $db->insert(array('set' => $record, 'table' => $table)); // wo wird dieser eingefügt.
903  } catch (Exception $e)
904  {
905  egotec_error_log($e->getMessage());
906  }
907  }
908  $db->commit();
909  }
910 
937  abstract function createTable($table, $struct, $drop_flag=true);
938 
963  abstract function alterTable($table, $struct);
964 
977  function dropTable($table)
978  {
979  $this->query('DROP TABLE '.$table);
980  }
981 
985  function begin()
986  {}
987 
991  function commit()
992  {}
993 
997  function rollback()
998  {}
999 
1005  function repair($table)
1006  {
1007  ;
1008  }
1009 
1017  function getQueryInfo()
1018  {
1019  ;
1020  }
1021 
1027  function tableExists($table)
1028  {
1029  ;
1030  }
1031 
1035  function optimize($table)
1036  {
1037  ;
1038  }
1039 
1046  protected function onError($action) {
1047  $file = $GLOBALS['egotec_conf']['var_dir'].'lib/db.php';
1048  if (Ego_System::file_exists($file)) {
1049  require_once($file);
1050  $func = 'db_'.$action;
1051  if (function_exists($func)) {
1052  $func($this);
1053  }
1054  }
1055  egotec_error_log('Connection failed');
1056  return null;
1057  }
1058 
1065  public abstract function getPrimary($table);
1066 
1084  public function getTables($like = '')
1085  {
1086  if ($like) {
1087  $this->query("SHOW TABLES FROM ".$GLOBALS['egotec_conf']['db']['database']." LIKE '$like%'");
1088  } else {
1089  $this->query("SHOW TABLES FROM ".$GLOBALS['egotec_conf']['db']['database']);
1090  }
1091 
1092  $all_table = array();
1093  $count = 0;
1094 
1095  while($table = $this->nextRecord()) {
1096  $all_table[$count++] = array_pop($this->Record);
1097  }
1098  return $all_table;
1099  }
1100 
1101  public abstract function getVersion();
1102 }
1103 ?>
query($query, $appendCurrent=false)
_cacheInit($query, $cache)
__construct($query=array(), $cache=false)
static file_exists($file)
select($query, $cache=false)
static log($file, $message)