From 3c42bf98612f74ddfe9e03c5b4afe7931f33f22a Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Tue, 9 Oct 2012 22:20:40 +0000 Subject: [PATCH] Template and DatabaseMysql fixes --- DatabaseMysql.php | 278 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 231 insertions(+), 47 deletions(-) diff --git a/DatabaseMysql.php b/DatabaseMysql.php index 104abc5..f64f174 100644 --- a/DatabaseMysql.php +++ b/DatabaseMysql.php @@ -17,14 +17,19 @@ if (!defined('MS_HASH')) define('MS_RESULT', 8); } +class DatabaseException extends Exception {} + class DatabaseMysql { var $host, $port, $socket, $username, $password, $dbName; - var $link; var $tableNames = array(); - var $queryCount = 0; var $queryLogFile; + var $reconnect = true; + + var $queryCount = 0; + var $link; + var $transactions; /** * Creates a MySQL connection object. @@ -46,7 +51,7 @@ class DatabaseMysql } else { - if ($host[1]) + if (isset($host[1])) { $port = $host[1]; } @@ -56,7 +61,6 @@ class DatabaseMysql $this->username = $username; $this->password = $password; $this->dbname = $dbname; - $this->connect(); } function connect() @@ -89,7 +93,13 @@ class DatabaseMysql function quote($value) { if ($value === NULL) + { return "NULL"; + } + if (!$this->link) + { + $this->connect(); + } return "'" . $this->link->real_escape_string($value) . "'"; } @@ -100,9 +110,100 @@ class DatabaseMysql { file_put_contents($this->queryLogFile, date("[Y-m-d H:i:s] ").$sql."\n", FILE_APPEND); } - return $this->link->query($sql, $fetchMode); + if (!$this->link) + { + $this->connect(); + } + $r = $this->link->query($sql, $fetchMode); + if (!$r) + { + if ($this->link->errno == 2006 && $this->reconnect) + { + // "MySQL server has gone away" + $this->link = false; + } + throw new DatabaseException($this->link->error, $this->link->errno); + } + return $r; } + /** + * Starts a transaction, supports nested calls and savepoints. + * @param boolean $savepoint Creates savepoints only this parameter is true. + */ + function begin($savepoint = false) + { + $this->transactions[] = $savepoint; + $n = count($this->transactions); + if ($n == 1) + { + return $this->query("BEGIN"); + } + elseif ($savepoint) + { + return $this->query("SAVEPOINT sp$n"); + } + return true; + } + + /** + * Commits transaction or releases last savepoint. + * If there is no last savepoint, just returns true. + */ + function commit() + { + $savepoint = array_pop($this->transactions); + if (!$this->transactions) + { + return $this->query("COMMIT"); + } + elseif ($savepoint) + { + return $this->query("RELEASE SAVEPOINT sp".count($this->transactions)); + } + return true; + } + + /** + * Rollbacks transaction or last savepoint. + * If there is no savepoint, returns false. + */ + function rollback() + { + $savepoint = array_pop($this->transactions); + if (!$this->transactions) + { + return $this->query("ROLLBACK"); + } + elseif ($savepoint) + { + return $this->query("ROLLBACK TO SAVEPOINT sp".count($this->transactions)); + } + return false; + } + + function errno() + { + return $this->link->errno; + } + + function error() + { + return $this->link->error; + } + + /** + * Builds WHERE-part of an SQL query. + * $where can also be a string - then it's passed as-is. + * + * @param array $where Query conditions: + * array( + * 'conditional expression', + * 'field_name' => 'value', + * 'field_name' => array('one', 'of', 'values'), + * 'field1,field2' => array(array(1, 2), array(3, 4)), + * ) + */ function where_builder($where) { if (!is_array($where)) @@ -140,6 +241,24 @@ class DatabaseMysql return $where; } + /** + * Builds an SQL query text. + * + * @param mixed $tables see $this->tablesBuilder() + * @param mixed $fields Field definitions - either a string or an array. + * Strings are passed to resulting query text as-is. + * Arrays have the following format: + * array('field1', 'alias2' => 'expression2', ...) + * @param mixed $where see $this->whereBuilder() + * @param array $options query options - array of: + * 'CALC_FOUND_ROWS' + * 'NO_CACHE' or 'CACHE' + * 'FOR UPDATE' or 'LOCK IN SHARE MODE' + * 'GROUP BY' => array($groupby_field1 => 'ASC', $groupby_field2 => 'DESC') + * 'ORDER BY' => array($orderby_field1 => 'ASC', $orderby_field2 => 'DESC') + * 'LIMIT' => array($limit, $offset) or array($limit) or just $limit + * 'OFFSET' => $offset, for the case when 'LIMIT' is just $limit + */ function select_builder($tables, $fields, $where, $options = NULL) { if (!$options) @@ -151,19 +270,25 @@ class DatabaseMysql $options[$v] = true; } if (is_array($fields)) + { + foreach ($fields as $k => $v) + if (!ctype_digit($k)) + $fields[$k] = "$v AS ".$this->quoteId($k); $fields = join(',', $fields); + } $where = $this->where_builder($where); $tables = $this->tables_builder($tables); $sql = 'SELECT '; - if ($options['CALC_FOUND_ROWS'] || $options['SQL_CALC_FOUND_ROWS']) + if (isset($options['CALC_FOUND_ROWS']) || isset($options['SQL_CALC_FOUND_ROWS'])) $sql .= 'SQL_CALC_FOUND_ROWS '; - if ($options['NO_CACHE'] || $options['SQL_NO_CACHE']) + if (isset($options['NO_CACHE']) || isset($options['SQL_NO_CACHE'])) $sql .= 'SQL_NO_CACHE '; - elseif ($options['CACHE'] || $options['SQL_CACHE']) + elseif (isset($options['CACHE']) || isset($options['SQL_CACHE'])) $sql .= 'SQL_CACHE '; $sql .= "$fields FROM $tables WHERE $where"; - if ($g = $options['GROUP BY']) + if (isset($options['GROUP BY'])) { + $g = $options['GROUP BY']; if (is_array($g)) { $g1 = array(); @@ -178,8 +303,9 @@ class DatabaseMysql } $sql .= " GROUP BY $g"; } - if ($g = $options['ORDER BY']) + if (isset($options['ORDER BY'])) { + $g = $options['ORDER BY']; if (is_array($g)) { $g1 = array(); @@ -194,20 +320,44 @@ class DatabaseMysql } $sql .= " ORDER BY $g"; } - if ($g = $options['LIMIT']) - { - if (is_array($g)) - $g = join(',', $g); - if ($g) - $sql .= " LIMIT $g"; - } - if ($options['FOR UPDATE']) + $sql .= $this->limit_option($options); + if (isset($options['FOR UPDATE'])) $sql .= ' FOR UPDATE'; - elseif ($options['LOCK IN SHARE MODE']) + elseif (isset($options['LOCK IN SHARE MODE'])) $sql .= ' LOCK IN SHARE MODE'; return $sql; } + /** + * Handles a single LIMIT or LIMIT and OFFSET options. + */ + function limit_option($options) + { + if (isset($options['LIMIT'])) + { + $g = $options['LIMIT']; + if (is_array($g)) + $g = join(',', $g); + elseif ($g && isset($options['OFFSET'])) + $g = "$options[OFFSET], $g"; + if ($g) + return " LIMIT $g"; + } + return ''; + } + + /** + * Builds FROM-part of an SQL query. + * + * $tables = array( + * 'table', + * 'alias' => 'table', + * 'alias' => array('INNER', 'table_name', $where_for_on_clause), + * 'alias' => array('INNER', $nested_tables), + * ) + * or just a string "`table1` INNER JOIN `table2` ON ..." + * names taken into `backticks` will be transformed using $this->tableNames + */ function tables_builder($tables) { if (is_array($tables)) @@ -255,15 +405,31 @@ class DatabaseMysql function tables_builder_pregcb1($m) { - $t = $this->tableNames[$m[2]]; - if (!$t) - $t = $m[2]; - return $m[1].$t; + if (isset($this->tableNames[$m[2]])) + return $m[1].$this->tableNames[$m[2]]; + return $m[1].$m[2]; } + /** + * Run a SELECT query and return results. + * + * Usage: either + * $this->select($tables, $fields, $where, $options, $format) + * using $this->select_builder() or + * $this->select($sql_text, $format) + * using query text. + * + * @param int $format Return format, bitmask of MS_XX constants: + * MS_RESULT = return mysqli result object to manually fetch from + * MS_LIST = return rows as indexed arrays + * MS_HASH = return rows as associative arrays (default) + * MS_ROW = only return the first row + * MS_COL = only return the first column + * MS_VALUE = only return the first cell (just 1 value) + */ function select($tables, $fields = '*', $where = 1, $options = NULL, $format = 0) { - if (!strcmp(intval($fields), "$fields")) + if (ctype_digit("$fields")) { $sql = $tables; $format = $fields; @@ -280,10 +446,10 @@ class DatabaseMysql $r = array(); if ($format & MS_ROW) { + if (!count($r)) + return NULL; if ($format & MS_COL) { - if (!count($r)) - return NULL; $r = $r[0]; $k = array_keys($r); $r = $r[$k[0]]; @@ -311,29 +477,37 @@ class DatabaseMysql return $this->select('SELECT FOUND_ROWS()', MS_VALUE); } + /** + * Delete a set of rows. + * @param mixed $tables see $this->tables_builder() + * @param mixed $where see $this->where_builder() + * @param array $options Options for query: + * 'LIMIT' => array($limit, $offset) or array($limit) or just $limit + * 'OFFSET' => $offset, for the case when 'LIMIT' is just $limit + */ function delete($tables, $where, $options = NULL) { $tables = $this->tables_builder($tables); $where = $this->where_builder($where); $sql = "DELETE FROM $tables WHERE $where"; - if (is_array($options)) - { - if ($g = $options['LIMIT']) - { - if (is_array($g)) - $g = join(',', $g); - if ($g) - $sql .= " LIMIT $g"; - } - } + $sql .= $this->limit_option($options); $this->query($sql); return $this->link->affected_rows; } + /** + * Builds an INSERT query. + * @param string $table Table name to insert rows to. + * @param array $rows Array of table rows to be inserted. + * @param boolean $onduplicatekey If true, include + * ON DUPLICATE KEY UPDATE column=VALUES(column) for all columns. + */ function insert_builder($table, $rows, $onduplicatekey = false) { - if ($this->tableNames[$table]) + if (isset($this->tableNames[$table])) + { $table = $this->tableNames[$table]; + } $key = array_keys($rows[0]); foreach ($rows as &$r) { @@ -355,6 +529,12 @@ class DatabaseMysql return $sql; } + /** + * Insert a single row into $table and return inserted ID. + * @param string $table Table name to insert row to. + * @param array $rows Row to be inserted. + * @return int $insert_id Autoincrement ID of inserted row (if appropriate). + */ function insert_row($table, $row) { $sql = $this->insert_builder($table, array($row)); @@ -368,6 +548,19 @@ class DatabaseMysql return $this->link->insert_id; } + /** + * Update or insert rows into $table. + * Update query: $this->update($table, $set, $where, $options); + * Insert-or-update query: $this->update($table, $rows); + * + * @param string $table Table name to update. + * @param array $rows One row or array of rows for insert-or-update query. + * @param array $set Assoc array with values for update query. + * @param array $where Conditions for update query, see $this->where_builder(). + * @param array $options Options for update query: + * 'LIMIT' => array($limit, $offset) or array($limit) or just $limit + * 'OFFSET' => $offset, for the case when 'LIMIT' is just $limit + */ function update($table, $rows, $where = NULL, $options = NULL) { if (!$rows) @@ -394,16 +587,7 @@ class DatabaseMysql } $where = $this->where_builder($where); $sql = 'UPDATE ' . $this->tables_builder($table) . ' SET ' . implode(', ', $sql) . ' WHERE ' . $where; - if (is_array($options)) - { - if ($g = $options['LIMIT']) - { - if (is_array($g)) - $g = join(',', $g); - if ($g) - $sql .= " LIMIT $g"; - } - } + $sql .= $this->limit_option($options); } if ($this->query($sql)) return $this->link->affected_rows;