Skip to content

Commit 5027842

Browse files
committed
PHPC-1571: Support server hedged reads via read preference
1 parent 1771d5c commit 5027842

7 files changed

+232
-3
lines changed

src/MongoDB/ReadPreference.c

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ zend_class_entry* php_phongo_readpreference_ce;
4040
* An exception will be thrown on error. */
4141
static bool php_phongo_readpreference_init_from_hash(php_phongo_readpreference_t* intern, HashTable* props) /* {{{ */
4242
{
43-
zval *mode, *tagSets, *maxStalenessSeconds;
43+
zval *mode, *tagSets, *maxStalenessSeconds, *hedge;
4444

4545
if ((mode = zend_hash_str_find(props, "mode", sizeof("mode") - 1)) && Z_TYPE_P(mode) == IS_STRING) {
4646
if (strcasecmp(Z_STRVAL_P(mode), PHONGO_READ_PRIMARY) == 0) {
@@ -111,6 +111,31 @@ static bool php_phongo_readpreference_init_from_hash(php_phongo_readpreference_t
111111
}
112112
}
113113

114+
if ((hedge = zend_hash_str_find(props, "hedge", sizeof("hedge") - 1))) {
115+
if (Z_TYPE_P(hedge) == IS_ARRAY || Z_TYPE_P(hedge) == IS_OBJECT) {
116+
bson_t* hedge_doc = bson_new();
117+
118+
if (mongoc_read_prefs_get_mode(intern->read_preference) == MONGOC_READ_PRIMARY) {
119+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires \"hedge\" field to not be present with \"primary\" mode", ZSTR_VAL(php_phongo_readpreference_ce->name));
120+
bson_destroy(hedge_doc);
121+
goto failure;
122+
}
123+
124+
php_phongo_zval_to_bson(hedge, PHONGO_BSON_NONE, hedge_doc, NULL);
125+
126+
if (EG(exception)) {
127+
bson_destroy(hedge_doc);
128+
goto failure;
129+
}
130+
131+
mongoc_read_prefs_set_hedge(intern->read_preference, hedge_doc);
132+
bson_destroy(hedge_doc);
133+
} else {
134+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires \"hedge\" field to be an array or object", ZSTR_VAL(php_phongo_readpreference_ce->name));
135+
goto failure;
136+
}
137+
}
138+
114139
return true;
115140

116141
failure:
@@ -238,6 +263,33 @@ static PHP_METHOD(ReadPreference, __construct)
238263
mongoc_read_prefs_set_max_staleness_seconds(intern->read_preference, maxStalenessSeconds);
239264
}
240265

266+
if (options && php_array_exists(options, "hedge")) {
267+
zval* hedge = php_array_fetchc(options, "hedge");
268+
269+
if (Z_TYPE_P(hedge) == IS_ARRAY || Z_TYPE_P(hedge) == IS_OBJECT) {
270+
bson_t* hedge_doc = bson_new();
271+
272+
if (mongoc_read_prefs_get_mode(intern->read_preference) == MONGOC_READ_PRIMARY) {
273+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "hedge may not be used with primary mode");
274+
bson_destroy(hedge_doc);
275+
return;
276+
}
277+
278+
php_phongo_zval_to_bson(hedge, PHONGO_BSON_NONE, hedge_doc, NULL);
279+
280+
if (EG(exception)) {
281+
bson_destroy(hedge_doc);
282+
return;
283+
}
284+
285+
mongoc_read_prefs_set_hedge(intern->read_preference, hedge_doc);
286+
bson_destroy(hedge_doc);
287+
} else {
288+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires \"hedge\" field to be an array or object", ZSTR_VAL(php_phongo_readpreference_ce->name));
289+
return;
290+
}
291+
}
292+
241293
if (!mongoc_read_prefs_is_valid(intern->read_preference)) {
242294
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Read preference is not valid");
243295
return;
@@ -264,6 +316,37 @@ static PHP_METHOD(ReadPreference, __set_state)
264316
php_phongo_readpreference_init_from_hash(intern, props);
265317
} /* }}} */
266318

