Skip to content

Commit ab5b07c

Browse files
committed
PHP-170 - Duration nanos should support a signed 64-bit range
1 parent a6d539e commit ab5b07c

14 files changed

+256
-68
lines changed

Diff for: behat.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default:
77
88
contexts:
99
- FeatureContext:
10-
cassandra_version: 3.0.8
10+
cassandra_version: "3.10"
1111

1212
cassandra-version-2.2:
1313
formatters:

Diff for: ext/config.m4

+2-2
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,8 @@ if test "$PHP_CASSANDRA" != "no"; then
270270
AC_MSG_CHECKING([for supported DataStax C/C++ driver version])
271271
PHP_CASSANDRA_FOUND_CASSANDRA_VERSION=`$AWK '/CASS_VERSION_MAJOR/ {printf $3"."} /CASS_VERSION_MINOR/ {printf $3"."} /CASS_VERSION_PATCH/ {printf $3}' $CPP_DRIVER_DIR/include/cassandra.h`
272272
PHP_CASSANDRA_FOUND_CASSANDRA_VERSION_NUMBER=`echo "${PHP_CASSANDRA_FOUND_CASSANDRA_VERSION}" | $AWK 'BEGIN { FS = "."; } { printf "%d", ([$]1 * 100 + [$]2) * 100 + [$]3;}'`
273-
if test "$PHP_CASSANDRA_FOUND_CASSANDRA_VERSION_NUMBER" -lt "20600"; then
274-
AC_MSG_ERROR([not supported. Driver version 2.6.0+ required (found $PHP_CASSANDRA_FOUND_CASSANDRA_VERSION)])
273+
if test "$PHP_CASSANDRA_FOUND_CASSANDRA_VERSION_NUMBER" -lt "20700"; then
274+
AC_MSG_ERROR([not supported. Driver version 2.7.0+ required (found $PHP_CASSANDRA_FOUND_CASSANDRA_VERSION)])
275275
else
276276
AC_MSG_RESULT([supported ($PHP_CASSANDRA_FOUND_CASSANDRA_VERSION)])
277277
fi

Diff for: ext/config.w32

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ARG_ENABLE("cassandra", "Enable DataStax PHP Cassandra extension", "no");
44

55
// Establish the minimum Cassandra C/C++ driver and PHP version
66
var driver_minimum_major = 2;
7-
var driver_minimum_minor = 6;
7+
var driver_minimum_minor = 7;
88
var driver_minimum_patch = 0;
99
var driver_minimum_version = driver_minimum_major + "." + driver_minimum_minor + "." + driver_minimum_patch;
1010
var php_minimum_major = 5;

Diff for: ext/php_driver_types.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ PHP_DRIVER_END_OBJECT_TYPE(inet)
176176
PHP_DRIVER_BEGIN_OBJECT_TYPE(duration)
177177
cass_int32_t months;
178178
cass_int32_t days;
179-
cass_int32_t nanos;
179+
cass_int64_t nanos;
180180
PHP_DRIVER_END_OBJECT_TYPE(duration)
181181

182182
PHP_DRIVER_BEGIN_OBJECT_TYPE(collection)

Diff for: ext/src/Duration.c

+40-33
Original file line numberDiff line numberDiff line change
@@ -15,64 +15,67 @@
1515

1616
zend_class_entry *php_driver_duration_ce = NULL;
1717

18-
static void to_string(zval *result, cass_int32_t value)
18+
static void to_string(zval *result, cass_int64_t value)
1919
{
2020
char *string;
21-
spprintf(&string, 0, "%d", value);
21+
spprintf(&string, 0, LL_FORMAT, value);
2222
PHP5TO7_ZVAL_STRING(result, string);
2323
efree(string);
2424
}
2525

