Skip to content

Instantly share code, notes, and snippets.

@thekid
Created January 3, 2012 14:17
Show Gist options
  • Select an option

  • Save thekid/1555053 to your computer and use it in GitHub Desktop.

Select an option

Save thekid/1555053 to your computer and use it in GitHub Desktop.
XP Framework: MySQL local socket connectivity
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;
+ }
+ }
+?>
<?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();
}
?>
<?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().')';
}
}
?>
<?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