319+
/* {{{ proto array|null MongoDB\Driver\ReadPreference::getHedge()
320+
Returns the ReadPreference hedge document */
321+
static PHP_METHOD(ReadPreference, getHedge)
322+
{
323+
php_phongo_readpreference_t* intern;
324+
const bson_t* hedge;
325+
326+
intern = Z_READPREFERENCE_OBJ_P(getThis());
327+
328+
if (zend_parse_parameters_none() == FAILURE) {
329+
return;
330+
}
331+
332+
hedge = mongoc_read_prefs_get_hedge(intern->read_preference);
333+
334+
if (!bson_empty0(hedge)) {
335+
php_phongo_bson_state state;
336+
337+
PHONGO_BSON_INIT_STATE(state);
338+
339+
if (!php_phongo_bson_to_zval_ex(bson_get_data(hedge), hedge->len, &state)) {
340+
zval_ptr_dtor(&state.zchild);
341+
return;
342+
}
343+
344+
RETURN_ZVAL(&state.zchild, 0, 1);
345+
} else {
346+
RETURN_NULL();
347+
}
348+
} /* }}} */
349+
267350
/* {{{ proto integer MongoDB\Driver\ReadPreference::getMaxStalenessSeconds()
268351
Returns the ReadPreference maxStalenessSeconds value */
269352
static PHP_METHOD(ReadPreference, getMaxStalenessSeconds)
@@ -353,11 +436,12 @@ static HashTable* php_phongo_readpreference_get_properties_hash(zval* object, bo
353436
HashTable* props;
354437
const char* modeString = NULL;
355438
const bson_t* tags;
439+
const bson_t* hedge;
356440
mongoc_read_mode_t mode;
357441

358442
intern = Z_READPREFERENCE_OBJ_P(object);
359443

360-
PHONGO_GET_PROPERTY_HASH_INIT_PROPS(is_debug, intern, props, 3);
444+
PHONGO_GET_PROPERTY_HASH_INIT_PROPS(is_debug, intern, props, 4);
361445

362446
if (!intern->read_preference) {
363447
return props;
@@ -366,6 +450,7 @@ static HashTable* php_phongo_readpreference_get_properties_hash(zval* object, bo
366450
tags = mongoc_read_prefs_get_tags(intern->read_preference);
367451
mode = mongoc_read_prefs_get_mode(intern->read_preference);
368452
modeString = php_phongo_readpreference_get_mode_string(mode);
453+
hedge = mongoc_read_prefs_get_hedge(intern->read_preference);
369454

370455
if (modeString) {
371456
zval z_mode;
@@ -398,6 +483,19 @@ static HashTable* php_phongo_readpreference_get_properties_hash(zval* object, bo
398483
zend_hash_str_update(props, "maxStalenessSeconds", sizeof("maxStalenessSeconds") - 1, &z_max_ss);
399484
}
400485

486+
if (!bson_empty0(hedge)) {
487+
php_phongo_bson_state state;
488+
489+
PHONGO_BSON_INIT_STATE(state);
490+
491+
if (!php_phongo_bson_to_zval_ex(bson_get_data(hedge), hedge->len, &state)) {
492+
zval_ptr_dtor(&state.zchild);
493+
goto done;
494+
}
495+
496+
zend_hash_str_update(props, "hedge", sizeof("hedge") - 1, &state.zchild);
497+
}
498+
401499
done:
402500
return props;
403501
} /* }}} */
@@ -424,6 +522,7 @@ static PHP_METHOD(ReadPreference, serialize)
424522
smart_str buf = { 0 };
425523
const char* modeString = NULL;
426524
const bson_t* tags;
525+
const bson_t* hedge;
427526
int64_t maxStalenessSeconds;
428527
mongoc_read_mode_t mode;
429528

@@ -441,8 +540,9 @@ static PHP_METHOD(ReadPreference, serialize)
441540
mode = mongoc_read_prefs_get_mode(intern->read_preference);
442541
modeString = php_phongo_readpreference_get_mode_string(mode);
443542
maxStalenessSeconds = mongoc_read_prefs_get_max_staleness_seconds(intern->read_preference);
543+
hedge = mongoc_read_prefs_get_hedge(intern->read_preference);
444544

445-
array_init_size(&retval, 3);
545+
array_init_size(&retval, 4);
446546

447547
if (modeString) {
448548
ADD_ASSOC_STRING(&retval, "mode", modeString);
@@ -465,6 +565,19 @@ static PHP_METHOD(ReadPreference, serialize)
465565
ADD_ASSOC_LONG_EX(&retval, "maxStalenessSeconds", maxStalenessSeconds);
466566
}
467567

568+
if (!bson_empty0(hedge)) {
569+
php_phongo_bson_state state;
570+
571+
PHONGO_BSON_INIT_STATE(state);
572+
573+
if (!php_phongo_bson_to_zval_ex(bson_get_data(hedge), hedge->len, &state)) {
574+
zval_ptr_dtor(&state.zchild);
575+
return;
576+
}
577+
578+
ADD_ASSOC_ZVAL_EX(&retval, "hedge", &state.zchild);
579+
}
580+
468581
PHP_VAR_SERIALIZE_INIT(var_hash);
469582
php_var_serialize(&buf, &retval, &var_hash);
470583
smart_str_0(&buf);
@@ -537,6 +650,7 @@ static zend_function_entry php_phongo_readpreference_me[] = {
537650
/* clang-format off */
538651
PHP_ME(ReadPreference, __construct, ai_ReadPreference___construct, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
539652
PHP_ME(ReadPreference, __set_state, ai_ReadPreference___set_state, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
653+
PHP_ME(ReadPreference, getHedge, ai_ReadPreference_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
540654
PHP_ME(ReadPreference, getMaxStalenessSeconds, ai_ReadPreference_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
541655
PHP_ME(ReadPreference, getMode, ai_ReadPreference_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
542656
PHP_ME(ReadPreference, getModeString, ai_ReadPreference_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)

tests/readPreference/readpreference-ctor-001.phpt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRI
77
var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, [['tag' => 'one']]));
88
var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY, []));
99
var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['maxStalenessSeconds' => 1000]));
10+
var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['hedge' => ['enabled' => true]]));
11+
var_dump(new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['hedge' => []]));
1012

