Created
January 3, 2012 14:17
-
-
Save thekid/1555053 to your computer and use it in GitHub Desktop.
XP Framework: MySQL local socket connectivity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/core/src/main/php/rdbms/mysqlx/LocalSocket.class.php b/core/src/main/php/rdbms/mysqlx/LocalSocket.class.php | |
| new file mode 100644 | |
| index 0000000..a5c18c9 | |
| --- /dev/null | |
| +++ b/core/src/main/php/rdbms/mysqlx/LocalSocket.class.php | |
| @@ -0,0 +1,66 @@ | |
| +<?php | |
| +/* This class is part of the XP framework | |
| + * | |
| + * $Id$ | |
| + */ | |
| + | |
| + uses('io.File'); | |
| + | |
| + /** | |
| + * Local socket: If "localhost" is supplied as host name to a MySqlx | |
| + * connection, a local socket is used depending on the operating system. | |
| + * To force using TCP/IP, use the value "127.0.0.1" instead. | |
| + * | |
| + * This exist for compatibility reasons with the MySQL client library! | |
| + * | |
| + * @see http://dev.mysql.com/doc/refman/5.1/en/option-files.html | |
| + */ | |
| + abstract class LocalSocket extends Object { | |
| + | |
| + /** | |
| + * Returns the implementation for the given operating system. | |
| + * | |
| + * @param string os operating system name, e.g. PHP_OS | |
| + * @return rdbms.mysqlx.LocalSocket | |
| + */ | |
| + public static function forName($os) { | |
| + if (0 === strncasecmp($os, 'Win', 3)) { | |
| + return XPClass::forName('rdbms.mysqlx.NamedPipe')->newInstance(); // TBI | |
| + } else { | |
| + return XPClass::forName('rdbms.mysqlx.UnixSocket')->newInstance(); | |
| + } | |
| + } | |
| + | |
| + /** | |
| + * Parse my.cnf file and return sections | |
| + * | |
| + * @param io.File ini | |
| + * @return [:[:string]] sections | |
| + */ | |
| + protected function parse($cnf) { | |
| + $cnf->open(FILE_MODE_READ); | |
| + $section= NULL; | |
| + $sections= array(); | |
| + while (FALSE !== ($line= $cnf->readLine())) { | |
| + if ('' === $line || '#' === $line{0}) { | |
| + continue; | |
| + } else if ('[' === $line{0}) { | |
| + $section= strtolower(trim($line, '[]')); | |
| + } else if (FALSE !== ($p= strpos($line, '='))) { | |
| + $key= strtolower(trim(substr($line, 0, $p))); | |
| + $value= trim(substr($line, $p+ 1)); | |
| + $sections[$section][$key]= $value; | |
| + } | |
| + } | |
| + $cnf->close(); | |
| + return $sections; | |
| + } | |
| + | |
| + /** | |
| + * Creates the socket instance | |
| + * | |
| + * @return peer.Socket | |
| + */ | |
| + public abstract function newInstance(); | |
| + } | |
| +?> | |
| diff --git a/core/src/main/php/rdbms/mysqlx/MySqlxConnection.class.php b/core/src/main/php/rdbms/mysqlx/MySqlxConnection.class.php | |
| index ad4ad60..b5347d4 100644 | |
| --- a/core/src/main/php/rdbms/mysqlx/MySqlxConnection.class.php | |
| +++ b/core/src/main/php/rdbms/mysqlx/MySqlxConnection.class.php | |
| @@ -11,7 +11,8 @@ | |
| 'rdbms.mysqlx.MySqlxProtocol', | |
| 'rdbms.Transaction', | |
| 'rdbms.StatementFormatter', | |
| - 'rdbms.mysql.MysqlDialect' | |
| + 'rdbms.mysql.MysqlDialect', | |
| + 'rdbms.mysqlx.LocalSocket' | |
| ); | |
| /** | |
| @@ -37,7 +38,18 @@ | |
| public function __construct($dsn) { | |
| parent::__construct($dsn); | |
| $this->formatter= new StatementFormatter($this, new MysqlDialect()); | |
| - $this->handle= new MysqlxProtocol(new Socket($this->dsn->getHost(), $this->dsn->getPort(3306))); | |
| + | |
| + // Compatibility with MSQL client library: If "localhost" is supplied | |
| + // as host name, use a local socket. To force using TCP/IP, use the | |
| + // value "127.0.0.1" instead. | |
| + $host= $this->dsn->getHost(); | |
| + if ('localhost' === $host) { | |
| + $sock= LocalSocket::forName(PHP_OS)->newInstance(); | |
| + } else { | |
| + $sock= new Socket($host, $this->dsn->getPort(3306)); | |
| + } | |
| + | |
| + $this->handle= new MysqlxProtocol($sock); | |
| } | |
| /** | |
| diff --git a/core/src/main/php/rdbms/mysqlx/UnixSocket.class.php b/core/src/main/php/rdbms/mysqlx/UnixSocket.class.php | |
| new file mode 100644 | |
| index 0000000..b716b0a | |
| --- /dev/null | |
| +++ b/core/src/main/php/rdbms/mysqlx/UnixSocket.class.php | |
| @@ -0,0 +1,65 @@ | |
| +<?php | |
| +/* This class is part of the XP framework | |
| + * | |
| + * $Id$ | |
| + */ | |
| + | |
| + $package= 'rdbms.mysqlx'; | |
| + | |
| + uses('rdbms.mysqlx.LocalSocket', 'peer.BSDSocket'); | |
| + | |
| + /** | |
| + * Use an AF_UNIX socket. The socket's location is determined in the | |
| + * following way: | |
| + * <ol> | |
| + * <li>First, the well-known locations /tmp/mysql.sock and /var/lib/mysql/mysql.sock | |
| + * are checked, in that order | |
| + * </li> | |
| + * <li>Then, the environment variable named "MYSQL_UNIX_PORT" is tested</li> | |
| + * <li>Finally, the MySQL configuration file is looked for inside the current | |
| + * user's home directory (<tt>~/.my.cnf</tt>) and then searched for in | |
| + * the directories /etc/ and /etc/mysql/ by name "my.cnf" | |
| + * </li> | |
| + * </ol> | |
| + * | |
| + * @see xp://rdbms.mysqlx.LocalSocket | |
| + * @see http://dev.mysql.com/doc/refman/5.1/en/problems-with-mysql-sock.html | |
| + */ | |
| + class rdbms·mysqlx·UnixSocket extends LocalSocket { | |
| + | |
| + /** | |
| + * Find local socket | |
| + * | |
| + * @return string or NULL if no file can be found | |
| + */ | |
| + protected function locate() { | |
| + | |
| + // 1. Check well-known locations, 2. environment | |
| + foreach (array('/tmp/mysql.sock', '/var/lib/mysql/mysql.sock', getenv('MYSQL_UNIX_PORT')) as $file) { | |
| + if (file_exists($file)) return $file; | |
| + } | |
| + | |
| + // 3. Check config files | |
| + foreach (array(getenv('HOME').'/.my.cnf', '/etc/my.cnf', '/etc/mysql/my.cnf') as $ini) { | |
| + if (!file_exists($ini)) continue; | |
| + $options= $this->parse(new File($ini)); | |
| + if (isset($options['client']['socket'])) return $options['client']['socket']; | |
| + } | |
| + | |
| + return NULL; | |
| + } | |
| + | |
| + /** | |
| + * Creates the socket instance | |
| + * | |
| + * @return peer.Socket | |
| + */ | |
| + public function newInstance() { | |
| + $sock= new BSDSocket($this->locate(), -1); | |
| + $sock->setDomain(AF_UNIX); | |
| + $sock->setType(SOCK_STREAM); | |
| + $sock->setProtocol(0); | |
| + return $sock; | |
| + } | |
| + } | |
| +?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* This class is part of the XP framework | |
| * | |
| * $Id$ | |
| */ | |
| uses('io.File'); | |
| /** | |
| * Local socket: If "localhost" is supplied as host name to a MySqlx | |
| * connection, a local socket is used depending on the operating system. | |
| * To force using TCP/IP, use the value "127.0.0.1" instead. | |
| * | |
| * This exist for compatibility reasons with the MySQL client library! | |
| * | |
| * @see http://dev.mysql.com/doc/refman/5.1/en/option-files.html | |
| */ | |
| abstract class LocalSocket extends Object { | |
| /** | |
| * Returns the implementation for the given operating system. | |
| * | |
| * @param string os operating system name, e.g. PHP_OS | |
| * @return rdbms.mysqlx.LocalSocket | |
| */ | |
| public static function forName($os) { | |
| if (0 === strncasecmp($os, 'Win', 3)) { | |
| return XPClass::forName('rdbms.mysqlx.NamedPipe')->newInstance(); // TBI | |
| } else { | |
| return XPClass::forName('rdbms.mysqlx.UnixSocket')->newInstance(); | |
| } | |
| } | |
| /** | |
| * Parse my.cnf file and return sections | |
| * | |
| * @param io.File ini | |
| * @return [:[:string]] sections | |
| */ | |
| protected function parse($cnf) { | |
| $cnf->open(FILE_MODE_READ); | |
| $section= NULL; | |
| $sections= array(); | |
| while (FALSE !== ($line= $cnf->readLine())) { | |
| if ('' === $line || '#' === $line{0}) { | |
| continue; | |
| } else if ('[' === $line{0}) { | |
| $section= strtolower(trim($line, '[]')); | |
| } else if (FALSE !== ($p= strpos($line, '='))) { | |
| $key= strtolower(trim(substr($line, 0, $p))); | |
| $value= trim(substr($line, $p+ 1)); | |
| $sections[$section][$key]= $value; | |
| } | |
| } | |
| $cnf->close(); | |
| return $sections; | |
| } | |
| /** | |
| * Creates the socket instance | |
| * | |
| * @return peer.Socket | |
| */ | |
| public abstract function newInstance(); | |
| } | |
| ?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* This class is part of the XP framework | |
| * | |
| * $Id$ | |
| */ | |
| uses( | |
| 'rdbms.DBConnection', | |
| 'rdbms.mysqlx.MySqlxResultSet', | |
| 'rdbms.mysqlx.MySqlxBufferedResultSet', | |
| 'rdbms.mysqlx.MySqlxProtocol', | |
| 'rdbms.Transaction', | |
| 'rdbms.StatementFormatter', | |
| 'rdbms.mysql.MysqlDialect', | |
| 'rdbms.mysqlx.LocalSocket' | |
| ); | |
| /** | |
| * Connection to MySQL Databases | |
| * | |
| * @see http://mysql.org/ | |
| * @test xp://net.xp_framework.unittest.rdbms.TokenizerTest | |
| * @test xp://net.xp_framework.unittest.rdbms.DBTest | |
| * @purpose Database connection | |
| */ | |
| class MySqlxConnection extends DBConnection { | |
| protected $affected= -1; | |
| static function __static() { | |
| DriverManager::register('mysql+x', new XPClass(__CLASS__)); | |
| } | |
| /** | |
| * Constructor | |
| * | |
| * @param rdbms.DSN dsn | |
| */ | |
| public function __construct($dsn) { | |
| parent::__construct($dsn); | |
| $this->formatter= new StatementFormatter($this, new MysqlDialect()); | |
| // Compatibility with MSQL client library: If "localhost" is supplied | |
| // as host name, use a local socket. To force using TCP/IP, use the | |
| // value "127.0.0.1" instead. | |
| $host= $this->dsn->getHost(); | |
| if ('localhost' === $host) { | |
| $sock= LocalSocket::forName(PHP_OS)->newInstance(); | |
| } else { | |
| $sock= new Socket($host, $this->dsn->getPort(3306)); | |
| } | |
| $this->handle= new MysqlxProtocol($sock); | |
| } | |
| /** | |
| * Connect | |
| * | |
| * @param bool reconnect default FALSE | |
| * @return bool success | |
| * @throws rdbms.SQLConnectException | |
| */ | |
| public function connect($reconnect= FALSE) { | |
| if ($this->handle->connected) return TRUE; // Already connected | |
| if (!$reconnect && (NULL === $this->handle->connected)) return FALSE; // Previously failed connecting | |
| try { | |
| $this->handle->connect($this->dsn->getUser(), $this->dsn->getPassword()); | |
| $this->_obs && $this->notifyObservers(new DBEvent(__FUNCTION__, $reconnect)); | |
| } catch (IOException $e) { | |
| $this->handle->connected= NULL; | |
| $this->_obs && $this->notifyObservers(new DBEvent(__FUNCTION__, $reconnect)); | |
| throw new SQLConnectException($e->getMessage(), $this->dsn); | |
| } | |
| try { | |
| $this->handle->exec('set names LATIN1'); | |
| // Figure out sql_mode and update formatter's escaperules accordingly | |
| // - See: http://bugs.mysql.com/bug.php?id=10214 | |
| // - Possible values: http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html | |
| // "modes is a list of different modes separated by comma (,) characters." | |
| $modes= array_flip(explode(',', this(this($this->handle->consume($this->handle->query( | |
| "show variables like 'sql_mode'" | |
| )), 0), 1))); | |
| } catch (IOException $e) { | |
| // Ignore | |
| } | |
| // NO_BACKSLASH_ESCAPES: Disable the use of the backslash character | |
| // (\) as an escape character within strings. With this mode enabled, | |
| // backslash becomes any ordinary character like any other. | |
| // (Implemented in MySQL 5.0.1) | |
| isset($modes['NO_BACKSLASH_ESCAPES']) && $this->formatter->dialect->setEscapeRules(array( | |
| '"' => '""' | |
| )); | |
| return parent::connect(); | |
| } | |
| /** | |
| * Disconnect | |
| * | |
| * @return bool success | |
| */ | |
| public function close() { | |
| if (!$this->handle->connected) return FALSE; | |
| $this->handle->close(); | |
| return TRUE; | |
| } | |
| /** | |
| * Select database | |
| * | |
| * @param string db name of database to select | |
| * @return bool success | |
| * @throws rdbms.SQLStatementFailedException | |
| */ | |
| public function selectdb($db) { | |
| try { | |
| $this->handle->exec('use '.$db); | |
| return TRUE; | |
| } catch (IOException $e) { | |
| throw new SQLStatementFailedException($e->getMessage()); | |
| } | |
| } | |
| /** | |
| * Retrieve identity | |
| * | |
| * @return var identity value | |
| */ | |
| public function identity($field= NULL) { | |
| $i= $this->query('select last_insert_id() as xp_id')->next('xp_id'); | |
| $this->_obs && $this->notifyObservers(new DBEvent(__FUNCTION__, $i)); | |
| return $i; | |
| } | |
| /** | |
| * Retrieve number of affected rows for last query | |
| * | |
| * @return int | |
| */ | |
| protected function affectedRows() { | |
| return $this->affected; | |
| } | |
| /** | |
| * Execute any statement | |
| * | |
| * @param string sql | |
| * @param bool buffered default TRUE | |
| * @return rdbms.ResultSet or TRUE if no resultset was created | |
| * @throws rdbms.SQLException | |
| */ | |
| protected function query0($sql, $buffered= TRUE) { | |
| if (!$this->handle->connected) { | |
| if (!($this->flags & DB_AUTOCONNECT)) throw new SQLStateException('Not connected'); | |
| $c= $this->connect(); | |
| // Check for subsequent connection errors | |
| if (FALSE === $c) throw new SQLStateException('Previously failed to connect.'); | |
| } | |
| try { | |
| $this->handle->ready() || $this->handle->cancel(); | |
| $result= $this->handle->query($sql); | |
| } catch (MySqlxProtocolException $e) { | |
| $message= $e->getMessage().' (sqlstate '.$e->sqlstate.')'; | |
| switch ($e->error) { | |
| case 2006: // MySQL server has gone away | |
| case 2013: // Lost connection to MySQL server during query | |
| throw new SQLConnectionClosedException('Statement failed: '.$message, $sql, $e->error); | |
| case 1213: // Deadlock | |
| throw new SQLDeadlockException($message, $sql, $e->error); | |
| default: // Other error | |
| throw new SQLStatementFailedException($message, $sql, $e->error); | |
| } | |
| } catch (IOException $e) { | |
| throw new SQLStatementFailedException($e->getMessage()); | |
| } | |
| if (!is_array($result)) { | |
| $this->affected= $result; | |
| return TRUE; | |
| } | |
| $this->affected= -1; | |
| if (!$buffered || $this->flags & DB_UNBUFFERED) { | |
| return new MysqlxResultSet($this->handle, $result, $this->tz); | |
| } else { | |
| return new MysqlxBufferedResultSet($this->handle, $result, $this->tz); | |
| } | |
| } | |
| /** | |
| * Begin a transaction | |
| * | |
| * @param rdbms.Transaction transaction | |
| * @return rdbms.Transaction | |
| */ | |
| public function begin($transaction) { | |
| if (!$this->query('begin')) return FALSE; | |
| $transaction->db= $this; | |
| return $transaction; | |
| } | |
| /** | |
| * Rollback a transaction | |
| * | |
| * @param string name | |
| * @return bool success | |
| */ | |
| public function rollback($name) { | |
| return $this->query('rollback'); | |
| } | |
| /** | |
| * Commit a transaction | |
| * | |
| * @param string name | |
| * @return bool success | |
| */ | |
| public function commit($name) { | |
| return $this->query('commit'); | |
| } | |
| /** | |
| * Creates a string representation | |
| * | |
| * @return string | |
| */ | |
| public function toString() { | |
| return $this->getClassName().'(->'.$this->dsn->toString().', '.$this->handle->toString().')'; | |
| } | |
| } | |
| ?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* This class is part of the XP framework | |
| * | |
| * $Id$ | |
| */ | |
| $package= 'rdbms.mysqlx'; | |
| uses('rdbms.mysqlx.LocalSocket', 'peer.BSDSocket'); | |
| /** | |
| * Use an AF_UNIX socket. The socket's location is determined in the | |
| * following way: | |
| * <ol> | |
| * <li>First, the well-known locations /tmp/mysql.sock and /var/lib/mysql/mysql.sock | |
| * are checked, in that order | |
| * </li> | |
| * <li>Then, the environment variable named "MYSQL_UNIX_PORT" is tested</li> | |
| * <li>Finally, the MySQL configuration file is looked for inside the current | |
| * user's home directory (<tt>~/.my.cnf</tt>) and then searched for in | |
| * the directories /etc/ and /etc/mysql/ by name "my.cnf" | |
| * </li> | |
| * </ol> | |
| * | |
| * @see xp://rdbms.mysqlx.LocalSocket | |
| * @see http://dev.mysql.com/doc/refman/5.1/en/problems-with-mysql-sock.html | |
| */ | |
| class rdbms·mysqlx·UnixSocket extends LocalSocket { | |
| /** | |
| * Find local socket | |
| * | |
| * @return string or NULL if no file can be found | |
| */ | |
| protected function locate() { | |
| // 1. Check well-known locations, 2. environment | |
| foreach (array('/tmp/mysql.sock', '/var/lib/mysql/mysql.sock', getenv('MYSQL_UNIX_PORT')) as $file) { | |
| if (file_exists($file)) return $file; | |
| } | |
| // 3. Check config files | |
| foreach (array(getenv('HOME').'/.my.cnf', '/etc/my.cnf', '/etc/mysql/my.cnf') as $ini) { | |
| if (!file_exists($ini)) continue; | |
| $options= $this->parse(new File($ini)); | |
| if (isset($options['client']['socket'])) return $options['client']['socket']; | |
| } | |
| return NULL; | |
| } | |
| /** | |
| * Creates the socket instance | |
| * | |
| * @return peer.Socket | |
| */ | |
| public function newInstance() { | |
| $sock= new BSDSocket($this->locate(), -1); | |
| $sock->setDomain(AF_UNIX); | |
| $sock->setType(SOCK_STREAM); | |
| $sock->setProtocol(0); | |
| return $sock; | |
| } | |
| } | |
| ?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment