diff --git a/php_memcached.c b/php_memcached.c index 19673323..26cab2b8 100644 --- a/php_memcached.c +++ b/php_memcached.c @@ -135,7 +135,8 @@ typedef enum { MEMC_OP_ADD, MEMC_OP_REPLACE, MEMC_OP_APPEND, - MEMC_OP_PREPEND + MEMC_OP_PREPEND, + MEMC_OP_CAS } php_memc_write_op; typedef struct { @@ -400,7 +401,7 @@ static memcached_return s_server_cursor_version_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context); static - zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, zend_string *server_key, zend_string *key, zval *value, time_t expiration); + zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, zend_string *server_key, zend_string *key, zval *value, time_t expiration, uint64_t cas_token); static void php_memc_destroy(memcached_st *memc, php_memc_user_data_t *memc_user_data); @@ -750,44 +751,32 @@ zend_bool s_invoke_cache_callback(zval *zobject, zend_fcall_info *fci, zend_fcal php_memc_object_t *intern = Z_MEMC_OBJ_P(zobject); /* Prepare params */ - ZVAL_COPY(¶ms[0], zobject); + ZVAL_COPY(¶ms[0], zobject); /* memc */ ZVAL_STR_COPY(¶ms[1], key); /* key */ ZVAL_NEW_REF(¶ms[2], value); /* value */ + ZVAL_NEW_EMPTY_REF(¶ms[3]); /* expiration */ + ZVAL_NULL(Z_REFVAL(params[3])); - if (with_cas) { - fci->param_count = 3; - } else { - ZVAL_NEW_EMPTY_REF(¶ms[3]); /* expiration */ - ZVAL_NULL(Z_REFVAL(params[3])); - fci->param_count = 4; - } - + fci->param_count = 4; fci->retval = &retval; fci->params = params; - if (zend_call_function(fci, fcc) == SUCCESS) { - if (zend_is_true(&retval)) { - time_t expiration; - zval *val = Z_REFVAL(params[2]); - - if (with_cas) { - if (Z_TYPE_P(val) == IS_ARRAY) { - zval *rv = zend_hash_str_find(Z_ARRVAL_P(val), "value", sizeof("value") - 1); - if (rv) { - zval *cas = zend_hash_str_find(Z_ARRVAL_P(val), "cas", sizeof("cas") -1); - expiration = cas? Z_LVAL_P(cas) : 0; - status = s_memc_write_zval (intern, MEMC_OP_SET, NULL, key, rv, expiration); - } - /* memleak? zval_ptr_dtor(value); */ - ZVAL_COPY(value, val); - } - } else { - expiration = zval_get_long(Z_REFVAL(params[3])); - status = s_memc_write_zval (intern, MEMC_OP_SET, NULL, key, val, expiration); - /* memleak? zval_ptr_dtor(value); */ - ZVAL_COPY(value, val); - } + if (zend_call_function(fci, fcc) == SUCCESS && zend_is_true(&retval)) { + time_t expiration; + zval *val = Z_REFVAL(params[2]); + zval *rv = NULL; + zval *zv_cas = NULL; + uint64_t cas; + + if (Z_TYPE_P(val) == IS_ARRAY) { + rv = zend_hash_str_find(Z_ARRVAL_P(val), "value", sizeof("value") - 1); + zv_cas = zend_hash_str_find(Z_ARRVAL_P(val), "cas", sizeof("cas") - 1); } + + expiration = zval_get_long(Z_REFVAL(params[3])); + cas = zv_cas ? s_zval_to_uint64(zv_cas) : 0; + status = s_memc_write_zval (intern, MEMC_OP_SET, NULL, key, rv ? rv : val, expiration, cas); + ZVAL_COPY(value, val); } else { s_memc_set_status(intern, MEMCACHED_NOTFOUND, 0); @@ -1054,7 +1043,7 @@ zend_bool s_should_retry_write (php_memc_object_t *intern, memcached_return stat } static -zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, zend_string *server_key, zend_string *key, zval *value, time_t expiration) +zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, zend_string *server_key, zend_string *key, zval *value, time_t expiration, uint64_t cas) { uint32_t flags = 0; zend_string *payload = NULL; @@ -1099,6 +1088,10 @@ zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, ze case MEMC_OP_PREPEND: status = memc_write_using_fn_by_key(memcached_prepend_by_key); break; + + case MEMC_OP_CAS: + status = memcached_cas_by_key(intern->memc, ZSTR_VAL(server_key), ZSTR_LEN(server_key), ZSTR_VAL(key), ZSTR_LEN(key), ZSTR_VAL(payload), ZSTR_LEN(payload), expiration, flags, cas); + break; } if (status == MEMCACHED_END) { @@ -1131,6 +1124,10 @@ zend_bool s_memc_write_zval (php_memc_object_t *intern, php_memc_write_op op, ze case MEMC_OP_PREPEND: status = memc_write_using_fn(memcached_prepend); break; + + case MEMC_OP_CAS: + status = memcached_cas(intern->memc, ZSTR_VAL(key), ZSTR_LEN(key), ZSTR_VAL(payload), ZSTR_LEN(payload), expiration, flags, cas); + break; } if (status == MEMCACHED_END) { status = MEMCACHED_SUCCESS; @@ -1820,7 +1817,7 @@ static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke str_key = zend_string_init(tmp_key, tmp_len, 0); } - if (!s_memc_write_zval (intern, MEMC_OP_SET, server_key, str_key, value, expiration)) { + if (!s_memc_write_zval (intern, MEMC_OP_SET, server_key, str_key, value, expiration, 0)) { php_error_docref(NULL, E_WARNING, "failed to set key %s", ZSTR_VAL(str_key)); } @@ -1898,6 +1895,22 @@ PHP_METHOD(Memcached, replaceByKey) } /* }}} */ +/* {{{ Memcached::cas(double cas_token, string key, mixed value [, int expiration ]) + Sets the value for the given key, failing if the cas_token doesn't match the one in memcache */ +PHP_METHOD(Memcached, cas) +{ + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_CAS, 0); +} +/* }}} */ + +/* {{{ Memcached::casByKey(double cas_token, string server_key, string key, mixed value [, int expiration ]) + Sets the value for the given key on the server identified by the server_key, failing if the cas_token doesn't match the one in memcache */ +PHP_METHOD(Memcached, casByKey) +{ + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_CAS, 1); +} +/* }}} */ + /* {{{ -- php_memc_store_impl */ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool by_key) { @@ -1906,7 +1919,9 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool zend_string *s_value; zval s_zvalue; zval *value = NULL; + zval *zv_cas = NULL; zend_long expiration = 0; + uint64_t cas = 0; MEMC_METHOD_INIT_VARS; if (by_key) { @@ -1920,6 +1935,11 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &server_key, &key, &expiration) == FAILURE) { return; } + } else if (op == MEMC_OP_CAS) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zSSz|l", &zv_cas, &server_key, &key, &value, &expiration) == FAILURE) { + return; + } + cas = s_zval_to_uint64(zv_cas); } else { if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSz|l", &server_key, &key, &value, &expiration) == FAILURE) { return; @@ -1936,6 +1956,11 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &key, &expiration) == FAILURE) { return; } + } else if (op == MEMC_OP_CAS) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zSz|l", &zv_cas, &key, &value, &expiration) == FAILURE) { + return; + } + cas = s_zval_to_uint64(zv_cas); } else { if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &key, &value, &expiration) == FAILURE) { return; @@ -1959,82 +1984,13 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool } } - if (!s_memc_write_zval (intern, op, server_key, key, value, expiration)) { + if (!s_memc_write_zval (intern, op, server_key, key, value, expiration, cas)) { RETURN_FALSE; } RETURN_TRUE; } /* }}} */ -/* {{{ -- php_memc_cas_impl */ -static void php_memc_cas_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) -{ - zval *zv_cas; - uint64_t cas; - zend_string *key; - zend_string *server_key = NULL; - zval *value; - time_t expiration = 0; - zend_string *payload; - uint32_t flags = 0; - memcached_return status; - MEMC_METHOD_INIT_VARS; - - if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zSSz|ll", &zv_cas, &server_key, &key, - &value, &expiration) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "zSz|ll", &zv_cas, &key, &value, - &expiration) == FAILURE) { - return; - } - } - - MEMC_METHOD_FETCH_OBJECT; - s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - MEMC_CHECK_KEY(intern, key); - - cas = s_zval_to_uint64(zv_cas); - - payload = s_zval_to_payload(intern, value, &flags); - if (payload == NULL) { - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FALSE; - } - - if (by_key) { - status = memcached_cas_by_key(intern->memc, ZSTR_VAL(server_key), ZSTR_LEN(server_key), ZSTR_VAL(key), ZSTR_LEN(key), ZSTR_VAL(payload), ZSTR_LEN(payload), expiration, flags, cas); - } else { - status = memcached_cas(intern->memc, ZSTR_VAL(key), ZSTR_LEN(key), ZSTR_VAL(payload), ZSTR_LEN(payload), expiration, flags, cas); - } - - zend_string_release(payload); - if (s_memc_status_handle_result_code(intern, status) == FAILURE) { - RETURN_FALSE; - } - - RETURN_TRUE; -} -/* }}} */ - -/* {{{ Memcached::cas(double cas_token, string key, mixed value [, int expiration ]) - Sets the value for the given key, failing if the cas_token doesn't match the one in memcache */ -PHP_METHOD(Memcached, cas) -{ - php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); -} -/* }}} */ - -/* {{{ Memcached::casByKey(double cas_token, string server_key, string key, mixed value [, int expiration ]) - Sets the value for the given key on the server identified by the server_key, failing if the cas_token doesn't match the one in memcache */ -PHP_METHOD(Memcached, casByKey) -{ - php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); -} -/* }}} */ - /* {{{ Memcached::delete(string key [, int time ]) Deletes the given key */ PHP_METHOD(Memcached, delete) diff --git a/tests/cachecallback.phpt b/tests/cachecallback.phpt index 41edfc5c..55d4f647 100644 --- a/tests/cachecallback.phpt +++ b/tests/cachecallback.phpt @@ -27,6 +27,11 @@ $m->get ($first_key, function (Memcached $memc, $key, &$value, &$expiration) { var_dump ($m->get ($first_key)); +// Get the first key again, expecting that the callback is _not_ called a second timd +$m->get ($first_key, function (Memcached $memc, $key, &$value, &$expiration) { + throw new Exception ('This callback should not be called'); + }); + var_dump ( $m->get ($second_key, function (Memcached $memc, $key, &$value, &$expiration) { $value = "hello"; @@ -50,14 +55,89 @@ try { var_dump ($m->get ($third_key)); +echo "OK" . PHP_EOL; + +// +// Run through the scenarios again in GET_EXTENDED mode +// +$m->delete($first_key); +$m->delete($second_key); +$m->delete($third_key); + +var_dump ( +$m->get ($first_key, function (Memcached $memc, $key, &$value) { + $value = [ + "value" => "first_ext", + "cas" => 12345, + "expiration" => 10 ]; + return true; + }, Memcached::GET_EXTENDED) +); + +var_dump ($m->get ($first_key, null, Memcached::GET_EXTENDED)); + +// Get the first key again, expecting that the callback is _not_ called a second timd +$m->get ($first_key, function (Memcached $memc, $key, &$value) { + throw new Exception ('This callback should not be called'); + }, Memcached::GET_EXTENDED); + +var_dump ( +$m->get ($second_key, function (Memcached $memc, $key, &$value) { + $value = [ + "value" => "second_ext", + "cas" => 12345, + "expiration" => 10 ]; + return false; + }, Memcached::GET_EXTENDED) +); + +var_dump ($m->get ($second_key)); +var_dump ($m->get ($second_key, null, Memcached::GET_EXTENDED)); + +try { + $m->get ($third_key, function (Memcached $memc, $key, &$value) { + $value = [ + "value" => "third_ext", + "cas" => "12345", + "expiration" => "10" ]; + throw new Exception ('this is a test'); + return true; + }, Memcached::GET_EXTENDED); +} catch (Exception $e) { + echo 'Got exception' . PHP_EOL; +} + +var_dump ($m->get ($third_key)); echo "OK" . PHP_EOL; ---EXPECT-- +--EXPECTF-- string(5) "hello" string(5) "hello" bool(false) bool(false) Got exception bool(false) -OK \ No newline at end of file +OK +array(3) { + ["value"]=> + string(9) "first_ext" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) +} +array(3) { + ["value"]=> + string(9) "first_ext" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) +} +bool(false) +bool(false) +bool(false) +Got exception +bool(false) +OK