1113
?>
1214
===DONE===
@@ -38,4 +40,17 @@ object(MongoDB\Driver\ReadPreference)#%d (%d) {
3840
["maxStalenessSeconds"]=>
3941
int(1000)
4042
}
43+
object(MongoDB\Driver\ReadPreference)#%d (%d) {
44+
["mode"]=>
45+
string(9) "secondary"
46+
["hedge"]=>
47+
object(stdClass)#%d (%d) {
48+
["enabled"]=>
49+
bool(true)
50+
}
51+
}
52+
object(MongoDB\Driver\ReadPreference)#%d (%d) {
53+
["mode"]=>
54+
string(9) "secondary"
55+
}
4156
===DONE===
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
MongoDB\Driver\ReadPreference construction (combining hedge with primary read preference)
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/tools.php';
7+
8+
echo throws(function() {
9+
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_PRIMARY, null, ['hedge' => ['enabled' => true]]);
10+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
11+
12+
?>
13+
===DONE===
14+
<?php exit(0); ?>
15+
--EXPECTF--
16+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
17+
hedge may not be used with primary mode
18+
===DONE===

tests/readPreference/readpreference-debug-001.phpt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ $tests = [
1515
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, [['dc' => 'ny']]),
1616
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, [['dc' => 'ny'], ['dc' => 'sf', 'use' => 'reporting'], []]),
1717
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['maxStalenessSeconds' => 1000]),
18+
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['hedge' => ['enabled' => true]]),
1819
];
1920

