index( 'test_db', 'test_table', 'PRIMARY', array('id','name','code') ); * // Works with InnoDB * $hs->insert( 'PRIMARY', array( 1, 'FOO', 'F@' ) ); * // Works with InnoDB and MyISAM * $results = $hs->select('PRIMARY', '=', 1 ); * // Works with InnoDB * $hs->update( 'PRIMARY', '=', 1, array( '1', 'FOO UPDATED AT '.time() ) ); * // Works with InnoDB and MyISAM * $hs->delete( 'PRIMARY', '=', 1 ); * * Written against https://github.com/ahiguti/HandlerSocket-Plugin-for-MySQL/blob/master/docs-en/protocol.en.txt */ class handler_socket { var $debug = false; var $read = null; var $write = null; var $indexes = array(); var $lookup = array(); /* Initialize a handlersocket client. * * @param $host string the hostname of ip address of the server * @param $read_port integer the port number to connect to for read connections * @param $write_post integer the port number to connect to for write connections */ function handler_socket( $host, $read_port, $write_port ) { $this->read = (object)array( 'fp' => null, 'host' => $host, 'port' => $read_port, 'errno' => null, 'error' => null ); $this->write = (object)array( 'fp' => null, 'host' => $host, 'port' => $write_port, 'errno' => null, 'error' => null ); } /* Define an index, and the columns you want to interract with when using it * * @param $db string the name, in MySQL, of the database in which the table exists * @param $table string the name, in MySQL, of the table on which the index exists * @param $key string the name, in MySQL, of the index you wish to use on the table * @param $columns array the columns you wish to interract with. This includes querying against, returning, inserting, and updating */ function index( $db, $table, $key, $columns ) { if ( isset( $this->lookup[$key] ) ) return true; $this->lookup[$key] = count( $this->indexes ); $this->indexes[] = (object)array( 'd' => $db, 't' => $table, 'k' => $key, 'c' => $columns, 'read' => false, 'write' => false, ); } /* * Select against a defined index * * @param $key string the defined index you wish to use, use "PRIMARY" fpr the primary key * @param $op string the operation you want to use, one of '=', '>', '>=', '<', or '<=' * @param $values array a list of strings to compare to the index columns using the $op to test * @param $limit integer * @param $offset integer */ function select( $key, $op, $values, $limit=1, $offset=0 ) { if ( !$this->open( $key ) ) return false; if ( !$values ) return false; if ( !is_array($values) ) $values = array( $values ); $id = $this->lookup[$key]; $request = array( $id, $op, (string)count($values) ); foreach( $values as $val ) $request[] = (string)$val; $request[] = $limit; $request[] = $offset; $request = $this->create_request( $request ); return $this->parse_response( $this->query( 'read', $request ), $key ); } /* * Delete via comparison against a defined index * * @param $key string the defined index you wish to use, use "PRIMARY" fpr the primary key * @param $op string the operation you want to use, one of '=', '>', '>=', '<', or '<=' * @param $compare array a list of strings to compare to the index columns using the $op to test * @param $limit integer * @param $offset integer */ function delete( $key, $op, $compare, $limit=1, $offset=0 ) { if ( !$this->open( $key, 'write' ) ) return false; if ( !is_array( $compare ) ) $compare = array( $compare ); $id = $this->lookup[$key]; $request = array( $id, $op, (string)count($compare) ); foreach( $compare as $val ) $request[] = (string)$val; $request[] = $limit; $request[] = $offset; $request[] = 'D'; $request = $this->create_request( $request ); return $this->parse_response( $this->query( 'write', $request ), false ); } /* * Update via comparison against a defined index * * @param $key string the defined index you wish to use, use "PRIMARY" fpr the primary key * @param $op string the operation you want to use, one of '=', '>', '>=', '<', or '<=' * @param $compare array a list of strings to compare to the index columns using the $op to test * @param $update array a list of strings to use, mapping to the columns in the defined index * @param $limit integer * @param $offset integer */ function update( $key, $op, $compare, $update, $limit=1, $offset=0 ) { if ( !$this->open( $key, 'write' ) ) return false; if ( !$update ) return false; if ( !is_array( $compare ) ) $compare = array( $compare ); if ( !is_array( $update ) ) $update = array( $update ); $id = $this->lookup[$key]; $request = array( $id, $op, (string)count($compare) ); foreach( $compare as $val ) $request[] = (string)$val; $request[] = $limit; $request[] = $offset; $request[] = 'U'; foreach( $update as $val ) $request[] = (string)$val; $request = $this->create_request( $request ); return $this->parse_response( $this->query( 'write', $request ), true ); } /* * Insert into a given table, using the key as a reference * * @param $key string the defined index you wish to use, use "PRIMARY" fpr the primary key * @param $values array a list of strings to use for the insert, mapping to the columns in the defined index */ function insert( $key, $values ) { if ( !$this->open( $key, 'write' ) ) return false; if ( !is_array( $values ) ) $values = array( $valuse ); $id = $this->lookup[$key]; $request = array( $id, '+', (string)count($values) ); foreach( $values as $val ) $request[] = (string)$val; $request = $this->create_request( $request ); return $this->parse_response( $this->query( 'write', $request ), false ); } private function open( $key, $type="read" ) { if ( !isset( $this->lookup[$key] ) ) return false; $id = $this->lookup[$key]; if ( $this->indexes[$id]->$type ) return true; $request = $this->create_request( array( 'P', (string)$id, $this->indexes[$id]->d, $this->indexes[$id]->t, $this->indexes[$id]->k, implode( ',', $this->indexes[$id]->c ) )); if ( !$this->parse_response( $this->query( $type, $request ), true ) ) return false; $this->indexes[$id]->$type = 1; return true; } private function query( $type='read', $request ) { if ( !$this->connect( $type ) ) return false; if ( $this->debug ) echo "> $type > $request"; if ( false === fwrite( $this->$type->fp, $request ) ) return false; return $this->read( $type ); } private function connect( $type='read' ) { if ( !isset( $this->$type ) ) { if ( $this->debug ) echo "connect: $type, no such thing\n"; return false; } if ( $this->$type->fp ) { if ( $this->debug ) echo "connect: $type, already connected\n"; return true; } $this->$type->fp = fsockopen( $this->$type->host, $this->$type->port, $this->$type->errno, $this->$type->error ); if ( $this->$type->fp ) { if ( $this->debug ) echo "connect: $type, connected\n"; return true; } $this->$type->fp = null; if ( $this->debug ) echo "connect: $type, failed to connect\n"; return false; } private function read( $type='read' ) { if ( !isset( $this->$type ) ) return false; if ( !$this->$type->fp ) return false; $line = ''; $chars = 0; if ( $this->debug ) echo "< $type < "; while ( !feof( $this->$type->fp ) ) { $chars++; $c = fread( $this->$type->fp, 1 ); if ( $this->debug ) echo $c; if ( $c === "\x0a" ) break; if ( $chars < 2 ) continue; $line .= $c; } return $line; } private function create_request( $tokens ) { foreach( $tokens as $idx => $token ) $tokens[$idx] = $this->encode_token( $token ); return implode( "\x09", $tokens ) . "\x0a"; } private function parse_response( $line, $key=null ) { $tokens = explode( "\x09", $line ); foreach( $tokens as $idx => $token ) $tokens[$idx] == $this->unencode_token( $token ); $error = array_shift( $tokens ); if ( $error ) return false; // open, insert if ( $key === true ) return true; // find_modify if ( $key === false ) return array_shift( $tokens ); // find $cols = array_shift( $tokens ); $i = 0; $rval = array(); foreach( $tokens as $v ) { $pos = ++$i%$cols; if ( $pos == 1 ) $entry = array(); $n = $pos - 1; if ( $n == -1 ) $n = ( $cols - 1 ); if ( $key ) $n = $this->indexes[$this->lookup[$key]]->c[$n]; $entry[$n] = $v; if ( $pos == 0 ) $rval[] = (object)$entry; } return $rval; } private function unencode_token( $token ) { if ( $token === "\x00" ) return null; return str_replace( array( "\x01\x40", "\x01\x41", "\x01\x42", "\x01\x43", "\x01\x44", "\x01\x45", "\x01\x46", "\x01\x47", "\x01\x48", "\x01\x49", "\x01\x4a", "\x01\x4b", "\x01\x4c", "\x01\x4d", "\x01\x4e", "\x01\x4f", ), array( "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", ), $token ); } private function encode_token( $token ) { if ( $token === null ) return "\x00"; return str_replace( array( "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", ), array( "\x01\x40", "\x01\x41", "\x01\x42", "\x01\x43", "\x01\x44", "\x01\x45", "\x01\x46", "\x01\x47", "\x01\x48", "\x01\x49", "\x01\x4a", "\x01\x4b", "\x01\x4c", "\x01\x4d", "\x01\x4e", "\x01\x4f", ), $token ); } }