26-
static int get_int32(zval* value, cass_int32_t* destination, const char* param_name TSRMLS_DC)
26+
static int get_param(zval* value,
27+
const char* param_name,
28+
cass_int64_t min,
29+
cass_int64_t max,
30+
cass_int64_t *destination TSRMLS_DC)
2731
{
28-
// Adapted from Bigint __construct method.
2932
if (Z_TYPE_P(value) == IS_LONG) {
30-
cass_int64_t long_value = Z_LVAL_P(value);
33+
php5to7_long long_value = Z_LVAL_P(value);
3134

32-
if (long_value > INT32_MAX || long_value < INT32_MIN) {
33-
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC,
34-
"%s must be between %d and %d, " LL_FORMAT " given",
35-
param_name, INT32_MIN, INT32_MAX, long_value);
35+
if (long_value > max || long_value < min) {
36+
zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
37+
"%s must be between " LL_FORMAT " and " LL_FORMAT ", " LL_FORMAT " given",
38+
param_name, min, max, long_value);
3639
return 0;
3740
}
3841

3942
*destination = long_value;
4043
} else if (Z_TYPE_P(value) == IS_DOUBLE) {
4144
double double_value = Z_DVAL_P(value);
4245

43-
if (double_value > INT32_MAX || double_value < INT32_MIN) {
44-
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC,
45-
"%s must be between %d and %d, %g given",
46-
param_name, INT32_MIN, INT32_MAX, double_value);
46+
if (double_value > max || double_value < min) {
47+
zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
48+
"%s must be between " LL_FORMAT " and " LL_FORMAT ", %g given",
49+
param_name, min, max, double_value);
4750
return 0;
4851
}
49-
*destination = (cass_int32_t) double_value;
52+
*destination = (cass_int64_t) double_value;
5053
} else if (Z_TYPE_P(value) == IS_STRING) {
5154
cass_int64_t parsed_big_int;
5255
if (!php_driver_parse_bigint(Z_STRVAL_P(value), Z_STRLEN_P(value), &parsed_big_int TSRMLS_CC)) {
5356
return 0;
5457
}
5558

56-
if (parsed_big_int > INT32_MAX || parsed_big_int < INT32_MIN) {
57-
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC,
58-
"%s must be between %d and %d, " LL_FORMAT " given",
59-
param_name, INT32_MIN, INT32_MAX, parsed_big_int);
59+
if (parsed_big_int > max || parsed_big_int < min) {
60+
zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
61+
"%s must be between " LL_FORMAT " and " LL_FORMAT ", " LL_FORMAT " given",
62+
param_name, min, max, parsed_big_int);
6063
return 0;
6164
}
62-
*destination = (cass_int32_t) parsed_big_int;
65+
*destination = parsed_big_int;
6366
} else if (Z_TYPE_P(value) == IS_OBJECT &&
6467
instanceof_function(Z_OBJCE_P(value), php_driver_bigint_ce TSRMLS_CC)) {
6568
php_driver_numeric *bigint = PHP_DRIVER_GET_NUMERIC(value);
6669
cass_int64_t bigint_value = bigint->data.bigint.value;
6770

68-
if (bigint_value > INT32_MAX || bigint_value < INT32_MIN) {
69-
zend_throw_exception_ex(php_driver_range_exception_ce, 0 TSRMLS_CC,
70-
"%s must be between %d and %d, " LL_FORMAT " given",
71-
param_name, INT32_MIN, INT32_MAX, bigint_value);
71+
if (bigint_value > max || bigint_value < min) {
72+
zend_throw_exception_ex(php_driver_invalid_argument_exception_ce, 0 TSRMLS_CC,
73+
"%s must be between " LL_FORMAT " and " LL_FORMAT ", " LL_FORMAT " given",
74+
param_name, min, max, bigint_value);
7275
return 0;
7376
}
7477

75-
*destination = (cass_int32_t) bigint_value;
78+
*destination = bigint_value;
7679
} else {
7780
throw_invalid_argument(value, param_name, "a long, a double, a numeric string or a " \
7881
PHP_DRIVER_NAMESPACE "\\Bigint" TSRMLS_CC);
@@ -91,7 +94,7 @@ char *php_driver_duration_to_string(php_driver_duration *duration)
9194
int is_negative = 0;
9295
cass_int32_t final_months = duration->months;
9396
cass_int32_t final_days = duration->days;
94-
cass_int32_t final_nanos = duration->nanos;
97+
cass_int64_t final_nanos = duration->nanos;
9598

9699
is_negative = final_months < 0 || final_days < 0 || final_nanos < 0;
97100
if (final_months < 0)
@@ -101,14 +104,15 @@ char *php_driver_duration_to_string(php_driver_duration *duration)
101104
if (final_nanos < 0)
102105
final_nanos = -final_nanos;
103106

104-
spprintf(&rep, 0, "%s%dmo%dd%dns", is_negative ? "-" : "", final_months, final_days, final_nanos);
107+
spprintf(&rep, 0, "%s%dmo%dd" LL_FORMAT "ns", is_negative ? "-" : "", final_months, final_days, final_nanos);
105108
return rep;
106109
}
107110

108111
void
109112
php_driver_duration_init(INTERNAL_FUNCTION_PARAMETERS)
110113
{
111114
zval *months, *days, *nanos;
115+
cass_int64_t param;
112116
php_driver_duration *self = NULL;
113117

114118
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zzz", &months, &days, &nanos) == FAILURE) {
@@ -117,20 +121,23 @@ php_driver_duration_init(INTERNAL_FUNCTION_PARAMETERS)
117121

118122
self = PHP_DRIVER_GET_DURATION(getThis());
119123

120-
if (!get_int32(months, &self->months, "months" TSRMLS_CC)) {
124+
if (!get_param(months, "months", INT32_MIN, INT32_MAX, &param TSRMLS_CC)) {
121125
return;
122126
}
123-
if (!get_int32(days, &self->days, "days" TSRMLS_CC)) {
127+
self->months = (cass_int32_t)param;
128+
129+
if (!get_param(days, "days", INT32_MIN, INT32_MAX, &param TSRMLS_CC)) {
124130
return;
125131
}
132+
self->days = (cass_int32_t)param;
126133

127-
// No need to check the result of nanos parsing; get_int64 sets the exception if there's
128-
// a failure, and we have no more work to do anyway.
129-
get_int32(nanos, &self->nanos, "nanos" TSRMLS_CC);
134+
if (!get_param(nanos, "nanos", INT64_MIN, INT64_MAX, &self->nanos TSRMLS_CC)) {
135+
return;
136+
}
130137

131138
// Verify that all three attributes are non-negative or non-positive.
132-
if (!(self->months <= 0 && self->days <= 0 && self->nanos <=0) &&
133-
!(self->months >= 0 && self->days >= 0 && self->nanos >=0)) {
139+
if (!(self->months <= 0 && self->days <= 0 && self->nanos <= 0) &&
140+
!(self->months >= 0 && self->days >= 0 && self->nanos >= 0)) {
134141
zend_throw_exception_ex(spl_ce_BadFunctionCallException, 0 TSRMLS_CC, "%s",
135142
"A duration must have all non-negative or non-positive attributes"
136143
);

Diff for: features/duration.feature

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
@cassandra-version-3.10
2+
Feature: Duration
3+
4+
PHP Driver supports the `Duration` datatype
5+
6+
Background:
7+
Given a running Cassandra cluster
8+
9+
Scenario: Use the duration type
10+
Given the following schema:
11+
"""cql
12+
CREATE KEYSPACE simplex WITH replication = {
13+
'class': 'SimpleStrategy',
14+
'replication_factor': 1
15+
};
16+
USE simplex;
17+
CREATE TABLE duration (k text PRIMARY KEY, d duration);
18+
"""
19+
And the following example:
20+
"""
21+
<?php
22+
$cluster = Cassandra::cluster()->build();
23+
$session = $cluster->connect("simplex");
24+
25+
$durations = array(
26+
array('two_days', new Cassandra\Duration(2, 0, 0)),
27+
array('twelve_hours', new Cassandra\Duration(0, 12, 0)),
28+
array('three_seconds', new Cassandra\Duration(0, 0, 3 * (1000 ** 3))),
29+
array('two_days_twelve_hours_and_three_seconds', new Cassandra\Duration(2, 12, 3 * (1000 ** 3)))
30+
);
31+
32+
foreach ($durations as $duration) {
33+
$options = array('arguments' => $duration);
34+
$session->execute("INSERT INTO duration (k, d) VALUES (?, ?)", $options);
35+
}
36+
37+
$rows = $session->execute("SELECT * FROM duration");
38+
39+
foreach ($rows as $row) {
40+
echo "{$row['k']}: {$row['d']}" . PHP_EOL;
41+
}
42+
"""
43+
When it is executed
44+
Then its output should contain these lines in any order:
45+
"""
46+
twelve_hours: 0mo12d0ns
47+
three_seconds: 0mo0d3000000000ns
48+
two_days_twelve_hours_and_three_seconds: 2mo12d3000000000ns
49+
two_days: 2mo0d0ns
50+
"""
51+

Diff for: features/duration_collections.feature

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
@cassandra-version-3.10
2+
Feature: Duration
3+
4+
PHP Driver supports the `Duration` datatype in collections
5+
6+
Background:
7+
Given a running Cassandra cluster
8+
9+
Scenario: Use the duration type in collections
10+
Given the following schema:
11+
"""cql
12+
CREATE KEYSPACE simplex WITH replication = {
13+
'class': 'SimpleStrategy',
14+
'replication_factor': 1
15+
};
16+
USE simplex;
17+
CREATE TYPE duration_user_type (duration1 duration, duration2 duration);
18+
CREATE TABLE durations (k text PRIMARY KEY,
19+
duration_list list<duration>,
20+
duration_tuple tuple<duration, duration>,
21+
duration_udt duration_user_type);
22+
"""
23+
And the following example:
24+
"""
25+
<?php
26+
$cluster = Cassandra::cluster()->build();
27+
$session = $cluster->connect("simplex");
28+
29+
# Construct collection arguments
30+
31+
$list = Cassandra\Type::collection(Cassandra\Type::duration())->create(
32+
new Cassandra\Duration(1, 2, 3)
33+
);
34+
$list->add(new Cassandra\Duration(4, 5, 6));
35+
36+
$tuple = Cassandra\Type::tuple(Cassandra\Type::duration(), Cassandra\Type::duration())->create(
37+
new Cassandra\Duration(1, 2, 3), new Cassandra\Duration(4, 5, 6)
38+
);
39+
40+
$udt = Cassandra\Type::userType('duration1', Cassandra\Type::duration(), 'duration2', Cassandra\Type::duration())->create(
41+
'duration1', new Cassandra\Duration(1, 2, 3)
42+
);
43+
$udt->set('duration2', new Cassandra\Duration(4, 5, 6));
44+
45+
46+
# Insert collections containing durations into table
47+
$options = array('arguments' =>
48+
array('key1', $list, $tuple, $udt)
49+
);
50+
$session->execute("INSERT INTO durations
51+
(k, duration_list, duration_tuple, duration_udt) VALUES
52+
(?, ?, ?, ?)", $options);
53+
# Select collections from table and print as WKT
54+
$rows = $session->execute("SELECT * FROM durations WHERE k = 'key1'");
55+
$row = $rows->first();
56+
57+
58+
$rows = $session->execute("SELECT * FROM durations WHERE k = 'key1'");
59+
$row = $rows->first();
60+
61+
echo 'The list contains durations: [' . implode(', ', $row['duration_list']->values()) . ']' . PHP_EOL;
62+
echo 'The tuple contains durations: (' . implode(', ', $row['duration_tuple']->values()) . ')' . PHP_EOL;
63+
$asWkt = array_map(function($k, $v) { return $k . ': ' . $v; },
64+
array_keys($row["duration_udt"]->values()), $row["duration_udt"]->values());
65+
echo 'The udt contains durations: {' . implode(', ', $asWkt) . '}' . PHP_EOL;
66+
"""
67+
When it is executed
68+
Then its output should contain these lines in any order:
69+
"""
70+
The list contains durations: [1mo2d3ns, 4mo5d6ns]
71+
The tuple contains durations: (1mo2d3ns, 4mo5d6ns)
72+
The udt contains durations: {duration1: 1mo2d3ns, duration2: 4mo5d6ns}
73+
"""
74+

Diff for: tests/unit/Cassandra/CollectionTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ public function scalarTypes()
112112
array(Type::timeuuid(), new Timeuuid(0)),
113113
array(Type::uuid(), new Uuid("03398c99-c635-4fad-b30a-3b2c49f785c2")),
114114
array(Type::varchar(), "varchar"),
115-
array(Type::varint(), new Varint("9223372036854775808"))
115+
array(Type::varint(), new Varint("9223372036854775808")),
116+
array(Type::duration(), new Duration(1, 2, 3))
116117
);
117118
}
118119

0 commit comments

Comments
 (0)