2021
foreach ($tests as $test) {
@@ -89,4 +90,13 @@ object(MongoDB\Driver\ReadPreference)#%d (%d) {
8990
["maxStalenessSeconds"]=>
9091
int(1000)
9192
}
93+
object(MongoDB\Driver\ReadPreference)#%d (%d) {
94+
["mode"]=>
95+
string(9) "secondary"
96+
["hedge"]=>
97+
object(stdClass)#%d (%d) {
98+
["enabled"]=>
99+
bool(true)
100+
}
101+
}
92102
===DONE===
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
MongoDB\Driver\ReadPreference::getHedge()
3+
--FILE--
4+
<?php
5+
6+
require_once __DIR__ . '/../utils/tools.php';
7+
8+
$tests = [
9+
[],
10+
['enabled' => true],
11+
(object) ['enabled' => true],
12+
['foo' => 'bar'],
13+
];
14+
15+
foreach ($tests as $test) {
16+
$rp = new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['hedge' => $test]);
17+
var_dump($rp->getHedge());
18+
}
19+
20+
?>
21+
===DONE===
22+
<?php exit(0); ?>
23+
--EXPECTF--
24+
NULL
25+
object(stdClass)#%d (%d) {
26+
["enabled"]=>
27+
bool(true)
28+
}
29+
object(stdClass)#%d (%d) {
30+
["enabled"]=>
31+
bool(true)
32+
}
33+
object(stdClass)#%d (%d) {
34+
["foo"]=>
35+
string(3) "bar"
36+
}
37+
===DONE===

tests/readPreference/readpreference-serialization-001.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ $tests = [
1515
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, [['dc' => 'ny']]),
1616
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, [['dc' => 'ny'], ['dc' => 'sf', 'use' => 'reporting'], []]),
1717
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['maxStalenessSeconds' => 1000]),
18+
new MongoDB\Driver\ReadPreference(MongoDB\Driver\ReadPreference::RP_SECONDARY, null, ['hedge' => ['enabled' => true]]),
1819
];
1920

2021
foreach ($tests as $test) {
@@ -184,4 +185,25 @@ object(MongoDB\Driver\ReadPreference)#%d (%d) {
184185
int(1000)
185186
}
186187

188+
object(MongoDB\Driver\ReadPreference)#%d (%d) {
189+
["mode"]=>
190+
string(9) "secondary"
191+
["hedge"]=>
192+
object(stdClass)#%d (%d) {
193+
["enabled"]=>
194+
bool(true)
195+
}
196+
}
197+
bool(true)
198+
C:29:"MongoDB\Driver\ReadPreference":82:{a:2:{s:4:"mode";s:9:"secondary";s:5:"hedge";O:8:"stdClass":1:{s:7:"enabled";b:1;}}}
199+
object(MongoDB\Driver\ReadPreference)#%d (%d) {
200+
["mode"]=>
201+
string(9) "secondary"
202+
["hedge"]=>
203+
object(stdClass)#%d (%d) {
204+
["enabled"]=>
205+
bool(true)
206+
}
207+
}
208+
187209
===DONE===

tests/readPreference/readpreference-set_state_error-001.phpt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ echo throws(function() {
3535
MongoDB\Driver\ReadPreference::__set_state(['mode' => 'primary', 'maxStalenessSeconds' => 100]);
3636
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
3737

38+
39+
echo throws(function() {
40+
MongoDB\Driver\ReadPreference::__set_state(['mode' => 'secondary', 'hedge' => 'foo']);
41+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
42+
43+
echo throws(function() {
44+
MongoDB\Driver\ReadPreference::__set_state(['mode' => 'primary', 'hedge' => []]);
45+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
46+
3847
?>
3948
===DONE===
4049
<?php exit(0); ?>
@@ -53,4 +62,8 @@ OK: Got MongoDB\Driver\Exception\InvalidArgumentException
5362
MongoDB\Driver\ReadPreference initialization requires "maxStalenessSeconds" integer field to be >= 90
5463
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
5564
MongoDB\Driver\ReadPreference initialization requires "maxStalenessSeconds" array field to not be present with "primary" mode
65+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
66+
MongoDB\Driver\ReadPreference initialization requires "hedge" field to be an array or object
67+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
68+
MongoDB\Driver\ReadPreference initialization requires "hedge" field to not be present with "primary" mode
5669
===DONE===

0 commit comments

Comments
 (0)