configuration = $configuration; } public function escapeString($string) { if (!$this->connection) { $this->establishConnection(); } return mysql_real_escape_string($string, $this->connection); } public function escapeColumnName($name) { return '`'.str_replace('`', '\\`', $name).'`'; } public function escapeMultilineComment($comment) { // These can either terminate a comment, confuse the hell out of the parser, // make MySQL execute the comment as a query, or, in the case of semicolon, // are quasi-dangerous because the semicolon could turn a broken query into // a working query plus an ignored query. static $map = array( '--' => '(DOUBLEDASH)', '*/' => '(STARSLASH)', '//' => '(SLASHSLASH)', '#' => '(HASH)', '!' => '(BANG)', ';' => '(SEMICOLON)', ); $comment = str_replace( array_keys($map), array_values($map), $comment); // For good measure, kill anything else that isn't a nice printable // character. $comment = preg_replace('/[^\x20-\x7F]+/', ' ', $comment); return '/* '.$comment.' */'; } public function escapeStringForLikeClause($value) { $value = $this->escapeString($value); // Ideally the query shouldn't be modified after safely escaping it, // but we need to escape _ and % within LIKE terms. $value = str_replace( // Even though we've already escaped, we need to replace \ with \\ // because MYSQL unescapes twice inside a LIKE clause. See note // at mysql.com. However, if the \ is being used to escape a single // quote ('), then the \ should not be escaped. Thus, after all \ // are replaced with \\, we need to revert instances of \\' back to // \'. array('\\', '\\\\\'', '_', '%'), array('\\\\', '\\\'', '\_', '\%'), $value); return $value; } private function getConfiguration($key, $default = null) { return idx($this->configuration, $key, $default); } private function establishConnection() { $this->connection = null; $user = $this->getConfiguration('user'); $host = $this->getConfiguration('host'); $conn = @mysql_connect( $host, $user, $this->getConfiguration('pass'), $new_link = true, $flags = 0); if (!$conn) { $errno = mysql_errno(); $error = mysql_error(); throw new AphrontQueryConnectionException( "Attempt to connect to {$user}@{$host} failed with error #{$errno}: ". "{$error}."); } $ret = @mysql_select_db($this->getConfiguration('database'), $conn); if (!$ret) { $this->throwQueryException($conn); } $this->connection = $conn; } public function getInsertID() { return mysql_insert_id($this->requireConnection()); } public function getAffectedRows() { return mysql_affected_rows($this->requireConnection()); } public function getTransactionKey() { return (int)$this->requireConnection(); } private function requireConnection() { if (!$this->connection) { $this->establishConnection(); } return $this->connection; } public function selectAllResults() { $result = array(); $res = $this->lastResult; if ($res == null) { throw new Exception('No query result to fetch from!'); } while (($row = mysql_fetch_assoc($res)) !== false) { $result[] = $row; } return $result; } public function executeRawQuery($raw_query) { $this->lastResult = null; $retries = 3; while ($retries--) { try { if (!$this->connection) { $this->establishConnection(); } $result = mysql_query($raw_query, $this->connection); if ($result) { $this->lastResult = $result; break; } $this->throwQueryException($this->connection); } catch (AphrontQueryConnectionLostException $ex) { if (!$retries) { throw $ex; } if ($this->isInsideTransaction()) { throw $ex; } $this->connection = null; } } } private function throwQueryException($connection) { $errno = mysql_errno($connection); $error = mysql_error($connection); switch ($errno) { case 2013: // Connection Dropped case 2006: // Gone Away throw new AphrontQueryConnectionLostException("#{$errno}: {$error}"); case 1213: // Deadlock case 1205: // Lock wait timeout exceeded throw new AphrontQueryRecoverableException("#{$errno}: {$error}"); case 1062: // Duplicate Key $matches = null; $key = null; if (preg_match('/for key \'(.*)\'$/', $error, $matches)) { $key = $matches[1]; } throw new AphrontQueryDuplicateKeyException($key, "{$errno}: {$error}"); default: // TODO: 1064 is syntax error, and quite terrible in production. throw new AphrontQueryException("#{$errno}: {$error}"); } } }