insert('1', array('email' => '[email protected]', 'password' => 'test'));
// $users->get('1');
// $users->multiget(array(1, 2));
// $users->get_count('1');
// $users->get_range('1', '10');
// $users->remove('1');
// $users->remove('1', 'password');
//
class CassandraConn {
const DEFAULT_THRIFT_PORT = 9160;
static private $connections = array();
static private $last_error;
static public function add_node($host,
$port=self::DEFAULT_THRIFT_PORT,
$framed_transport=false,
$send_timeout=null,
$recv_timeout=null,
$persist=false) {
try {
// Create Thrift transport and binary protocol cassandra client
$socket = new TSocket($host, $port, $persist);
if($send_timeout) $socket->setSendTimeout($send_timeout);
if($recv_timeout) $socket->setRecvTimeout($recv_timeout);
if($framed_transport) {
$transport = new TFramedTransport($socket, true, true);
} else {
$transport = new TBufferedTransport($socket, 1024, 1024);
}
$client = new CassandraClient(new TBinaryProtocolAccelerated($transport));
// Store it in the connections
self::$connections[] = array(
'transport' => $transport,
'client' => $client
);
// Done
return TRUE;
} catch (TException $tx) {
self::$last_error = 'TException: '.$tx->getMessage() . "\n";
}
return FALSE;
}
// Default client
static public function get_client($write_mode = false) {
// * Try to connect to every cassandra node in order
// * Failed connections will be retried
// * Once a connection is opened, it stays open
// * TODO: add random and round robin order
// * TODO: add write-preferred and read-preferred nodes
shuffle(self::$connections);
foreach(self::$connections as $connection) {
try {
$transport = $connection['transport'];
$client = $connection['client'];
if(!$transport->isOpen()) {
$transport->open();
}
return $client;
} catch (TException $tx) {
self::$last_error = 'TException: '.$tx->getMessage() . "\n";
continue;
}
}
throw new Exception("Could not connect to a cassandra server");
}
}
class CassandraUtil {
// UUID
static public function uuid1($node="", $ns="") {
return UUID::generate(UUID::UUID_TIME,UUID::FMT_STRING, $node, $ns);
}
// Time
static public function get_time() {
// By Zach Buller ([email protected])
$time1 = microtime();
settype($time1, 'string'); //needs converted to string, otherwise will omit trailing zeroes
$time2 = explode(" ", $time1);
$time2[0] = preg_replace('/0./', '', $time2[0], 1);
$time3 = ($time2[1].$time2[0])/100;
return $time3;
}
}
class CassandraCF {
const DEFAULT_ROW_LIMIT = 100; // default max # of rows for get_range()
const DEFAULT_COLUMN_TYPE = "UTF8Type";
const DEFAULT_SUBCOLUMN_TYPE = null;
public $keyspace;
public $column_family;
public $is_super;
public $read_consistency_level;
public $write_consistency_level;
public $column_type; // CompareWith (TODO: actually use this)
public $subcolumn_type; // CompareSubcolumnsWith (TODO: actually use this)
public $parse_columns;
/*
BytesType: Simple sort by byte value. No validation is performed.
AsciiType: Like BytesType, but validates that the input can be parsed as US-ASCII.
UTF8Type: A string encoded as UTF8
LongType: A 64bit long
LexicalUUIDType: A 128bit UUID, compared lexically (by byte value)
TimeUUIDType: a 128bit version 1 UUID, compared by timestamp
*/
public function __construct($keyspace, $column_family,
$is_super=false,
$column_type=self::DEFAULT_COLUMN_TYPE,
$subcolumn_type=self::DEFAULT_SUBCOLUMN_TYPE,
$read_consistency_level=cassandra_ConsistencyLevel::ONE,
$write_consistency_level=cassandra_ConsistencyLevel::ZERO) {
// Vars
$this->keyspace = $keyspace;
$this->column_family = $column_family;
$this->is_super = $is_super;
$this->column_type = $column_type;
$this->subcolumn_type = $subcolumn_type;
$this->read_consistency_level = $read_consistency_level;
$this->write_consistency_level = $write_consistency_level;
// Toggles parsing columns
$this->parse_columns = true;
}
public function get($key, $super_column=NULL, $slice_start="", $slice_finish="", $column_reversed=False, $column_count=100) {
$column_parent = new cassandra_ColumnParent();
$column_parent->column_family = $this->column_family;
$column_parent->super_column = $this->unparse_column_name($super_column, true);
$slice_range = new cassandra_SliceRange();
$slice_range->count = $column_count;
$slice_range->reversed = $column_reversed;
$slice_range->start = $slice_start ? $this->unparse_column_name($slice_start, false) : "";
$slice_range->finish = $slice_finish ? $this->unparse_column_name($slice_finish, false) : "";
$predicate = new cassandra_SlicePredicate();
$predicate->slice_range = $slice_range;
$client = CassandraConn::get_client();
$resp = $client->get_slice($this->keyspace, $key, $column_parent, $predicate, $this->read_consistency_level);
if($super_column) {
return $this->supercolumns_or_columns_to_array($resp, false);
} else {
return $this->supercolumns_or_columns_to_array($resp);
}
}
public function multiget($keys, $slice_start="", $slice_finish="") {
$column_parent = new cassandra_ColumnParent();
$column_parent->column_family = $this->column_family;
$column_parent->super_column = NULL;
$slice_range = new cassandra_SliceRange();
$slice_range->start = $slice_start ? $this->unparse_column_name($slice_start, false) : "";
$slice_range->finish = $slice_finish ? $this->unparse_column_name($slice_finish, false) : "";
$predicate = new cassandra_SlicePredicate();
$predicate->slice_range = $slice_range;
$client = CassandraConn::get_client();
$resp = $client->multiget_slice($this->keyspace, $keys, $column_parent, $predicate, $this->read_consistency_level);
$ret = null;
// foreach($keys as $sk => $k) {
// $ret[$k] = $this->supercolumns_or_columns_to_array($resp[$k]);
// }
foreach($resp as $key => $val) {
$ret[$key] = $this->supercolumns_or_columns_to_array($val);
}
return $ret;
}
public function get_count($key, $super_column=null) {
$column_path = new cassandra_ColumnPath();
$column_path->column_family = $this->column_family;
$column_path->super_column = $super_column;
$client = CassandraConn::get_client();
$resp = $client->get_count($this->keyspace, $key, $column_path, $this->read_consistency_level);
return $resp;
}
public function get_range($start_key="", $end_key="", $row_count=self::DEFAULT_ROW_LIMIT, $slice_start="", $slice_finish="") {
$column_parent = new cassandra_ColumnParent();
$column_parent->column_family = $this->column_family;
$column_parent->super_column = NULL;
$slice_range = new cassandra_SliceRange();
$slice_range->start = $slice_start ? $this->unparse_column_name($slice_start, true) : "";
$slice_range->finish = $slice_finish ? $this->unparse_column_name($slice_finish, true) : "";
$predicate = new cassandra_SlicePredicate();
$predicate->slice_range = $slice_range;
$key_range = new cassandra_KeyRange();
$key_range->start_key = $start_key;
$key_range->end_key = $end_key;
$key_range->count = $row_count;
$client = CassandraConn::get_client();
$resp = $client->get_range_slices($this->keyspace, $column_parent, $predicate, $key_range, $this->read_consistency_level);
return $this->keyslices_to_array($resp);
}
public function insert($key, $columns) {
$timestamp = CassandraUtil::get_time();
$cfmap = array();
$cfmap[$key][$this->column_family] = $this->array_to_mutation($columns, $timestamp);
$client = CassandraConn::get_client();
$resp = $client->batch_mutate($this->keyspace, $cfmap, $this->write_consistency_level);
return $resp;
}
public function remove($key, $column_name=null) {
$timestamp = CassandraUtil::get_time();
$column_path = new cassandra_ColumnPath();
$column_path->column_family = $this->column_family;
if($this->is_super) {
$column_path->super_column = $this->unparse_column_name($column_name, true);
} else {
$column_path->column = $this->unparse_column_name($column_name, false);
}
$client = CassandraConn::get_client();
$resp = $client->remove($this->keyspace, $key, $column_path, $timestamp, $this->write_consistency_level);
return $resp;
}
// Wrappers
public function get_list($key, $key_name='key', $slice_start="", $slice_finish="") {
// Must be on supercols!
$resp = $this->get($key, NULL, $slice_start, $slice_finish);
$ret = array();
foreach($resp as $_key => $_value) {
$_value[$key_name] = $_key;
$ret[] = $_value;
}
return $ret;
}
public function get_range_list($key_name='key', $start_key="", $end_key="",
$row_count=self::DEFAULT_ROW_LIMIT, $slice_start="", $slice_finish="") {
$resp = $this->get_range($start_key, $end_key, $row_count, $slice_start, $slice_finish);
$ret = array();
foreach($resp as $_key => $_value) {
if(!empty($_value)) { // filter nulls
$_value[$key_name] = $_key;
$ret[] = $_value;
}
}
return $ret;
}
public function multiget_list($keys, $key_name='key', $slice_start="", $slice_finish="") {
$resp = $this->multiget($keys, $slice_start, $slice_finish);
$ret = array();
foreach($resp as $_key => $_value) {
$_value[$key_name] = $_key;
$ret[] = $_value;
}
return $ret;
}
// Helpers for parsing Cassandra's thrift objects into PHP arrays
public function keyslices_to_array($keyslices) {
$ret = null;
foreach($keyslices as $keyslice) {
$key = $keyslice->key;
$columns = $keyslice->columns;
$ret[$key] = $this->supercolumns_or_columns_to_array($columns);
}
return $ret;
}
public function supercolumns_or_columns_to_array($array_of_c_or_sc, $parse_as_columns=true) {
$ret = null;
foreach($array_of_c_or_sc as $c_or_sc) {
if($c_or_sc->column) { // normal columns
$name = $this->parse_column_name($c_or_sc->column->name, $parse_as_columns);
$value = $c_or_sc->column->value;
$ret[$name] = $value;
} else if($c_or_sc->super_column) { // super columns
$name = $this->parse_column_name($c_or_sc->super_column->name, $parse_as_columns);
$columns = $c_or_sc->super_column->columns;
$ret[$name] = $this->columns_to_array($columns);
}
}
return $ret;
}
public function columns_to_array($array_of_c) {
$ret = null;
foreach($array_of_c as $c) {
$name = $this->parse_column_name($c->name, false);
$value = $c->value;
$ret[$name] = $value;
}
return $ret;
}
// Helpers for turning PHP arrays into Cassandra's thrift objects
public function array_to_mutation($array, $timestamp=null) {
if(empty($timestamp)) $timestamp = CassandraUtil::get_time();
$c_or_sc = $this->array_to_supercolumns_or_columns($array, $timestamp);
$ret = null;
foreach($c_or_sc as $row) {
$mutation = new cassandra_Mutation();
$mutation->column_or_supercolumn = $row;
$ret[] = $mutation;
}
return $ret;
}
public function array_to_supercolumns_or_columns($array, $timestamp=null) {
if(empty($timestamp)) $timestamp = CassandraUtil::get_time();
$ret = null;
foreach($array as $name => $value) {
$c_or_sc = new cassandra_ColumnOrSuperColumn();
if(is_array($value)) {
$c_or_sc->super_column = new cassandra_SuperColumn();
$c_or_sc->super_column->name = $this->unparse_column_name($name, true);
$c_or_sc->super_column->columns = $this->array_to_columns($value, $timestamp);
$c_or_sc->super_column->timestamp = $timestamp;
} else {
$c_or_sc = new cassandra_ColumnOrSuperColumn();
$c_or_sc->column = new cassandra_Column();
$c_or_sc->column->name = $this->unparse_column_name($name, true);
$c_or_sc->column->value = $this->to_column_value($value);;
$c_or_sc->column->timestamp = $timestamp;
}
$ret[] = $c_or_sc;
}
return $ret;
}
public function array_to_columns($array, $timestamp=null) {
if(empty($timestamp)) $timestamp = CassandraUtil::get_time();
$ret = null;
foreach($array as $name => $value) {
$column = new cassandra_Column();
$column->name = $this->unparse_column_name($name, false);
$column->value = $this->to_column_value($value);
$column->timestamp = $timestamp;
$ret[] = $column;
}
return $ret;
}
public function to_column_value($thing) {
if($thing === null) return "";
return $thing;
}
// ARGH
public function parse_column_name($column_name, $is_column=true) {
if(!$this->parse_columns) return $column_name;
if(!$column_name) return NULL;
$type = $is_column ? $this->column_type : $this->subcolumn_type;
if($type == "LexicalUUIDType" || $type == "TimeUUIDType") {
return UUID::convert($column_name, UUID::FMT_BINARY, UUID::FMT_STRING);
} else if($type == "LongType") {
return $this->unpack_longtype($column_name);
} else {
return $column_name;
}
}
public function unparse_column_name($column_name, $is_column=true) {
if(!$this->parse_columns) return $column_name;
if(!$column_name) return NULL;
$type = $is_column ? $this->column_type : $this->subcolumn_type;
if($type == "LexicalUUIDType" || $type == "TimeUUIDType") {
return UUID::convert($column_name, UUID::FMT_STRING, UUID::FMT_BINARY);
} else if($type == "LongType") {
return $this->pack_longtype($column_name);
} else {
return $column_name;
}
}
// See http://webcache.googleusercontent.com/search?q=cache:9jjbeSy434UJ:wiki.apache.org/cassandra/FAQ+cassandra+php+%22A+long+is+exactly+8+bytes%22&cd=1&hl=en&ct=clnk&gl=us
public function pack_longtype($x) {
return pack('C8',
($x >> 56) & 0xff, ($x >> 48) & 0xff, ($x >> 40) & 0xff, ($x >> 32) & 0xff,
($x >> 24) & 0xff, ($x >> 16) & 0xff, ($x >> 8) & 0xff, $x & 0xff
);
}
public function unpack_longtype($x) {
$a = unpack('C8', $x);
return ($a[1] << 56) + ($a[2] << 48) + ($a[3] << 40) + ($a[4] << 32) + ($a[5] << 24) + ($a[6] << 16) + ($a[7] << 8) + $a[8];
}
}