FRED™  3.0
FRED™: Framework for Rapid and Easy Development
Db.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi\Fred;
4 
5 /**
6  * Database access layer (PDO based).
7  */
8 class Db extends Component{
9 
10  const ACTION_INSERT = 'insert'; //!< Insert a new record.
11  const ACTION_CREATE = 'create'; //!< Create a new record (if none exists, otherwise ignore).
12  const ACTION_REPLACE = 'replace'; //!< Replace an existing record (overwrite).
13  const ACTION_UPDATE = 'update'; //!< Update an existing record.
14  const ACTION_UPSERT = 'upsert'; //!< Insert a new record, or update the existing record if it already exists.
15  const ACTION_DELETE = 'delete'; //!< Delete an existing record.
16 
17  const EVENT_OPEN = 'db:open';
18  const EVENT_LOG_CHANGE = 'db:logChange';
19  const EVENT_LOG_ADD_ID = 'db:logAddId';
20 
21  public $multiOperators = ['|' => 'or','&' => 'and']; //!< Operators (key) and glue (value) for multi column where conditions.
22  public $statTime = 3600; //!< Query time above which to increment a stat counter for the specific query (id = SQL).
23  public $statPrefix = 'db';
24  public $logTimes = [ //!< Query time above which to add a note to the log (key = prio, value = edge; higher times first).
25  Log::CRITICAL => 10.0,
26  Log::ERROR => 5.0,
27  Log::WARNING => 2.5
28  ];
29  public $logTimeSignificant = 3; //!< Number of significant figures (rounding).
30  public $logTables = []; //!< Tables (key) and key columns (value) to log changes for.
31  public $defTables = null; //!< Table definition to use (table name).
32  public $migrateClassName = __CLASS__ . '\\Migrate'; //!< Class name for the migration tool.
33 
34  public $queryCount = 0; //!< Number of queries executed.
35  public $queryTime = 0; //!< Total time spent on queries (seconds).
36  public $allowInsert = true; //!< Allow inserts (for secondary processes like logs).
37 
38  protected $_defaultOptions = [\PDO::MYSQL_ATTR_FOUND_ROWS => true]; //default connection options (functional, but driver specific).
39  protected $_defaultAttributes = [\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true]; //default attributes (functional, but driver specific).
40  protected $_eachOptions = [\PDO::ATTR_PERSISTENT => false]; //!< Do not (re-) use an existing connection (might be busy).
41  protected $_eachAttributes = [\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false]; //!< Do not buffer; retrieve one by one.
42  protected $_connection = []; //!< Connection parameters (keys 'dsn', 'username', 'password', 'options').
43  protected $_attributes = []; //!< Connection specific attributes (key = attribute, value = value).
44 
45  protected $_version = null;
46  protected $_pdo = null;
47  protected $_migrate = null;
48 
49  protected $_startTime = null;
50  protected $_duploCount = [];
51  protected $_logAddId = false;
52  protected $_scout = [];
53  /**
54  * Create a PDO instance.
55  * @param $options array Connection options.
56  * @param $attributes Attributes (applied after connection).
57  * @return PDO
58  */
59  public function createPdo($options = null,$attributes = null){
60  $connection = new \Rsi\Wrapper\Record($this->_connection);
61  $pdo = new \PDO($connection->dsn,$connection->username,$connection->password,array_replace(
62  $this->_defaultOptions,
63  $connection->options ? $connection->options->data : [],
64  $options ?: []
65  ));
66  foreach(array_replace(
67  $this->_defaultAttributes,
68  $this->_attributes,
69  $attributes ?: [],
70  [\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
71  ) as $attribute => $value) $pdo->setAttribute($attribute,$value);
72  $this->component('event')->trigger(self::EVENT_OPEN,$this,$pdo);
73  return $pdo;
74  }
75  /**
76  * Convert a Unix timestamp to database date format.
77  * @param int $time Timestamp (empty = now).
78  * @return string Date as a string.
79  */
80  public function date($time = null){
81  return date('Y-m-d',$time ?: time());
82  }
83  /**
84  * Convert a Unix timestamp to database date+time format.
85  * @param int $time Timestamp (empty = now).
86  * @return string Date+time as a string.
87  */
88  public function dateTime($time = null){
89  return date('Y-m-d H:i:s',$time ?: time());
90  }
91  /**
92  * Escape a value used for a "like" comparision.
93  * @param string $value String to escape.
94  * @param string $escape Escape character.
95  * @return Escaped string.
96  */
97  public function escapeLike($value,$escape = '\\'){
98  foreach([$escape,'%','_'] as $char) $value = str_replace($char,$escape . $char,$value);
99  return $value;
100  }
101  /**
102  * Check if an SQL statement is a select statement.
103  * @param string $sql SQL statement.
104  * @return bool True if it is a select statement.
105  */
106  public function isSelection($sql){
107  return !strcasecmp(substr(trim($sql),0,7),'select ');
108  }
109  /**
110  * Primary key column.
111  * @param string $table For this table.
112  * @return string Column name.
113  */
114  public function keyColumn($table){
115  return 'id';
116  }
117  /**
118  * Begin a transaction.
119  */
120  public function begin(){
121  $this->pdo->beginTransaction();
122  }
123  /**
124  * Roll a transaction back.
125  */
126  public function rollBack(){
127  $this->pdo->rollBack();
128  }
129  /**
130  * Commit a transaction.
131  */
132  public function commit(){
133  $this->pdo->commit();
134  }
135  /**
136  * Wrap a callback function in a transaction.
137  * @param callable $callback Callback function (first and only parameter is this instance).
138  * @param bool $throw If true, an exception is re-thrown after the rollback.
139  * @return bool True on success, false on error (and $throw set to false).
140  */
141  public function transaction($callback,$throw = true){
142  $this->begin();
143  try{
144  call_user_func($callback,$this);
145  $this->commit();
146  }
147  catch(\Exception $e){
148  $this->rollBack();
149  if($throw) throw $e;
150  return false;
151  }
152  return true;
153  }
154  /**
155  * Get the last auto-incremented ID.
156  * @return string
157  */
158  public function lastInsertId(){
159  return $this->pdo->lastInsertId();
160  }
161 
162  protected function startTimer(){
163  $this->_startTime = microtime(true);
164  }
165 
166  protected function checkTimer($sql,$args){
167  $this->queryCount++;
168  $this->queryTime += ($time = microtime(true) - $this->_startTime);
169  $this->allowInsert = $this->isSelection($sql);
170  if(($time >= $this->statTime) && ($stats = $this->component('stats')))
171  $stats->inc($this->statPrefix . ':' . trim(preg_replace('/\\s+/',' ',$sql)),$time);
172  if($log = $this->component('log')){
173  foreach($this->logTimes as $prio => $edge) if($time >= $edge){
174  $log->add($prio,'Slow query',compact('sql','args') + [
175  'time' => round($time,max(0,$this->logTimeSignificant - ceil(log($time,10)))),
176  'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
177  ]);
178  break;
179  }
180  if($this->_fred->debug){
181  if(!array_key_exists($hash = md5($sql . serialize($args)),$this->_duploCount)) $this->_duploCount[$hash] = 0;
182  else $log->info('Duplicate query' . ($this->_duploCount[$hash]++ ? ' (' . $this->_duploCount[$hash] . ')' : ''),__FILE__,__LINE__,compact('sql','args'));
183  }
184  }
185  $this->allowInsert = true;
186  }
187 
188  protected function prepareArgs(&$sql,&$args){
189  if(!$args) $args = [];
190  else foreach($args as $key => $value)
191  if(!preg_match($pattern = "/:$key\\b/",$sql)) unset($args[$key]); //not used in SQL statement
192  elseif(is_array($value)){
193  if(!$value) throw new \Exception("Empty array for '$key'");
194  unset($args[$key]);
195  $keys = [];
196  foreach($value as $sub) $args[$keys[] = $key . 's' . count($keys)] = $sub;
197  $sql = preg_replace($pattern,':' . implode(',:',$keys),$sql);
198  }
199  }
200  /**
201  * Create a PDO statement.
202  * @param string $sql SQL statement to execute.
203  * @param array $args Variables to bind to the statement.
204  * @param PDO $pdo Specific PDO connection to use.
205  * @return PDOStatement (false on failure).
206  */
207  public function statement($sql,$args = null,$pdo = null){
208  if(!$pdo) $pdo = $this->pdo;
209  if($statement = $pdo->prepare($sql)){
210  if($args) foreach($args as $key => $value){
211  $statement->bindValue(':' . $key,$value);
212  unset($value);
213  }
214  $statement->execute();
215  }
216  return $statement;
217  }
218  /**
219  * Execute an SQL statement.
220  * @param string $sql SQL statement to execute.
221  * @param array $args Variables to bind to the statement.
222  * @param PDO $pdo Specific PDO connection to use.
223  * @return int The number of affected rows.
224  */
225  public function execute($sql,$args = null,$pdo = null){
226  if($log = $this->component('log')) $log->debug(__CLASS__ . "::execute('$sql',args)",__FILE__,__LINE__,compact('sql','args'));
227  if(!$pdo) $pdo = $this->pdo;
228  $result = null;
229  $this->prepareArgs($sql,$args);
230  $this->startTimer();
231  if(!$args) $result = $pdo->exec($sql);
232  elseif($statement = $this->statement($sql,$args,$pdo)){
233  $result = $statement->rowCount();
234  $statement->closeCursor();
235  $statement = null;
236  }
237  $this->checkTimer($sql,$args);
238  return $result;
239  }
240  /**
241  * Execute an SQL statement.
242  * @param string $sql SQL statement to execute.
243  * @param array $args Variables to bind to the statement.
244  * @param PDO $pdo Specific PDO connection to use.
245  * @return PDOStatement (false on failure).
246  */
247  public function query($sql,$args = null,$pdo = null){
248  if($log = $this->component('log')) $log->debug(__CLASS__ . "::query('$sql',args)",__FILE__,__LINE__,compact('sql','args'));
249  if(!$pdo) $pdo = $this->pdo;
250  $this->prepareArgs($sql,$args);
251  $this->startTimer();
252  $result = $args ? $this->statement($sql,$args,$pdo) : $pdo->query($sql);
253  $this->checkTimer($sql,$args);
254  return $result;
255  }
256  /**
257  * Fetch a row from an SQL statement.
258  * @param PDOStatement $statement
259  * @return array
260  */
261  public function fetch($statement){
262  $row = $statement->fetch();
263  return $this->defTables ? $this->component('def')->convertRecord($row,$this->defTables) : $row;
264  }
265  /**
266  * Return al rows from an SQL statement.
267  * @param string $sql SQL statement to execute.
268  * @param array $args Variables to bind to the statement.
269  * @return array All rows (false on failure).
270  */
271  public function all($sql,$args = null){
272  if($statement = $this->query($sql,$args)){
273  if(!$this->defTables) return $statement->fetchAll();
274  $rows = [];
275  while($row = $this->fetch($statement)) $rows[] = $row;
276  $statement->closeCursor();
277  $statement = null;
278  return $rows;
279  }
280  return false;
281  }
282  /**
283  * Return a single row from an SQL statement.
284  * Returns a single value if the resulting row has only one column.
285  * @param string $sql SQL statement to execute.
286  * @param array $args Variables to bind to the statement.
287  * @param bool $auto Set to false to always return a row.
288  * @return mixed Row (multi column) or value (single column).
289  */
290  public function single($sql,$args = null,$auto = true){
291  if($statement = $this->query($sql,$args)){
292  $row = $this->fetch($statement);
293  $statement->closeCursor();
294  $statement = null;
295  if($row) return $auto && (count($row) == 1) ? array_pop($row) : $row;
296  }
297  return false;
298  }
299  /**
300  * Returns an array from an SQL statement.
301  * If the query returns only one column, the result is an array of those values. With two columns, the first one becomes the
302  * key, and the second one the value of an assoc.array. With three or more columns the first column will be the key, and the
303  * others the value.
304  * @param string $sql SQL statement to execute.
305  * @param array $args Variables to bind to the statement.
306  * @return array
307  */
308  public function record($sql,$args = null){
309  $result = [];
310  if($statement = $this->query($sql,$args)){
311  while($row = $this->fetch($statement)){
312  $key = array_shift($row);
313  switch(count($row)){
314  case 0: $result[] = $key; break;
315  case 1: $result[$key] = array_pop($row); break;
316  default: $result[$key] = $row;
317  }
318  }
319  $statement->closeCursor();
320  $statement = null;
321  }
322  return $result;
323  }
324  /**
325  * Run a callback function for every row in an SQL resultset.
326  * A separate conection is used without buffering. This makes it possible to process huge datasets one record at a time,
327  * without running out of memory.
328  * @param callable $callback Callback function (parameters row and index). If the function returns explicitly false the
329  * execution of further rows is halted.
330  * @param string $sql SQL statement to execute.
331  * @param array $args Variables to bind to the statement.
332  * @return int Number of records processed (false on error).
333  */
334  public function each($callback,$sql,$args = null){
335  $result = false;
336  if($statement = $this->query($sql,$args,$pdo = $this->createPdo($this->_eachOptions,$this->_eachAttributes))){
337  $result = 0;
338  while($row = $this->fetch($statement)) if(call_user_func($callback,$row,$result++) === false) break;
339  $statement->closeCursor();
340  $statement = null;
341  }
342  $pdo = null;
343  return $result;
344  }
345  /**
346  * Run a callback function for every row in a batched SQL resultset.
347  * The rows are fetched in batches. This makes it possible to process huge datasets one record at a time, without using a
348  * separate connection, and without running out of memory. Note that this may not work perfectly when the dataset changes
349  * frequently (items added or deleted in the beginning shift the contents of later batches).
350  * @param callable $callback Callback function (parameters row and index). If the function returns explicitly false the
351  * execution of further rows is halted.
352  * @param string $sql SQL statement to execute.
353  * @param array $args Variables to bind to the statement.
354  * @param int $limit Batch size.
355  * @return int Number of records processed.
356  */
357  public function batch($callback,$sql,$args = null,$limit = 1000){
358  if(!stripos($sql,'order by')) throw new \Exception('No ordering');
359  $result = $offset = 0;
360  while(($result == $offset) && ($rows = $this->all($this->limit($sql,$limit,$offset),$args))){
361  foreach($rows as $row) if(call_user_func($callback,$row,$result++) === false) break 2;
362  $offset += $limit;
363  }
364  return $result;
365  }
366  /**
367  * Expand an array with scalar values and arrays to multiple arrays.
368  * @param array $columns Columns (key = column name, value = array with values or single scalar value).
369  * @return array Array of column arrays (key = column name, value = value).
370  */
371  public function multiColumns($columns){
372  $keys = $count = false;
373  foreach($columns as $column => $value) if(is_array($value)){
374  if($keys === false) $count = count($keys = array_keys($value));
375  elseif(($count != count($value)) || array_diff($keys,array_keys($value)))
376  throw new \Exception("Different keys for column '$column'");
377  }
378  $result = [];
379  foreach((array)$keys as $key){
380  $result[$key] = [];
381  foreach($columns as $column => $value) $result[$key][$column] = is_array($value) ? $value[$key] : $value;
382  }
383  return $result;
384  }
385  /**
386  * Prepare a where statement.
387  * @param string|array $where If this a string, it is used directly as the where clause. If it is an assoc.array, it is
388  * translated to a where statement. Key => value pairs are translated as follow:
389  * - 'key' => 'value' : "key = 'value'" (or "key is null" when the value is null, or "key in (...)" when the value is an
390  * array).
391  * - 'key<>' => 'value' : "key <> 'value'" (or "key is not null" when the value is null, or "key not in (...)" when the
392  * value is an array).
393  * - 'key~' => 'value' : "key like 'value'"
394  * - 'key^' => [key1 => value1,key2 => value2] : "(key1 = value1 or key2 = value2)" (same rules as above apply for the 'or'
395  * part).
396  * @param string|array $args If the $where is a string, and this is also, this value is added to the where. Otherwise the
397  * arguments resulting from the translation will be added to this array.
398  * @param string $glue Glue to combine seperate arguments.
399  */
400  public function prepareWhere(&$where,&$args = null,$glue = 'and'){
401  if($extra = $args && is_string($args) ? "\n" . $args : null) $args = null;
402  if(is_array($where)){
403  if(!is_array($args)) $args = [];
404  $def = $this->defTables ? $this->component('def') : null;
405  foreach($where as $column => &$value){
406  if($raw = $column[0] == '!') $column = substr($column,1);
407  $negation = '';
408  if($operator = preg_match('/\\W+$/',$column,$match) ? $match[0] : null){
409  $column = substr($column,0,-strlen($operator));
410  if($operator[0] == '!'){
411  $negation = 'not ';
412  $operator = substr($operator,1);
413  }
414  switch($short = $operator){
415  case '~' : $operator = 'like'; break;
416  }
417  }
418  else $short = $operator = '=';
419 
420  if($value === null) switch($operator){
421  case '<>': $negation = 'not ';
422  case '=': $value = "`$column` is {$negation}null"; break;
423  default: $value = '0=1'; //undefined
424  }
425  elseif(is_array($value)){
426  if(array_key_exists($operator,$this->multiOperators)){
427  $multi = count($value) > 1;
428  $this->prepareWhere($value,$args,$this->multiOperators[$operator]);
429  if($multi) $value = "($value)";
430  }
431  elseif($value){
432  if($def) foreach($value as &$sub) $sub = $def->formatColumn($column,$sub,$this->defTables);
433  unset($sub);
434  if(!$negation && (count($value) == 1)){
435  $args[$key = 'a' . count($args)] = array_pop($value);
436  $value = "`$column` $operator :$key";
437  }
438  else{
439  switch($short){
440  case '<>':
441  $negation = 'not ';
442  case '=':
443  $args[$key = 'a' . count($args)] = $value;
444  $value = "`$column` {$negation}in (:$key)";
445  break;
446  case '~':
447  $likes = [];
448  foreach($value as $sub){
449  $args[$key = 'l' . count($args)] = $sub;
450  $likes[] = "`$column` like :$key";
451  }
452  $value = $negation . '(' . implode(' or ',$likes) . ')';
453  break;
454  case '..':
455  $key = 'b' . count($args);
456  $args += ['l' . $key => array_shift($value),'u' . $key => array_shift($value)];
457  if($value) throw new \InvalidArgumentException("Too many arguments for 'between' for column '$column'");
458  $value = "$negation`$column` between :l$key and :u$key";
459  break;
460  default:
461  throw new \DomainException("Invalid array operator '$operator' for column '$column'");
462  }
463  }
464  }
465  else $value = (int)($operator == '<>') . '=1';
466  }
467  else{
468  if($null = substr($operator,0,1) == '#') $operator = substr($operator,1);
469  if($raw) $value = "$negation`$column` $operator $value";
470  else{
471  $args[$key = 'v' . count($args)] = $def ? $def->formatColumn($column,$value,$this->defTables) : $value;
472  $value = "$negation`$column` $operator :$key";
473  }
474  if($null) $value = "(`$column` is null or $value)";
475  }
476  }
477  unset($value);
478  $where = implode("\n $glue ",$where);
479  }
480  elseif(!$args) $args = [];
481  $where = ($where ?: '1=1') . $extra;
482  }
483  /**
484  * Limit an SQL statement.
485  * @param string $sql Original SQL statement.
486  * @param int $limit Limit (number of rows).
487  * @param int $offset Offset.
488  * @return string SQL statement with limit.
489  */
490  public function limit($sql,$limit,$offset = null){
491  return $sql . ' limit ' . max(0,(int)$offset) . ',' . max(0,(int)$limit);
492  }
493  /**
494  * Log a change.
495  * @param string $table Table name.
496  * @param string $action Change type (see ACTION_* constants).
497  * @param array $key Key of the changed record (key = column name, value = value).
498  * @param array $old Original values for changed columns (key = column name, value = value).
499  * @param array $new New values for changed columns (key = column name, value = value).
500  */
501  protected function logChange($table,$action,$key,$old,$new){
502  $this->_fred->event->trigger(self::EVENT_LOG_CHANGE,$this,$table,$action,$key,$old,$new);
503  }
504  /**
505  * Add an auto-increment ID to the latest change log.
506  * @param string $id The last auto-incremented ID (the key is stored in $this->_logAddId).
507  */
508  protected function logAddId($id){
509  $this->_fred->event->trigger(self::EVENT_LOG_ADD_ID,$this,$this->_logAddId,$id);
510  }
511  /**
512  * Log the changes for an action.
513  * @param string $table Table name.
514  * @param string $action Change type (see ACTION_* constants).
515  * @param array $columns New values (key = column name, value = value).
516  * @param string|array $where Where.
517  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
518  */
519  protected function logChanges($table,$action,$columns,$where,$args = null){
520  $this->_logAddId = false;
521  if($key_columns = $this->logTables[$table] ?? null) try{
522  $key = is_array($where) ? \Rsi\Record::select($where,$key_columns) : [];
523  if($keys = count($key) == count($key_columns) ? [$key] : $this->select($table,$key_columns,$where,$args)){
524  foreach($keys as $key) if(
525  ($current = $this->select($table,$columns === false ? '*' : array_keys($columns),$key,null,1)) &&
526  ($changed = array_diff_assoc($current,$columns === false ? $key : $columns))
527  ) $this->logChange($table,$action,$key,$changed,$columns === false ? false : array_intersect_key($columns,$changed));
528  }
529  elseif($action != self::ACTION_DELETE){
530  $this->logChange($table,$action,false,false,$columns);
531  if(count($key_columns) == 1) $this->_logAddId = array_pop($key_columns);
532  }
533  }
534  catch(\Exception $e){
535  if($this->_fred->debug) throw $e;
536  $this->component('log')->critical("Could not log '$action' on '$table': " . $e->getMessage(),__FILE__,__LINE__,compact('columns','where','args'));
537  }
538  }
539  /**
540  * Pre-select records (will be used by select()).
541  * @param string $table Table name.
542  * @param array $columns Columns to select from the table. Leaving this empty will clear the cache for this table.
543  * @param string|array $where Where.
544  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
545  * @return array Selected rows (null on clear).
546  */
547  public function scout($table,$columns = null,$where = null,$args = null){
548  $result = null;
549  if($columns){
550  if(!array_key_exists($table,$this->_scout)) $this->_scout[$table] = [];
551  if(!array_key_exists($key = serialize($columns),$this->_scout[$table])) $this->_scout[$table][$key] = [];
552  foreach(($result = $this->select($table,$columns,$where,$args)) as $record) $this->_scout[$table][$key][serialize($record)] = $record;
553  }
554  else unset($this->_scout[$table]);
555  return $result;
556  }
557 
558  protected function updateSql($columns,&$args){
559  if(!is_array($columns)) return $columns;
560  $def = $this->defTables ? $this->component('def') : null;
561  $sql = [];
562  foreach($columns as $column => $value){
563  if($raw = substr($column,0,1) == '!') $column = substr($column,1);
564  elseif($def) $value = $def->formatColumn($column,$value,$this->defTables);
565  $args[$key = 'u' . count($args)] = $value;
566  $sql[] = "`$column` = " . ($raw ? $value : ':' . $key);
567  }
568  return implode(',',$sql);
569  }
570  /**
571  * Insert a record.
572  * @param string $table Table name.
573  * @param array $columns Columns (key = column name, value = value or values - for multiple records).
574  * @param string $action Insert type to perform (see ACTION_* constants).
575  * @param string|array $update Update for an upsert action.
576  * @return int The number of affected rows (0 = failure, 1 = success).
577  */
578  public function insert($table,$columns,$action = self::ACTION_INSERT,$update = null){
579  $this->scout($table);
580  $def = $this->defTables ? $this->component('def') : null;
581  $result = 0;
582  foreach($this->multiColumns($columns) as $columns){
583  $values = [];
584  foreach($columns as $column => &$value){
585  if($raw = substr($column,0,1) == '!') $column = substr($column,1);
586  elseif($def) $value = $def->formatColumn($column,$value,$this->defTables);
587  $values[$column] = $raw ? $value : ':' . $column;
588  }
589  unset($value);
590  $this->logChanges($table,$action,$update ?: $columns,$args = $columns);
591  $sql = " into `$table` (`" . implode('`,`',array_keys($values)) . "`)\nvalues (" . implode(',',$values) . ')';
592  switch($action){
593  case self::ACTION_UPSERT: $sql .= "\non duplicate key update " . $this->updateSql($update,$args);
594  case self::ACTION_INSERT: $sql = 'insert' . $sql; break;
595  case self::ACTION_CREATE: $sql = 'insert ignore' . $sql; break;
596  case self::ACTION_REPLACE: $sql = 'replace' . $sql; break;
597  default: throw new \Exception("Unknown insert action '$action'");
598  }
599  if($this->execute($sql,$args)){
600  $result++;
601  if($this->_logAddId && ($id = $this->lastInsertId())) try{
602  $this->logAddId($id);
603  }
604  catch(\Exception $e){
605  if($this->_fred->debug) throw $e;
606  $this->component('log')->critical($e);
607  }
608  }
609  }
610  return $result;
611  }
612  /**
613  * Create a record (if it does not exists).
614  * @param string $table Table name.
615  * @param array $columns Columns (key = column name, value = value or values - for multiple records).
616  * @return int The number of affected rows (0 = failure, 1 = success).
617  */
618  public function create($table,$columns){
619  return $this->insert($table,$columns,self::ACTION_CREATE);
620  }
621  /**
622  * Replace a record (insert if not exists).
623  * @param string $table Table name.
624  * @param array $columns Columns (key = column name, value = value or values - for multiple records).
625  * @return int The number of affected rows (0 = failure, 1 = success).
626  */
627  public function replace($table,$columns){
628  return $this->insert($table,$columns,self::ACTION_REPLACE);
629  }
630  /**
631  * Insert a record, or update it if it already exists.
632  * @param string $table Table name.
633  * @param array $columns Columns (key = column name, value = value or values - for multiple records).
634  * @param array $update Columns to update if the record already exists (key = column name, value = new value).
635  */
636  public function upsert($table,$columns,$update){
637  return $this->insert($table,$columns,self::ACTION_UPSERT,$update);
638  }
639  /**
640  * Select records.
641  * @param string $table Table name.
642  * @param array $columns Columns to select from the table. Prefix the first columns with a '+' to make this the key.
643  * @param string|array $where Where.
644  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
645  * @param int|bool $limit Limit the number of rows (0 = no limit).
646  * @param int $offset Offset.
647  * @return array Single value when limit is true, single row when limit is 1, otherwise an array of records.
648  * @see prepareWhere()
649  */
650  public function select($table,$columns = '*',$where = null,$args = null,$limit = null,$offset = null){
651  if(
652  ($limit == 1) && !$offset &&
653  array_key_exists($table,$this->_scout) && array_key_exists($key = serialize($columns),$this->_scout[$table]) &&
654  is_array($where) && !array_filter($where,function($value){
655  return is_array($value);
656  })
657  ) foreach($this->_scout[$table][$key] as $record){
658  foreach($where as $key => $value) if(($record[$key] ?? false) !== $value) continue 2;
659  return $record;
660  }
661  $columns = preg_replace('/^(`)?\\+/','$1',is_array($columns) ? '`' . implode('`,`',$columns) . '`' : $columns,1,$record);
662  $this->prepareWhere($where,$args);
663  $sql = "select $columns\nfrom `$table`\nwhere $where";
664  if($limit) $sql = $this->limit($sql,$limit,$offset);
665  if($record) return $this->record($sql,$args);
666  $rows = $this->all($sql,$args);
667  return $limit === true
668  ? (($row = array_shift($rows)) ? array_shift($row) : false) //single column
669  : ($rows ? ($limit == 1 ? array_shift($rows) : $rows) : false);
670  }
671  /**
672  * Update one or more record(s).
673  * @param string $table Table name.
674  * @param array $columns Columns to update (key = column name, value = new value).
675  * @param string|array $where Where (empty = all).
676  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
677  * @param bool $insert_if_not_exists If true, a new record will be inserted when there is not yet an existing record.
678  * @return int The number of affected rows (0 = failure, 1 or more = success).
679  */
680  public function update($table,$columns,$where,$args = null,$insert_if_not_exists = false){
681  $this->scout($table);
682  $this->logChanges($table,self::ACTION_UPDATE,$columns,$where,$args);
683  $this->prepareWhere($where,$args);
684  $sql = "update `$table` set " . $this->updateSql($columns,$args) . "\nwhere $where";
685  $result = $this->execute($sql,$args);
686  if(!$result && $insert_if_not_exists) $result = $this->insert($table,$columns);
687  return $result;
688  }
689  /**
690  * Increment one or more columns, or insert a new record if none exists.
691  * @param string $table Table name.
692  * @param string|array $columns Column to increment (string), or columns to update (array; key = column name,
693  * value = increment).
694  * @param array $where Key to increment.
695  * @return int The number of affected rows (0 = failure, 1 or more = success).
696  */
697  public function inc($table,$columns,$where){
698  if(!is_array($columns)) $columns = [$columns => 1];
699  $update = [];
700  foreach($columns as $column => $value) $update['!' . $column] = "`$column` + $value";
701  return $this->upsert($table,$columns + $where,$update);
702  }
703  /**
704  * Delete one or more record(s).
705  * @param string $table Table name.
706  * @param string|array $where Where (empty = all).
707  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
708  * @return int The number of affected rows (0 = failure, 1 or more = success).
709  */
710  public function delete($table,$where,$args = null){
711  $this->scout($table);
712  $this->logChanges($table,self::ACTION_DELETE,false,$where,$args);
713  $this->prepareWhere($where,$args);
714  $sql = "delete from `$table` where $where";
715  return $this->execute($sql,$args);
716  }
717  /**
718  * Check if a record exists that meets the requirement.
719  * @param string $table Table name.
720  * @param string|array $where Where.
721  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
722  * @return bool True if a record exists.
723  */
724  public function exists($table,$where = null,$args = null){
725  $this->prepareWhere($where,$args);
726  $sql = $this->limit("select 1 from `$table` where $where",1);
727  return (bool)$this->fetch($this->query($sql,$args));
728  }
729  /**
730  * Run an aggregation query.
731  * @param string $table Table name.
732  * @param string $function Aggregation function (e.g. "sum").
733  * @param string|array $where Where.
734  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
735  * @return mixed
736  */
737  protected function aggregate($table,$function,$where = null,$args = null){
738  $this->prepareWhere($where,$args);
739  $sql = "select $function from `$table` where $where";
740  $row = $this->fetch($this->query($sql,$args));
741  return array_pop($row);
742  }
743  /**
744  * The number of records that meet the requirements.
745  * @param string $table Table name.
746  * @param string|array $where Where.
747  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
748  * @return int Number of rows.
749  */
750  public function count($table,$where = null,$args = null){
751  return $this->aggregate($table,'count(*)',$where,$args);
752  }
753  /**
754  * Smallest value from records that meet the requirements.
755  * @param string $table Table name.
756  * @param string $column Column to get value from.
757  * @param string|array $where Where.
758  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
759  * @return mixed Smallest value.
760  */
761  public function min($table,$column,$where = null,$args = null){
762  return $this->aggregate($table,"min(`$column`)",$where,$args);
763  }
764  /**
765  * Biggest value from records that meet the requirements.
766  * @param string $table Table name.
767  * @param string $column Column to get value from.
768  * @param string|array $where Where.
769  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
770  * @return mixed Biggest value.
771  */
772  public function max($table,$column,$where = null,$args = null){
773  return $this->aggregate($table,"max(`$column`)",$where,$args);
774  }
775  /**
776  * Average value from records that meet the requirements.
777  * @param string $table Table name.
778  * @param string $column Column to get value from.
779  * @param string|array $where Where.
780  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
781  * @return mixed Average value.
782  */
783  public function average($table,$column,$where = null,$args = null){
784  return $this->aggregate($table,"avg(`$column`)",$where,$args);
785  }
786  /**
787  * Total value from records that meet the requirements.
788  * @param string $table Table name.
789  * @param string $column Column to get value from.
790  * @param string|array $where Where.
791  * @param string|array $args Arguments (assoc.array) or extra statement for the where.
792  * @return mixed Total value.
793  */
794  public function sum($table,$column,$where = null,$args = null){
795  return $this->aggregate($table,"sum(`$column`)",$where,$args);
796  }
797  /**
798  * Name of the current database.
799  * @return string
800  */
801  public function database(){
802  return $this->single('select database()');
803  }
804  /**
805  * All tables in current database.
806  * @return array
807  */
808  public function tables(){
809  return $this->record('show tables');
810  }
811  /**
812  * Column properties for a table.
813  * @param string $table Name of the table.
814  * @param string $database Name of the database (current database when empty).
815  * @return array Key = column name, value = assoc.array with properties.
816  */
817  public function columns($table,$database = null){
818  return $this->record('
819  select
820  COLUMN_NAME,
821  DATA_TYPE as `type`,
822  CHARACTER_MAXIMUM_LENGTH as `length`,
823  NUMERIC_PRECISION as `precision`,
824  NUMERIC_SCALE as `scale`,
825  if(right(COLUMN_TYPE,8) = "unsigned",1,0) as `unsigned`,
826  COLLATION_NAME as `collation`,
827  COLUMN_DEFAULT as `default`,
828  if(IS_NULLABLE = "NO",1,0) as `required`,
829  if(COLUMN_KEY = "PRI",1,0) as `primary`,
830  (
831  select concat(REFERENCED_TABLE_NAME,".",REFERENCED_COLUMN_NAME)
832  from INFORMATION_SCHEMA.KEY_COLUMN_USAGE
833  where TABLE_SCHEMA = col.TABLE_SCHEMA
834  and TABLE_NAME = col.TABLE_NAME
835  and COLUMN_NAME = col.COLUMN_NAME
836  and REFERENCED_COLUMN_NAME is not null
837  ) as `ref`
838  from INFORMATION_SCHEMA.COLUMNS as col
839  where TABLE_SCHEMA = :database
840  and TABLE_NAME = :table
841  order by ORDINAL_POSITION',
842  [
843  'database' => $database ?: $this->database(),
844  'table' => $table
845  ]
846  );
847  }
848  /**
849  * Optimize table(s).
850  * @param string $table Table to optimize (empty for all tables).
851  */
852  public function optimize($table = null){
853  if($table){
854  $this->component('log')->debug(__CLASS__ . "::optimize('$table')",__FILE__,__LINE__);
855  $this->record("optimize table `$table`");
856  }
857  else foreach($this->tables() as $table) $this->optimize($table);
858  }
859  /**
860  * Explain plan.
861  * @param string $sql SQL statement to explain.
862  * @param array $args Variables to bind to the statement.
863  * @return array Steps in the plan.
864  */
865  public function explain($sql,$args = null){
866  return $this->all('explain ' . $sql,$args);
867  }
868  /**
869  * Running processes.
870  * @return array Key = process ID, value = array with info.
871  */
872  public function processes(){
873  $sql = '
874  select
875  ID,USER as `user`,HOST as `host`,DB as `database`,
876  if(STATE = "",COMMAND,STATE) as `status`,TIME' . (stripos($this->version,'MariaDB') ? '_MS / 1000' : '') . ' as `time`,
877  INFO as `sql`
878  from INFORMATION_SCHEMA.PROCESSLIST';
879  return $this->record($sql . ' where INFO not like :sql', ['sql' => trim($sql) . '%']);
880  }
881  /**
882  * Kill a process
883  * @param int $id Process ID.
884  * @return bool True on success.
885  */
886  public function kill($id){
887  try{
888  $this->execute('kill :id',['id' => $id]);
889  return true;
890  }
891  catch(\Exception $e){
892  $this->component('log')->info($e);
893  }
894  return false;
895  }
896 
897  protected function getMigrate(){
898  if(!$this->_migrate){
899  $class_name = $this->migrateClassName;
900  $this->_migrate = new $class_name($this->_fred,$this->config('migrate',[]) + ['db' => $this]);
901  }
902  return $this->_migrate;
903  }
904 
905  protected function getPdo(){
906  if(!$this->_pdo) $this->_pdo = $this->createPdo();
907  return $this->_pdo;
908  }
909 
910  protected function getVersion(){
911  if($this->_version === null)
912  $this->_version = \Rsi\Record::value($this->record('show variables where variable_name="version"'),false);
913  return $this->_version;
914  }
915 
916  public function __call($func_name,$params){
917  $result = false;
918  if(substr($func_name,0,1) == '_'){
919  $func_name = substr($func_name,1);
920  $table = array_shift($params);
921  if(in_array($func_name,['insert','replace','select','update','delete','exists','count'])) array_unshift($params,$table);
922  $prev_tables = $this->defTables;
923  $this->defTables = (array)$table;
924  try{
925  $result = call_user_func_array([$this,$func_name],$params);
926  }
927  finally{
928  $this->defTables = $prev_tables;
929  }
930  }
931  else{
932  if(is_numeric($where = array_shift($params)) || (is_string($where) && !preg_match('/[\\s=<>]/',$where)))
933  $where = [$this->keyColumn($func_name) => $where];
934  $result = $this->select($func_name,'*',$where,array_shift($params),1);
935  }
936  return $result;
937  }
938 
939  public function __invoke($sql,$args = null){
940  return $this->execute($sql,$args);
941  }
942 
943  public function __sleep(){
944  return [];
945  }
946 
947 }
Rsi\Fred\Db\date
date($time=null)
Convert a Unix timestamp to database date format.
Definition: Db.php:80
Rsi\Fred\Db\startTimer
startTimer()
Definition: Db.php:162
Rsi\Fred\Db\replace
replace($table, $columns)
Replace a record (insert if not exists).
Definition: Db.php:627
Rsi\Fred\Db\optimize
optimize($table=null)
Optimize table(s).
Definition: Db.php:852
Rsi\Fred\Db\ACTION_DELETE
const ACTION_DELETE
Delete an existing record.
Definition: Db.php:15
Rsi\Fred\Db\$_version
$_version
Definition: Db.php:45
Rsi\Fred\Db\keyColumn
keyColumn($table)
Primary key column.
Definition: Db.php:114
Rsi\Fred\Db\getPdo
getPdo()
Definition: Db.php:905
Rsi\Fred\Db\__sleep
__sleep()
Definition: Db.php:943
Rsi\Fred\Db\single
single($sql, $args=null, $auto=true)
Return a single row from an SQL statement.
Definition: Db.php:290
Rsi\Fred\Db\transaction
transaction($callback, $throw=true)
Wrap a callback function in a transaction.
Definition: Db.php:141
Rsi\Fred\Db\multiColumns
multiColumns($columns)
Expand an array with scalar values and arrays to multiple arrays.
Definition: Db.php:371
Rsi\Fred\Db\$logTimes
$logTimes
Definition: Db.php:24
Rsi\Fred\Db\$statPrefix
$statPrefix
Definition: Db.php:23
Rsi\Fred\Db\$allowInsert
$allowInsert
Allow inserts (for secondary processes like logs).
Definition: Db.php:36
Rsi\Fred\Controller\Provider\Db
Definition: Db.php:7
Rsi\Fred\Db\$logTimeSignificant
$logTimeSignificant
Number of significant figures (rounding).
Definition: Db.php:29
Rsi\Fred\Db\__call
__call($func_name, $params)
Definition: Db.php:916
Rsi\Fred\Db\$_migrate
$_migrate
Definition: Db.php:47
Rsi\Fred\Db\all
all($sql, $args=null)
Return al rows from an SQL statement.
Definition: Db.php:271
Rsi\Fred\Db\isSelection
isSelection($sql)
Check if an SQL statement is a select statement.
Definition: Db.php:106
Rsi\Fred\Db\EVENT_LOG_CHANGE
const EVENT_LOG_CHANGE
Definition: Db.php:18
Rsi\Fred\Db\upsert
upsert($table, $columns, $update)
Insert a record, or update it if it already exists.
Definition: Db.php:636
Rsi\Fred\Db\logAddId
logAddId($id)
Add an auto-increment ID to the latest change log.
Definition: Db.php:508
Rsi\Fred\Db\count
count($table, $where=null, $args=null)
The number of records that meet the requirements.
Definition: Db.php:750
Rsi\Fred\Db\createPdo
createPdo($options=null, $attributes=null)
Create a PDO instance.
Definition: Db.php:59
Rsi\Fred\Db\ACTION_CREATE
const ACTION_CREATE
Create a new record (if none exists, otherwise ignore).
Definition: Db.php:11
Rsi\Fred\Db\tables
tables()
All tables in current database.
Definition: Db.php:808
Rsi\Fred\Db\query
query($sql, $args=null, $pdo=null)
Execute an SQL statement.
Definition: Db.php:247
Rsi\Fred\Log\CRITICAL
const CRITICAL
Critical conditions.
Definition: Log.php:10
Rsi\Fred\Db\limit
limit($sql, $limit, $offset=null)
Limit an SQL statement.
Definition: Db.php:490
Rsi\Fred\Db\columns
columns($table, $database=null)
Column properties for a table.
Definition: Db.php:817
Rsi\Fred\Db\$_defaultAttributes
$_defaultAttributes
Definition: Db.php:39
Rsi\Fred\Db\ACTION_UPDATE
const ACTION_UPDATE
Update an existing record.
Definition: Db.php:13
Rsi\Fred\Db\$_logAddId
$_logAddId
Definition: Db.php:51
Rsi\Fred\Db\commit
commit()
Commit a transaction.
Definition: Db.php:132
Rsi\Fred\Db\insert
insert($table, $columns, $action=self::ACTION_INSERT, $update=null)
Insert a record.
Definition: Db.php:578
Rsi\Fred\Db\$_eachAttributes
$_eachAttributes
Do not buffer; retrieve one by one.
Definition: Db.php:41
Rsi\Fred\Db\$_defaultOptions
$_defaultOptions
Definition: Db.php:38
Rsi\Fred\Db\logChange
logChange($table, $action, $key, $old, $new)
Log a change.
Definition: Db.php:501
Rsi\Fred\Component
Basic component class.
Definition: Component.php:8
Rsi\Fred\Db\ACTION_INSERT
const ACTION_INSERT
Insert a new record.
Definition: Db.php:10
Rsi\Fred\Db\$queryCount
$queryCount
Number of queries executed.
Definition: Db.php:34
Rsi\Fred\Db\EVENT_LOG_ADD_ID
const EVENT_LOG_ADD_ID
Definition: Db.php:19
Rsi\Fred\Db\kill
kill($id)
Kill a process.
Definition: Db.php:886
Rsi\Fred\Db\record
record($sql, $args=null)
Returns an array from an SQL statement.
Definition: Db.php:308
Rsi\Fred\Db\select
select($table, $columns=' *', $where=null, $args=null, $limit=null, $offset=null)
Select records.
Definition: Db.php:650
Rsi\Fred\Db\explain
explain($sql, $args=null)
Explain plan.
Definition: Db.php:865
Rsi\Fred\Component\component
component($name)
Get a component (local or default).
Definition: Component.php:81
Rsi\Fred\Db\execute
execute($sql, $args=null, $pdo=null)
Execute an SQL statement.
Definition: Db.php:225
Rsi\Fred\Db\processes
processes()
Running processes.
Definition: Db.php:872
Rsi\Fred\Db\getMigrate
getMigrate()
Definition: Db.php:897
Rsi\Fred\Db\logChanges
logChanges($table, $action, $columns, $where, $args=null)
Log the changes for an action.
Definition: Db.php:519
Rsi\Fred\Db\$_duploCount
$_duploCount
Definition: Db.php:50
Rsi\Fred\Db\$queryTime
$queryTime
Total time spent on queries (seconds).
Definition: Db.php:35
Rsi\Fred\Db\$_scout
$_scout
Definition: Db.php:52
Rsi\Fred\Db\rollBack
rollBack()
Roll a transaction back.
Definition: Db.php:126
Rsi\Fred\Component\config
config($key, $default=null)
Retrieve a config value.
Definition: Component.php:53
Rsi\Fred\Db\begin
begin()
Begin a transaction.
Definition: Db.php:120
Rsi\Fred\Db\$statTime
$statTime
Query time above which to increment a stat counter for the specific query (id = SQL).
Definition: Db.php:22
Rsi\Fred\Db\database
database()
Name of the current database.
Definition: Db.php:801
Rsi\Fred\Db\inc
inc($table, $columns, $where)
Increment one or more columns, or insert a new record if none exists.
Definition: Db.php:697
Rsi\Fred\Db\ACTION_UPSERT
const ACTION_UPSERT
Insert a new record, or update the existing record if it already exists.
Definition: Db.php:14
Rsi\Fred\Db\$_connection
$_connection
Connection parameters (keys 'dsn', 'username', 'password', 'options').
Definition: Db.php:42
Rsi\Fred\Db\scout
scout($table, $columns=null, $where=null, $args=null)
Pre-select records (will be used by select()).
Definition: Db.php:547
Rsi\Fred\Db\$_pdo
$_pdo
Definition: Db.php:46
Rsi\Fred\Db\ACTION_REPLACE
const ACTION_REPLACE
Replace an existing record (overwrite).
Definition: Db.php:12
Rsi\Fred\Db\getVersion
getVersion()
Definition: Db.php:910
Rsi\Fred\Db\prepareWhere
prepareWhere(&$where, &$args=null, $glue='and')
Prepare a where statement.
Definition: Db.php:400
Rsi\Fred\Db\min
min($table, $column, $where=null, $args=null)
Smallest value from records that meet the requirements.
Definition: Db.php:761
Rsi\Fred\Db\statement
statement($sql, $args=null, $pdo=null)
Create a PDO statement.
Definition: Db.php:207
Rsi\Fred\Db\batch
batch($callback, $sql, $args=null, $limit=1000)
Run a callback function for every row in a batched SQL resultset.
Definition: Db.php:357
Rsi\Fred\Log\ERROR
const ERROR
Error conditions.
Definition: Log.php:11
Rsi\Fred\Db\updateSql
updateSql($columns, &$args)
Definition: Db.php:558
Rsi\Fred\Db\update
update($table, $columns, $where, $args=null, $insert_if_not_exists=false)
Update one or more record(s).
Definition: Db.php:680
Rsi\Fred\Db\EVENT_OPEN
const EVENT_OPEN
Definition: Db.php:17
Rsi\Fred\Db\each
each($callback, $sql, $args=null)
Run a callback function for every row in an SQL resultset.
Definition: Db.php:334
Rsi\Fred\Db\$migrateClassName
$migrateClassName
Class name for the migration tool.
Definition: Db.php:32
Rsi\Fred\Db\sum
sum($table, $column, $where=null, $args=null)
Total value from records that meet the requirements.
Definition: Db.php:794
Rsi\Fred\Db\$defTables
$defTables
Table definition to use (table name).
Definition: Db.php:31
Rsi\Fred\Db\$multiOperators
$multiOperators
Operators (key) and glue (value) for multi column where conditions.
Definition: Db.php:21
Rsi\Fred\Db\__invoke
__invoke($sql, $args=null)
Definition: Db.php:939
Rsi\Fred\Db\prepareArgs
prepareArgs(&$sql, &$args)
Definition: Db.php:188
Rsi\Fred\Db\create
create($table, $columns)
Create a record (if it does not exists).
Definition: Db.php:618
Rsi\Fred\Log\WARNING
const WARNING
Warning conditions.
Definition: Log.php:12
Rsi\Fred\Db\$_eachOptions
$_eachOptions
Do not (re-) use an existing connection (might be busy).
Definition: Db.php:40
Rsi\Fred\Db\$_startTime
$_startTime
Definition: Db.php:49
Rsi\Fred\Db\max
max($table, $column, $where=null, $args=null)
Biggest value from records that meet the requirements.
Definition: Db.php:772
Rsi\Fred\Db\checkTimer
checkTimer($sql, $args)
Definition: Db.php:166
Rsi\Fred\Db\average
average($table, $column, $where=null, $args=null)
Average value from records that meet the requirements.
Definition: Db.php:783
Rsi\Fred\Db\fetch
fetch($statement)
Fetch a row from an SQL statement.
Definition: Db.php:261
Rsi\Fred\Db\aggregate
aggregate($table, $function, $where=null, $args=null)
Run an aggregation query.
Definition: Db.php:737
Rsi\Fred\Db\dateTime
dateTime($time=null)
Convert a Unix timestamp to database date+time format.
Definition: Db.php:88
Rsi\Fred\Db\exists
exists($table, $where=null, $args=null)
Check if a record exists that meets the requirement.
Definition: Db.php:724
Rsi\Fred\Db\lastInsertId
lastInsertId()
Get the last auto-incremented ID.
Definition: Db.php:158
Rsi\Fred\Db\$_attributes
$_attributes
Connection specific attributes (key = attribute, value = value).
Definition: Db.php:43
Rsi\Fred\Exception
Definition: Exception.php:5
Rsi\Fred\Db\$logTables
$logTables
Tables (key) and key columns (value) to log changes for.
Definition: Db.php:30
Rsi\Fred\Db\escapeLike
escapeLike($value, $escape='\\')
Escape a value used for a "like" comparision.
Definition: Db.php:97
Rsi\Fred
Definition: Alive.php:3