diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md index e073e3c..0678831 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ Cassandra client library for PHP -Cassandra client library for PHP, which support Protocol v3 (Cassandra 2.1) and asynchronous request +Cassandra client library for PHP, which support Protocol v4 (Cassandra 4) and asynchronous request ## Features -* Using Protocol v3 (Cassandra 2.1) +* Using Protocol v4 (Cassandra 4) * Support ssl/tls with stream transport layer * Support asynchronous and synchronous request * Support for logged, unlogged and counter batches @@ -233,6 +233,18 @@ All types are supported. // Inet new Cassandra\Type\Inet('127.0.0.1'); +// Date + new Cassandra\Type\Date(date('Y-m-d', strtotime('now'))); + +// Time + new Cassandra\Type\Time(date('H:i:s', strtotime('now'))); + +// Smallint + new Cassandra\Type\SmallInt(32700); + +// Tinyint + new Cassandra\Type\TinyInt(255); + // Int new Cassandra\Type\PhpInt(1); diff --git a/composer.json b/composer.json index 0c75e64..b675e3b 100755 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "duoshuo/php-cassandra", - "description": "Cassandra client library for PHP, which support Protocol v3 and asynchronous request", + "description": "Cassandra client library for PHP, which support Protocol v4 and asynchronous request", "minimum-stability": "stable", "keywords" : [ "cassandra", @@ -18,6 +18,10 @@ { "name": "Evseev Nikolay", "email": "evseevnn@gmail.com" + }, + { + "name": "Ciaran Moore", + "email": "ciaran.f.moore@gmail.com" } ], "require": { diff --git a/src/Connection.php b/src/Connection.php index 5b4fc3e..2b2cda1 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -9,7 +9,7 @@ class Connection { * @var array */ protected $_options = [ - 'CQL_VERSION' => '3.0.0' + 'CQL_VERSION' => '4.0.0' ]; /** @@ -179,7 +179,7 @@ protected function _getResponse() { return $response; default: - throw new Exception('php-cassandra supports CQL binary protocol v3 only, please upgrade your Cassandra to 2.1 or later.'); + throw new Exception('php-cassandra supports CQL binary protocol v3+ only, please upgrade your Cassandra to 2.1 or later.'); } } diff --git a/src/Request/Startup.php b/src/Request/Startup.php index 01915b1..037a3c3 100644 --- a/src/Request/Startup.php +++ b/src/Request/Startup.php @@ -30,6 +30,14 @@ class Startup extends Request{ * different from the protocol version. * - "COMPRESSION": the compression algorithm to use for frames (See section 5). * This is optional, if not specified no compression will be used. + * - "NO_COMPACT": whether or not connection has to be established in compatibility + * mode. This mode will make all Thrift and Compact Tables to be exposed as if + * they were CQL Tables. This is optional; if not specified, the option will + * not be used. + * - "THROW_ON_OVERLOAD": In case of server overloaded with too many requests, by + * default the server puts back pressure on the client connection. Instead, the server + * can send an OverloadedException error message back to the client if this option is + * set to true. * * @param array $options */ diff --git a/src/Response/Error.php b/src/Response/Error.php index 175d661..c3fa8dc 100644 --- a/src/Response/Error.php +++ b/src/Response/Error.php @@ -12,6 +12,9 @@ class Error extends Response { const TRUNCATE_ERROR = 0x1003; const WRITE_TIMEOUT = 0x1100; const READ_TIMEOUT = 0x1200; + const READ_FAILURE = 0x1300; + const FUNCTION_FAILURE = 0x1400; + const WRITE_FAILURE = 0x1500; const SYNTAX_ERROR = 0x2000; const UNAUTHORIZED = 0x2100; const INVALID = 0x2200; @@ -27,95 +30,129 @@ class Error extends Response { * * @return array */ - public function getData() { - $this->_stream->offset(0); - $data = []; - $data['code'] = $this->_stream->readInt(); - - switch($data['code']){ - case self::SERVER_ERROR: - $data['message'] = "Server error: " . $this->_stream->readString(); - break; - - case self::PROTOCOL_ERROR: - $data['message'] = "Protocol error: " . $this->_stream->readString(); - break; - - case self::BAD_CREDENTIALS: - $data['message'] = "Bad credentials: " . $this->_stream->readString(); - break; - - case self::UNAVAILABLE_EXCEPTION: - $data['message'] = "Unavailable exception. Error data: " . var_export([ + public function getData() { + $this->_stream->offset(0); + $data = []; + $data['code'] = $this->_stream->readInt(); + + switch($data['code']){ + case self::SERVER_ERROR: + $data['message'] = "Server error - Something unexpected happened. This indicates a server-side bug: " . $this->_stream->readString(); + break; + + case self::PROTOCOL_ERROR: + $data['message'] = "Protocol error - Some client message triggered a protocol violation (for instance a QUERY message is sent before a STARTUP one has been sent): " . $this->_stream->readString(); + break; + + case self::BAD_CREDENTIALS: + $data['message'] = "Authentication error - Authentication was required and failed. The possible reason for failing depends on the authenticator in use, which may or may not include more detail in the accompanying error message: " . $this->_stream->readString(); + break; + // ERROR message body: + case self::UNAVAILABLE_EXCEPTION: + $data['message'] = "Unavailable exception. Error data: " . var_export([ 'error'=>$this->_stream->readString(), 'consistency' => $this->_stream->readShort(), 'node' => $this->_stream->readInt(), - 'replica' => $this->_stream->readInt() - ], true); - break; - - case self::OVERLOADED: - $data['message'] = "Overloaded: " . $this->_stream->readString(); - break; - - case self::IS_BOOTSTRAPPING: - $data['message'] = "Is_bootstrapping: " . $this->_stream->readString(); - break; - - case self::TRUNCATE_ERROR: - $data['message'] = "Truncate_error: " . $this->_stream->readString(); - break; - - case self::WRITE_TIMEOUT: - $data['message'] = "Write_timeout. Error data: " . var_export([ - 'error'=>$this->_stream->readString(), - 'consistency' => $this->_stream->readShort(), - 'node' => $this->_stream->readInt(), - 'replica' => $this->_stream->readInt(), - 'writeType' => $this->_stream->readString() - ], true); - break; - - case self::READ_TIMEOUT: - $data['message'] = "Read_timeout. Error data: " . var_export([ + 'replica' => $this->_stream->readInt() + ], true); + break; + + case self::OVERLOADED: + $data['message'] = "Overloaded - the request cannot be processed because the coordinator node is overloaded: " . $this->_stream->readString(); + break; + + case self::IS_BOOTSTRAPPING: + $data['message'] = "Is_bootstrapping - the request was a read request but the coordinator node is bootstrapping: " . $this->_stream->readString(); + break; + + case self::TRUNCATE_ERROR: + $data['message'] = "Truncate_error - Error during a truncation error: " . $this->_stream->readString(); + break; + // ERROR message body: + case self::WRITE_TIMEOUT: + $data['message'] = "Write_timeout - Timeout exception during a write request. Error data: " . var_export([ 'error'=>$this->_stream->readString(), 'consistency' => $this->_stream->readShort(), - 'node' => $this->_stream->readInt(), - 'replica' => $this->_stream->readInt(), - 'dataPresent' => $this->_stream->readChar() - ], true); - break; - - case self::SYNTAX_ERROR: - $data['message'] = "Syntax_error: " . $this->_stream->readString(); - break; - - case self::UNAUTHORIZED: - $data['message'] = "Unauthorized: " . $this->_stream->readString(); - break; - - case self::INVALID: - $data['message'] = "Invalid: " . $this->_stream->readString(); - break; - - case self::CONFIG_ERROR: - $data['message'] = "Config_error: " . $this->_stream->readString(); - break; - - case self::ALREADY_EXIST: - $data['message'] = "Already_exists: " . $this->_stream->readString(); - break; - - case self::UNPREPARED: - $data['message'] = "Unprepared: " . $this->_stream->readShort(); - break; - - default: - $data['message'] = 'Unknown error'; - } - - return $data; - } + 'node' => $this->_stream->readInt(), + 'replica' => $this->_stream->readInt(), + 'writeType' => $this->_stream->readString() + ], true); + break; + // ERROR message body: + case self::READ_TIMEOUT: + $data['message'] = "Read_timeout - Timeout exception during a read request. Error data: " . var_export([ + 'error'=>$this->_stream->readString(), + 'consistency' => $this->_stream->readShort(), + 'node' => $this->_stream->readInt(), + 'replica' => $this->_stream->readInt(), + 'dataPresent' => $this->_stream->readChar() + ], true); + break; + // ERROR message body: + case self::READ_FAILURE: + $data['message'] = "Read_failure - A non-timeout exception during a read request. Error data: " . var_export([ + 'error'=>$this->_stream->readString(), + 'consistency' => $this->_stream->readShort(), + 'node' => $this->_stream->readInt(), + 'replica' => $this->_stream->readInt(), + 'numfailures' => $this->_stream->readInt(), + 'dataPresent' => $this->_stream->readChar() + ], true); + break; + // ERROR message body: + case self::FUNCTION_FAILURE: + $data['message'] = "Function_failure - A (user defined) function failed during execution. Error data: " . var_export([ + 'error'=>$this->_stream->readString(), + 'keyspace' => $this->_stream->readString(), + 'function' => $this->_stream->readString(), + 'arg_types' => $this->_stream->readString(), + ], true); + break; + // ERROR message body: + case self::WRITE_FAILURE: + $data['message'] = "Write_failure - A non-timeout exception during a write request. Error data: " . var_export([ + 'error'=>$this->_stream->readString(), + 'consistency' => $this->_stream->readShort(), + 'node' => $this->_stream->readInt(), + 'replica' => $this->_stream->readInt(), + 'writeType' => $this->_stream->readString() + ], true); + break; + + case self::SYNTAX_ERROR: + $data['message'] = "Syntax_error - The submitted query has a syntax error: " . $this->_stream->readString(); + break; + + case self::UNAUTHORIZED: + $data['message'] = "Unauthorized - The logged user doesn't have the right to perform the query: " . $this->_stream->readString(); + break; + + case self::INVALID: + $data['message'] = "Invalid - The query is syntactically correct but invalid: " . $this->_stream->readString(); + break; + + case self::CONFIG_ERROR: + $data['message'] = "Config_error - The query is invalid because of some configuration issue: " . $this->_stream->readString(); + break; + // ERROR message body: + case self::ALREADY_EXIST: + $data['message'] = "Already_exists - The query attempted to create a keyspace or a table that was already existing: " . var_export([ + 'error'=>$this->_stream->readString(), + 'keyspace' => $this->_stream->readString(), + 'table' => $this->_stream->readString() + ], true); + break; + + case self::UNPREPARED: + $data['message'] = "Unprepared - Can be thrown while a prepared statement tries to be executed if the provided prepared statement ID is not known by this host: " . $this->_stream->readShort(); + break; + + default: + $data['message'] = 'Unknown error'; + } + + return $data; + } /** * diff --git a/src/Type/Base.php b/src/Type/Base.php index 8949c13..8e37e50 100644 --- a/src/Type/Base.php +++ b/src/Type/Base.php @@ -13,13 +13,16 @@ abstract class Base{ const DOUBLE = 0x0007; const FLOAT = 0x0008; const INT = 0x0009; - const TEXT = 0x000A; // deprecated in Protocol v3 const TIMESTAMP = 0x000B; const UUID = 0x000C; const VARCHAR = 0x000D; const VARINT = 0x000E; const TIMEUUID = 0x000F; const INET = 0x0010; + const DATE = 0x0011; + const TIME = 0x0012; + const SMALLINT = 0x0013; + const TINYINT = 0x0014; const COLLECTION_LIST = 0x0020; const COLLECTION_MAP = 0x0021; const COLLECTION_SET = 0x0022; @@ -29,7 +32,6 @@ abstract class Base{ public static $typeClassMap = [ self::ASCII => 'Cassandra\Type\Ascii', self::VARCHAR => 'Cassandra\Type\Varchar', - self::TEXT => 'Cassandra\Type\Varchar', // deprecated in Protocol v3 self::VARINT => 'Cassandra\Type\Varint', self::BIGINT => 'Cassandra\Type\Bigint', self::COUNTER => 'Cassandra\Type\Counter', @@ -43,6 +45,10 @@ abstract class Base{ self::UUID => 'Cassandra\Type\Uuid', self::TIMEUUID => 'Cassandra\Type\Timeuuid', self::INET => 'Cassandra\Type\Inet', + self::DATE => 'Cassandra\Type\Date', + self::TIME => 'Cassandra\Type\Time', + self::SMALLINT => 'Cassandra\Type\SmallInt', + self::TINYINT => 'Cassandra\Type\TinyInt', self::COLLECTION_LIST => 'Cassandra\Type\CollectionList', self::COLLECTION_SET => 'Cassandra\Type\CollectionSet', self::COLLECTION_MAP => 'Cassandra\Type\CollectionMap', diff --git a/src/Type/Date.php b/src/Type/Date.php new file mode 100644 index 0000000..4d2d238 --- /dev/null +++ b/src/Type/Date.php @@ -0,0 +1,36 @@ +_value = $parsedDate; + } + + public static function binary($dateString){ + $parsedDate = strtotime($dateString); + return pack('N', $parsedDate); + } + + /** + * @return string + */ + public static function parse($binary){ + $unpacked = unpack('N', $binary); + return date('Y-m-d', $unpacked[1]); + } +} +Explanation: + diff --git a/src/Type/SmallInt.php b/src/Type/SmallInt.php new file mode 100644 index 0000000..f68a0d6 --- /dev/null +++ b/src/Type/SmallInt.php @@ -0,0 +1,34 @@ + 32767) + throw new Exception('Smallint value out of range.'); + + $this->_value = (int) $value; + } + + public static function binary($value){ + return pack('n', $value); + } + + /** + * @return int + */ + public static function parse($binary){ + $unpacked = unpack('n', $binary); + return $unpacked[1]; + } +} \ No newline at end of file diff --git a/src/Type/Time.php b/src/Type/Time.php new file mode 100644 index 0000000..05d746c --- /dev/null +++ b/src/Type/Time.php @@ -0,0 +1,34 @@ +_value = $parsedTime; + } + + public static function binary($timeString){ + $parsedTime = strtotime($timeString); + return pack('N', $parsedTime); + } + + /** + * @return string + */ + public static function parse($binary){ + $unpacked = unpack('N', $binary); + return date('H:i:s', $unpacked[1]); + } +} \ No newline at end of file diff --git a/src/Type/TinyInt.php b/src/Type/TinyInt.php new file mode 100644 index 0000000..77cd1a3 --- /dev/null +++ b/src/Type/TinyInt.php @@ -0,0 +1,34 @@ + 255) + throw new Exception('Tinyint value out of range.'); + + $this->_value = (int) $value; + } + + public static function binary($value){ + return pack('C', $value); + } + + /** + * @return int + */ + public static function parse($binary){ + $unpacked = unpack('C', $binary); + return $unpacked[1]; + } +} \ No newline at end of file