From a930b41fa138a55b976c55e21157e57b09355343 Mon Sep 17 00:00:00 2001 From: Mikko Date: Sat, 6 Feb 2016 05:07:52 +0000 Subject: [PATCH] Fixes memory errors, broken code, bugs etc --- package.xml | 1 + php_libmemcached_compat.c | 40 +- php_libmemcached_compat.h | 2 +- php_memcached.c | 4235 +++++++++++++++---------------- php_memcached_private.h | 29 +- tests/getdelayed.phpt | 37 +- tests/incrdecr_invalid_key.phpt | 1 + 7 files changed, 2128 insertions(+), 2217 deletions(-) diff --git a/package.xml b/package.xml index d2088a12..9d3be440 100644 --- a/package.xml +++ b/package.xml @@ -202,6 +202,7 @@ API * Fixes error where cache callback for get command was not setting expiration time properly * Added server type to server list * Remove use_sasl ini-variable and initialise sasl as needed + * CAS tokens are returned as integers and in case of too large values they overflow to strings Session handler * Session lock algorithm updated (new ini-values memcached.sess_lock_wait_min, memcached.sess_lock_wait_max and memcached.sess_lock_retries) diff --git a/php_libmemcached_compat.c b/php_libmemcached_compat.c index f49b162b..bd35d8fe 100644 --- a/php_libmemcached_compat.c +++ b/php_libmemcached_compat.c @@ -18,36 +18,20 @@ #include "php_memcached_private.h" #include "php_libmemcached_compat.h" -memcached_st *php_memc_create_str (const char *str, size_t str_len) +memcached_return php_memcached_exist (memcached_st *memc, zend_string *key) { -#ifdef HAVE_LIBMEMCACHED_MEMCACHED - return memcached (str, str_len); +#ifdef HAVE_MEMCACHED_EXIST + return memcached_exist (memc, key->val, key->len); #else - memcached_return rc; - memcached_st *memc; - memcached_server_st *servers; - - memc = memcached_create(NULL); - - if (!memc) { - return NULL; - } - - servers = memcached_servers_parse (str); - - if (!servers) { - memcached_free (memc); - return NULL; + memcached_return rc = MEMCACHED_SUCCESS; + uint32_t flags = 0; + size_t value_length = 0; + char *value = NULL; + + value = memcached_get (memc, key->val, key->len, &value_length, &flags, &rc); + if (value) { + free (value); } - - rc = memcached_server_push (memc, servers); - memcached_server_free (servers); - - if (rc != MEMCACHED_SUCCESS) { - memcached_free (memc); - return NULL; - } - return memc; + return rc; #endif } - diff --git a/php_libmemcached_compat.h b/php_libmemcached_compat.h index ae400d9c..e740d310 100644 --- a/php_libmemcached_compat.h +++ b/php_libmemcached_compat.h @@ -20,7 +20,7 @@ /* this is the version(s) we support */ #include -memcached_st *php_memc_create_str (const char *str, size_t str_len); +memcached_return php_memcached_exist (memcached_st *memc, zend_string *key); #if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x00052000 # define MEMCACHED_SERVER_TEMPORARILY_DISABLED (1024 << 2) diff --git a/php_memcached.c b/php_memcached.c index a8d9605b..c01b7f94 100644 --- a/php_memcached.c +++ b/php_memcached.c @@ -27,6 +27,9 @@ #include "php_memcached_server.h" #include "g_fmt.h" +#include +#include + #ifdef HAVE_MEMCACHED_SESSION # include "php_memcached_session.h" #endif @@ -55,8 +58,14 @@ #define MEMC_G(v) (php_memcached_globals.memc.v) #endif -#include + static + int le_memc; +static +int php_memc_list_entry(void) +{ + return le_memc; +} /**************************************** Custom options @@ -113,30 +122,34 @@ /**************************************** "get" operation flags ****************************************/ -#define MEMC_GET_PRESERVE_ORDER (1<<0) -#define MEMC_GET_EXTENDED (2<<0) +#define MEMC_GET_PRESERVE_ORDER 1 +#define MEMC_GET_EXTENDED 2 /**************************************** Helper macros ****************************************/ - - #define RETURN_FROM_GET RETURN_FALSE /**************************************** Structures and definitions ****************************************/ -enum memcached_compression_type { - COMPRESSION_TYPE_ZLIB = 1, - COMPRESSION_TYPE_FASTLZ = 2, -}; + +typedef enum { + MEMC_OP_SET, + MEMC_OP_TOUCH, + MEMC_OP_ADD, + MEMC_OP_REPLACE, + MEMC_OP_APPEND, + MEMC_OP_PREPEND +} php_memc_write_op; typedef struct { zend_bool is_persistent; - zend_bool compression; - enum memcached_serializer serializer; - enum memcached_compression_type compression_type; + zend_bool compression_enabled; + + zend_long serializer; + zend_long compression_type; zend_long store_retry_count; zend_long set_udf_flags; @@ -144,10 +157,8 @@ typedef struct { #if HAVE_MEMCACHED_SASL zend_bool has_sasl_data; #endif - } php_memc_user_data_t; - typedef struct { memcached_st *memc; zend_bool is_pristine; @@ -156,6 +167,22 @@ typedef struct { zend_object zo; } php_memc_object_t; +typedef struct { + size_t num_valid_keys; + + const char **mkeys; + size_t *mkeys_len; + + zend_string **strings; + +} php_memc_keys_t; + +typedef struct { + zval *object; + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} php_memc_result_callback_ctx_t; + static inline php_memc_object_t *php_memc_fetch_object(zend_object *obj) { return (php_memc_object_t *)((char *)obj - XtOffsetOf(php_memc_object_t, zo)); } @@ -184,15 +211,6 @@ typedef struct { #endif -enum { - MEMC_OP_SET, - MEMC_OP_TOUCH, - MEMC_OP_ADD, - MEMC_OP_REPLACE, - MEMC_OP_APPEND, - MEMC_OP_PREPEND -}; - static zend_class_entry *memcached_ce = NULL; static zend_class_entry *memcached_exception_ce = NULL; @@ -217,11 +235,11 @@ ZEND_GET_MODULE(memcached) static PHP_INI_MH(OnUpdateCompressionType) { if (!new_value) { - MEMC_G(compression_type_real) = COMPRESSION_TYPE_FASTLZ; + MEMC_G(compression_type) = COMPRESSION_TYPE_FASTLZ; } else if (!strcmp(new_value->val, "fastlz")) { - MEMC_G(compression_type_real) = COMPRESSION_TYPE_FASTLZ; + MEMC_G(compression_type) = COMPRESSION_TYPE_FASTLZ; } else if (!strcmp(new_value->val, "zlib")) { - MEMC_G(compression_type_real) = COMPRESSION_TYPE_ZLIB; + MEMC_G(compression_type) = COMPRESSION_TYPE_ZLIB; } else { return FAILURE; } @@ -231,22 +249,22 @@ static PHP_INI_MH(OnUpdateCompressionType) static PHP_INI_MH(OnUpdateSerializer) { if (!new_value) { - MEMC_G(serializer) = SERIALIZER_DEFAULT; + MEMC_G(serializer_type) = SERIALIZER_DEFAULT; } else if (!strcmp(new_value->val, "php")) { - MEMC_G(serializer) = SERIALIZER_PHP; + MEMC_G(serializer_type) = SERIALIZER_PHP; #ifdef HAVE_MEMCACHED_IGBINARY } else if (!strcmp(new_value->val, "igbinary")) { - MEMC_G(serializer) = SERIALIZER_IGBINARY; + MEMC_G(serializer_type) = SERIALIZER_IGBINARY; #endif // IGBINARY #ifdef HAVE_JSON_API } else if (!strcmp(new_value->val, "json")) { - MEMC_G(serializer) = SERIALIZER_JSON; + MEMC_G(serializer_type) = SERIALIZER_JSON; } else if (!strcmp(new_value->val, "json_array")) { - MEMC_G(serializer) = SERIALIZER_JSON_ARRAY; + MEMC_G(serializer_type) = SERIALIZER_JSON_ARRAY; #endif // JSON #ifdef HAVE_MEMCACHED_MSGPACK } else if (!strcmp(new_value->val, "msgpack")) { - MEMC_G(serializer) = SERIALIZER_MSGPACK; + MEMC_G(serializer_type) = SERIALIZER_MSGPACK; #endif // msgpack } else { return FAILURE; @@ -300,7 +318,6 @@ PHP_INI_BEGIN() MEMC_SESSION_INI_ENTRY("lock_wait_max", "2000", OnUpdateLongGEZero, lock_wait_max) MEMC_SESSION_INI_ENTRY("lock_retries", "5", OnUpdateLong, lock_retries) MEMC_SESSION_INI_ENTRY("lock_expire", "0", OnUpdateLongGEZero, lock_expiration) - MEMC_SESSION_INI_ENTRY("compression", "1", OnUpdateBool, compression_enabled) MEMC_SESSION_INI_ENTRY("binary_protocol", "1", OnUpdateBool, binary_protocol_enabled) MEMC_SESSION_INI_ENTRY("consistent_hash", "1", OnUpdateBool, consistent_hash_enabled) MEMC_SESSION_INI_ENTRY("number_of_replicas", "0", OnUpdateLongGEZero, number_of_replicas) @@ -319,7 +336,7 @@ PHP_INI_BEGIN() #endif - MEMC_INI_ENTRY("compression_type", "fastlz", OnUpdateCompressionType, compression_type) + MEMC_INI_ENTRY("compression_type", "fastlz", OnUpdateCompressionType, compression_name) MEMC_INI_ENTRY("compression_factor", "1.3", OnUpdateReal, compression_factor) MEMC_INI_ENTRY("compression_threshold", "2000", OnUpdateLong, compression_threshold) MEMC_INI_ENTRY("serializer", SERIALIZER_DEFAULT_NAME, OnUpdateSerializer, serializer_name) @@ -333,8 +350,6 @@ PHP_INI_END() /**************************************** Forward declarations ****************************************/ -static int php_memc_handle_error(php_memc_object_t *intern, memcached_return status); - static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key); static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key); static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool by_key); @@ -345,10 +360,19 @@ static void php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ /* Invoke PHP functions */ static - memcached_return s_invoke_cache_callback(zval *zobject, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string *key, zval *value); + zend_bool s_invoke_cache_callback(zval *zobject, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string *key, zval *value); + +/* Iterate result sets */ +typedef zend_bool (*php_memc_result_apply_fn)(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *context); + +static +memcached_return php_memc_result_apply(php_memc_object_t *intern, php_memc_result_apply_fn result_apply_fn, void *context); static - int s_invoke_result_callback(zval *zmemc_obj, zend_fcall_info *fci, zend_fcall_info_cache *fcc, memcached_result_st *result); +zend_bool php_memc_mget_apply(php_memc_object_t *intern, zend_string *server_key, + php_memc_keys_t *keys, php_memc_result_apply_fn result_apply_fn, + zend_bool with_cas, void *context); + /* Callback functions for different server list iterations */ static @@ -357,8 +381,8 @@ static static memcached_return s_server_cursor_version_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context); -/* Callback for dumping all keys to zval */ - +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); static void php_memc_destroy(memcached_st *memc, php_memc_user_data_t *memc_user_data); @@ -367,11 +391,17 @@ static zend_bool s_memcached_result_to_zval(memcached_st *memc, memcached_result_st *result, zval *return_value); static - zend_string *s_zval_to_payload(zval *value, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type); + zend_string *s_zval_to_payload(php_memc_object_t *intern, zval *value, uint32_t *flags); + +static + void s_hash_to_keys(php_memc_keys_t *keys_out, HashTable *hash_in, zend_bool preserve_order, zval *return_value); + +static + void s_clear_keys(php_memc_keys_t *keys); /**************************************** - Method implementations + Exported helper functions ****************************************/ zend_bool php_memc_init_sasl_if_needed() @@ -391,27 +421,6 @@ zend_bool php_memc_init_sasl_if_needed() #endif } - - -memcached_return php_memcached_exist (memcached_st *memc, zend_string *key) -{ -#ifdef HAVE_MEMCACHED_EXIST - return memcached_exist (memc, key->val, key->len); -#else - memcached_return rc = MEMCACHED_SUCCESS; - uint32_t flags = 0; - size_t value_length = 0; - char *value = NULL; - - value = memcached_get (memc, key->val, key->len, &value_length, &flags, &rc); - if (value) { - free (value); - } - return rc; -#endif -} - - char *php_memc_printable_func (zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) { char *buffer = NULL; @@ -429,1646 +438,1558 @@ char *php_memc_printable_func (zend_fcall_info *fci, zend_fcall_info_cache *fci_ return buffer; } + +/**************************************** + Handling error codes +****************************************/ + static -zend_bool s_invoke_new_instance_cb(zval *object, zend_fcall_info *fci, zend_fcall_info_cache *fci_cache, zend_string *persistent_id) +zend_bool s_memcached_return_is_error(memcached_return status, zend_bool strict) { - zend_bool ret = 1; - zval retval, id; - - if (persistent_id) { - ZVAL_STR(&id, persistent_id); - } else { - ZVAL_NULL(&id); - } + zend_bool result = 0; - ZVAL_UNDEF(&retval); + switch (status) { + case MEMCACHED_SUCCESS: + case MEMCACHED_STORED: + case MEMCACHED_DELETED: + case MEMCACHED_STAT: + case MEMCACHED_END: + case MEMCACHED_BUFFERED: + result = 0; + break; - zend_fcall_info_argn(fci, 2, object, &id); - fci->retval = &retval; - fci->no_separation = 1; + case MEMCACHED_SOME_ERRORS: + result = strict; + break; - if (zend_call_function(fci, fci_cache) == FAILURE) { - char *buf = php_memc_printable_func (fci, fci_cache); - php_error_docref(NULL, E_WARNING, "Failed to invoke 'on_new' callback %s()", buf); - efree (buf); - ret = 0; + default: + result = 1; + break; } + return result; +} - if (Z_TYPE(retval) != IS_UNDEF) { - zval_ptr_dtor(&retval); - } +static +int s_memc_status_handle_result_code(php_memc_object_t *intern, memcached_return status) +{ + intern->rescode = status; + intern->memc_errno = 0; - if (EG(exception)) { - ret = 0; + if (s_memcached_return_is_error(status, 1)) { + intern->memc_errno = memcached_last_error_errno(intern->memc); + return FAILURE; } - - zend_fcall_info_args_clear(fci, 1); - return ret; + return SUCCESS; } static - int le_memc; +void s_memc_set_status(php_memc_object_t *intern, memcached_return status, int memc_errno) +{ + intern->rescode = status; + intern->memc_errno = memc_errno; +} static -int php_memc_list_entry(void) +zend_bool s_memc_status_has_error(php_memc_object_t *intern) { - return le_memc; + return s_memcached_return_is_error(intern->rescode, 1); } -/* {{{ Memcached::__construct([string persistent_id[, callback on_new[, string connection_str]]])) - Creates a Memcached object, optionally using persistent memcache connection */ -static PHP_METHOD(Memcached, __construct) +static +zend_bool s_memc_status_has_result_code(php_memc_object_t *intern, memcached_return status) { - php_memc_object_t *intern; - php_memc_user_data_t *memc_user_data; - - zend_string *persistent_id = NULL; - zend_string *conn_str = NULL; - zend_string *plist_key = NULL; - zend_fcall_info fci = {0}; - zend_fcall_info_cache fci_cache; + return intern->rescode == status; +} - zend_bool is_persistent = 0; +/**************************************** + Marshall cas tokens +****************************************/ - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!f!S", &persistent_id, &fci, &fci_cache, &conn_str) == FAILURE) { - return; +static +void s_uint64_to_zval (zval *target, uint64_t value) +{ + if (value >= ((uint64_t) LONG_MAX)) { + char *buffer; +#ifdef PRIu64 + spprintf (&buffer, 0, "%" PRIu64, value); +#else + /* Best effort */ + spprintf (&buffer, 0, "%llu", value); +#endif + ZVAL_STRING (target, buffer); + efree(buffer); + } + else { + ZVAL_LONG (target, (zend_long) value); } +} - intern = Z_MEMC_OBJ_P(getThis()); - intern->is_pristine = 1; +static +uint64_t s_zval_to_uint64 (zval *cas) +{ + switch (Z_TYPE_P (cas)) { + case IS_LONG: + return (uint64_t) zval_get_long (cas); + break; - if (persistent_id && persistent_id->len) { - zend_resource *le; + case IS_DOUBLE: + if (Z_DVAL_P (cas) < 0.0) + return 0; - plist_key = zend_string_alloc(sizeof("memcached:id=") + persistent_id->len - 1, 0); - snprintf(plist_key->val, plist_key->len + 1, "memcached:id=%s", persistent_id->val); + return (uint64_t) zval_get_double (cas); + break; - if ((le = zend_hash_find_ptr(&EG(persistent_list), plist_key)) != NULL) { - if (le->type == php_memc_list_entry()) { - intern->memc = le->ptr; - intern->is_pristine = 0; - zend_string_release (plist_key); - return; + case IS_STRING: + { + uint64_t val; + char *end; + + errno = 0; + val = (uint64_t) strtoull (Z_STRVAL_P (cas), &end, 0); + + if (*end || (errno == ERANGE && val == UINT64_MAX) || (errno != 0 && val == 0)) { + php_error_docref(NULL, E_ERROR, "Failed to unmarshall cas token"); + return 0; } + return val; } - is_persistent = 1; + break; } + return 0; +} - if (conn_str && conn_str->len > 0) { - intern->memc = memcached (conn_str->val, conn_str->len); - } - else { - intern->memc = memcached (NULL, 0); - } - if (!intern->memc) { - // TODO: handle allocation fail - } +/**************************************** + Iterate over memcached results and mget +****************************************/ - memc_user_data = pecalloc (1, sizeof(*memc_user_data), is_persistent); - memc_user_data->serializer = MEMC_G(serializer); - memc_user_data->compression_type = MEMC_G(compression_type_real); - memc_user_data->compression = 1; - memc_user_data->store_retry_count = MEMC_G(store_retry_count); - memc_user_data->set_udf_flags = -1; - memc_user_data->is_persistent = is_persistent; +static +memcached_return php_memc_result_apply(php_memc_object_t *intern, php_memc_result_apply_fn result_apply_fn, void *context) +{ + memcached_result_st result, *result_ptr; + memcached_return rc, status = MEMCACHED_SUCCESS; - memcached_set_user_data(intern->memc, memc_user_data); + memcached_result_create(intern->memc, &result); - if (fci.size) { - if (!s_invoke_new_instance_cb(getThis(), &fci, &fci_cache, persistent_id) || EG(exception)) { - /* error calling or exception thrown from callback */ - if (plist_key) { - zend_string_release(plist_key); - } - /* - Setting intern->memc null prevents object destruction from freeing the memcached_st - We free it manually here because it might be persistent and has not been - registered to persistent_list yet - */ - php_memc_destroy(intern->memc, memc_user_data); - intern->memc = NULL; - return; + do { + result_ptr = memcached_fetch_result(intern->memc, &result, &rc); + + if (s_memcached_return_is_error(rc, 0)) { + status = rc; } - } - if (plist_key) { - zend_resource le; + /* nothing returned */ + if (!result_ptr) { + break; + } + else { + zend_string *key; + zval zv_value, zv_cas; + zend_bool retval; - le.type = php_memc_list_entry(); - le.ptr = intern->memc; + uint64_t cas; + uint32_t flags; - GC_REFCOUNT(&le) = 1; + const char *res_key; + size_t res_key_len; + - /* plist_key is not a persistent allocated key, thus we use str_update here */ - if (zend_hash_str_update_mem(&EG(persistent_list), plist_key->val, plist_key->len, &le, sizeof(le)) == NULL) { - zend_string_release(plist_key); - php_error_docref(NULL, E_ERROR, "could not register persistent entry"); - /* not reached */ - } - zend_string_release(plist_key); - } + ZVAL_UNDEF(&zv_value); + if (!s_memcached_result_to_zval(intern->memc, &result, &zv_value)) { + if (Z_TYPE(zv_value) != IS_UNDEF) { + zval_ptr_dtor(&zv_value); + } + if (EG(exception)) { + status = MEMC_RES_PAYLOAD_FAILURE; + memcached_quit(intern->memc); + break; + } + status = MEMCACHED_SOME_ERRORS; + continue; + } + res_key = memcached_result_key_value(&result); + res_key_len = memcached_result_key_length(&result); + cas = memcached_result_cas(&result); + flags = memcached_result_flags(&result); -#if 0 - m_obj = pecalloc(1, sizeof(*m_obj), intern->is_persistent); - if (m_obj == NULL) { - if (plist_key) { - zend_string_release(plist_key); - } - php_error_docref(NULL, E_ERROR, "out of memory: cannot allocate handle"); - /* not reached */ - } + s_uint64_to_zval(&zv_cas, cas); - if (conn_str) { - intern->memc = php_memc_create_str(conn_str->val, conn_str->len); - if (!intern->memc) { - char error_buffer[1024]; - if (plist_key) { - zend_string_release(plist_key); - } -#ifdef HAVE_LIBMEMCACHED_CHECK_CONFIGURATION - if (libmemcached_check_configuration(conn_str->val, conn_str->len, error_buffer, sizeof(error_buffer)) != MEMCACHED_SUCCESS) { - php_error_docref(NULL, E_ERROR, "configuration error %s", error_buffer); - } else { - php_error_docref(NULL, E_ERROR, "could not allocate libmemcached structure"); - } -#else - php_error_docref(NULL, E_ERROR, "could not allocate libmemcached structure"); -#endif - /* not reached */ - } - } else { - intern->memc = memcached_create(NULL); - if (intern->memc == NULL) { - if (plist_key) { - zend_string_release(plist_key); + key = zend_string_init (res_key, res_key_len, 0); + retval = result_apply_fn(intern, key, &zv_value, &zv_cas, flags, context); + + zend_string_release (key); + zval_ptr_dtor(&zv_value); + zval_ptr_dtor(&zv_cas); + + /* Stop iterating on false */ + if (!retval) { + /* Make sure we clear our results */ + while (memcached_fetch_result(intern->memc, &result, &rc)) {} + break; } - php_error_docref(NULL, E_ERROR, "could not allocate libmemcached structure"); - /* not reached */ } - } + } while (result_ptr != NULL); + memcached_result_free(&result); + return status; +} +static +zend_bool php_memc_mget_apply(php_memc_object_t *intern, zend_string *server_key, php_memc_keys_t *keys, + php_memc_result_apply_fn result_apply_fn, zend_bool with_cas, void *context) +{ + memcached_return status; + memcached_result_st result; + int mget_status; + uint64_t orig_cas_flag = 0; - intern->obj = m_obj; - intern->is_pristine = 1; + // Reset status code + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - if (fci.size) { /* will be 0 when not available */ - if (!php_memcached_on_new_callback(object, &fci, &fci_cache, persistent_id) || EG(exception)) { - /* error calling or exception thrown from callback */ - if (plist_key) { - zend_string_release(plist_key); - } + if (!keys->num_valid_keys) { + intern->rescode = MEMCACHED_BAD_KEY_PROVIDED; + return 0; + } - intern->obj = NULL; - if (intern->is_persistent) { - php_memc_destroy(m_obj, intern->is_persistent); - } + if (with_cas) { + orig_cas_flag = memcached_behavior_get (intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); - return; + if (!orig_cas_flag) { + memcached_behavior_set (intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); } } - if (intern->is_persistent) { - zend_resource le; + if (server_key) { + status = memcached_mget_by_key(intern->memc, server_key->val, server_key->len, keys->mkeys, keys->mkeys_len, keys->num_valid_keys); + } else { + status = memcached_mget(intern->memc, keys->mkeys, keys->mkeys_len, keys->num_valid_keys); + } - le.type = php_memc_list_entry(); - le.ptr = m_obj; - GC_REFCOUNT(&le) = 1; - /* plist_key is not a persistent allocated key, thus we use str_update here */ - if (zend_hash_str_update_mem(&EG(persistent_list), plist_key->val, plist_key->len, &le, sizeof(le)) == NULL) { - if (plist_key) { - zend_string_release(plist_key); - } - php_error_docref(NULL, E_ERROR, "could not register persistent entry"); - /* not reached */ - } + /* Need to handle result code before restoring cas flags, would mess up errno */ + mget_status = s_memc_status_handle_result_code(intern, status); + + if (with_cas && !orig_cas_flag) { + memcached_behavior_set (intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag); } - if (plist_key) { - zend_string_release(plist_key); + /* Return on failure codes */ + if (mget_status == FAILURE) { + return 0; } -#endif -} -/* }}} */ -static -void s_uint64_to_zval (zval *target, uint64_t value) -{ - if (value >= LONG_MAX) { - char *buffer; -#ifdef PRIu64 - spprintf (&buffer, 0, "%" PRIu64, value); -#else - /* Best effort */ - spprintf (&buffer, 0, "%llu", value); -#endif - ZVAL_STRING (target, buffer); - efree(buffer); + if (!result_apply_fn) { + /* no callback, for example getDelayed */ + return 1; } - else { - ZVAL_LONG (target, (zend_long) value); + + status = php_memc_result_apply(intern, result_apply_fn, context); + + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + return 0; } + + return 1; } +/**************************************** + Invoke callbacks +****************************************/ + static -uint64_t s_zval_to_uint64 (zval *cas) +zend_bool s_invoke_new_instance_cb(zval *object, zend_fcall_info *fci, zend_fcall_info_cache *fci_cache, zend_string *persistent_id) { - switch (Z_TYPE_P (cas)) { - case IS_LONG: - return (uint64_t) zval_get_long (cas); - break; + zend_bool ret = 1; + zval retval, id; - case IS_DOUBLE: - if (Z_DVAL_P (cas) < 0.0) - return 0; + if (persistent_id) { + ZVAL_STR(&id, persistent_id); + } else { + ZVAL_NULL(&id); + } - return (uint64_t) zval_get_double (cas); - break; + ZVAL_UNDEF(&retval); - case IS_STRING: - { - uint64_t val; - char *end; + zend_fcall_info_argn(fci, 2, object, &id); + fci->retval = &retval; + fci->no_separation = 1; - errno = 0; - val = (uint64_t) strtoull (Z_STRVAL_P (cas), &end, 0); + if (zend_call_function(fci, fci_cache) == FAILURE) { + char *buf = php_memc_printable_func (fci, fci_cache); + php_error_docref(NULL, E_WARNING, "Failed to invoke 'on_new' callback %s()", buf); + efree (buf); + ret = 0; + } - if (*end || (errno == ERANGE && val == UINT64_MAX) || (errno != 0 && val == 0)) { - php_error_docref(NULL, E_ERROR, "Failed to unmarshall cas token"); - return 0; - } - return val; - } - break; + if (Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&retval); } - return 0; -} + if (EG(exception)) { + ret = 0; + } -typedef struct { + zend_fcall_info_args_clear(fci, 1); + return ret; +} - size_t num_valid_keys; +static +zend_bool s_invoke_cache_callback(zval *zobject, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string *key, zval *value) +{ + memcached_return rc; + zend_bool status = 0; - const char **mkeys; - size_t *mkeys_len; + zval params[4]; + zval retval; + zval zv_key, ref_val; + zval ref_expiration, zv_expiration; + php_memc_object_t *intern = Z_MEMC_OBJ_P(zobject); - zend_string **strings; + /* Prepare params */ + ZVAL_STR(&zv_key, key); -} php_memcached_keys; + ZVAL_NULL(&ref_val); + ZVAL_NULL(&zv_expiration); -static -void s_hash_to_keys(php_memcached_keys *keys_out, HashTable *hash_in, zend_bool preserve_order, zval *return_value) -{ - size_t idx = 0, alloc_count; - zval *zv; + ZVAL_NEW_REF(&ref_val, value); + ZVAL_NEW_REF(&ref_expiration, &zv_expiration); - keys_out->num_valid_keys = 0; + ZVAL_COPY(¶ms[0], zobject); + ZVAL_COPY(¶ms[1], &zv_key); + ZVAL_COPY_VALUE(¶ms[2], &ref_val); + ZVAL_COPY_VALUE(¶ms[3], &ref_expiration); - alloc_count = zend_hash_num_elements(hash_in); - if (!alloc_count) { - return; - } - keys_out->mkeys = ecalloc (alloc_count, sizeof (char *)); - keys_out->mkeys_len = ecalloc (alloc_count, sizeof (size_t)); - keys_out->strings = ecalloc (alloc_count, sizeof (zend_string *)); + fci->retval = &retval; + fci->params = params; + fci->param_count = 4; - ZEND_HASH_FOREACH_VAL(hash_in, zv) { - zend_string *key = zval_get_string(zv); + ZVAL_UNDEF(&retval); - if (preserve_order && return_value) { - add_assoc_null_ex(return_value, key->val, key->len); + if (zend_call_function(fci, fcc) == SUCCESS) { + if (zend_is_true(&retval)) { + time_t expiration = zval_get_long(Z_REFVAL(ref_expiration)); + status = s_memc_write_zval (intern, MEMC_OP_SET, NULL, key, Z_REFVAL(ref_val), expiration); + ZVAL_DUP(value, Z_REFVAL(ref_val)); } + } + else { + s_memc_set_status(intern, MEMCACHED_NOTFOUND, 0); + } - if (key->len > 0 && key->len < MEMCACHED_MAX_KEY) { - keys_out->mkeys[idx] = key->val; - keys_out->mkeys_len[idx] = key->len; - keys_out->strings[idx] = key; - idx++; - } - else { - zend_string_release (key); - } - } ZEND_HASH_FOREACH_END(); + zval_ptr_dtor(zobject); + zval_ptr_dtor(&zv_key); + zval_ptr_dtor(&ref_val); + zval_ptr_dtor(&ref_expiration); - if (!idx) { - efree (keys_out->mkeys); - efree (keys_out->mkeys_len); - efree (keys_out->strings); + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); } - keys_out->num_valid_keys = idx; + return status; } -static -void s_clear_keys(php_memcached_keys *keys) -{ - size_t i; - for (i = 0; i < keys->num_valid_keys; i++) { - zend_string_release (keys->strings[i]); - } - efree(keys->strings); - efree(keys->mkeys); - efree(keys->mkeys_len); -} +/**************************************** + Wrapper for setting from zval +****************************************/ static -memcached_return s_memcached_get_multi(memcached_st *memc, HashTable *hash_keys, zend_string *server_key, zend_long get_flags, zval *return_value) +zend_bool s_compress_value (php_memc_compression_type compression_type, zend_string **payload_in, uint32_t *flags) { - php_memcached_keys keys = { 0 }; - zval values; - memcached_return rc, status = MEMCACHED_SUCCESS; - uint64_t orig_cas_flag; - memcached_result_st result; + /* status */ + zend_bool compress_status = 0; + zend_string *payload = *payload_in; - zend_bool extended = (get_flags & MEMC_GET_EXTENDED); - zend_bool preserve_order = (get_flags & MEMC_GET_PRESERVE_ORDER); + /* Additional 5% for the data */ + size_t buffer_size = (size_t) (((double) payload->len * 1.05) + 1.0); + char *buffer = emalloc(buffer_size); - array_init(&values); - s_hash_to_keys(&keys, hash_keys, preserve_order, &values); + /* Store compressed size here */ + size_t compressed_size = 0; + uint32_t original_size = payload->len; - if (!keys.num_valid_keys) { - zval_ptr_dtor(&values); - return MEMCACHED_NO_KEY_PROVIDED; - } + switch (compression_type) { - orig_cas_flag = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); + case COMPRESSION_TYPE_FASTLZ: + { + compressed_size = fastlz_compress(payload->val, payload->len, buffer); - if (extended && !orig_cas_flag) { - memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); - } + if (compressed_size > 0) { + compress_status = 1; + MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_FASTLZ); + } + } + break; - if (server_key) { - status = memcached_mget_by_key(memc, server_key->val, server_key->len, keys.mkeys, keys.mkeys_len, keys.num_valid_keys); - } else { - status = memcached_mget(memc, keys.mkeys, keys.mkeys_len, keys.num_valid_keys); - } + case COMPRESSION_TYPE_ZLIB: + { + compressed_size = buffer_size; + int status = compress((Bytef *) buffer, &compressed_size, (Bytef *) payload->val, payload->len); - s_clear_keys(&keys); + if (status == Z_OK) { + compress_status = 1; + MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_ZLIB); + } + } + break; - if (extended && !orig_cas_flag) { - memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 0); + default: + compress_status = 0; + break; } - memcached_result_create(memc, &result); - while ((memcached_fetch_result(memc, &result, &rc)) != NULL) { - - const char *res_key = NULL; - size_t res_key_len = 0; - zval value; - - /* For some reason instead of success it's end */ - if (rc == MEMCACHED_END) { - rc = MEMCACHED_SUCCESS; - } - - if (rc != MEMCACHED_SUCCESS) { - status = rc; - continue; - } + if (!compress_status) { + php_error_docref(NULL, E_WARNING, "could not compress value"); + MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); + efree (buffer); + return 0; + } - ZVAL_UNDEF(&value); - if (!s_memcached_result_to_zval(memc, &result, &value)) { - if (EG(exception)) { - status = MEMC_RES_PAYLOAD_FAILURE; + /* This means the value was too small to be compressed, still a success */ + if (compressed_size > (payload->len * MEMC_G(compression_factor))) { + MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); + efree (buffer); + return 1; + } - memcached_result_free(&result); - memcached_quit(memc); - zval_ptr_dtor (&values); - break; - } - status = MEMCACHED_SOME_ERRORS; - continue; - } + payload = zend_string_realloc(payload, compressed_size + sizeof(uint32_t), 0); - res_key = memcached_result_key_value(&result); - res_key_len = memcached_result_key_length(&result); + /* Copy the uin32_t at the beginning */ + memcpy(payload->val, &original_size, sizeof(uint32_t)); + memcpy(payload->val + sizeof (uint32_t), buffer, compressed_size); + efree(buffer); - if (extended) { - uint32_t flags; - uint64_t cas; - zval cas_token, node; + zend_string_forget_hash_val(payload); + *payload_in = payload; + return 1; +} - cas = memcached_result_cas(&result); - flags = memcached_result_flags(&result); +static +zend_bool s_serialize_value (php_memc_serializer_type serializer, zval *value, smart_str *buf, uint32_t *flags) +{ + switch (serializer) { - s_uint64_to_zval (&cas_token, cas); + /* + Igbinary serialization + */ +#ifdef HAVE_MEMCACHED_IGBINARY + case SERIALIZER_IGBINARY: + { + uint8_t *buffer; + size_t buffer_len; - array_init (&node); - add_assoc_zval (&node, "value", &value); - add_assoc_zval (&node, "cas", &cas_token); - add_assoc_long (&node, "flags", (zend_long) MEMC_VAL_GET_USER_FLAGS(flags)); + if (igbinary_serialize(&buffer, &buffer_len, value) != 0) { + php_error_docref(NULL, E_WARNING, "could not serialize value with igbinary"); + return 0; + } + smart_str_appendl (buf, buffer, buffer_len); + efree(buffer); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_IGBINARY); + } + break; +#endif - add_assoc_zval_ex(&values, res_key, res_key_len, &node); + /* + JSON serialization + */ +#ifdef HAVE_JSON_API + case SERIALIZER_JSON: + case SERIALIZER_JSON_ARRAY: + { + php_json_encode(buf, value, 0); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_JSON); } - else { - add_assoc_zval_ex(&values, res_key, res_key_len, &value); + break; +#endif + + /* + msgpack serialization + */ +#ifdef HAVE_MEMCACHED_MSGPACK + case SERIALIZER_MSGPACK: + php_msgpack_serialize(buf, value); + if (!buf->s) { + php_error_docref(NULL, E_WARNING, "could not serialize value with msgpack"); + return 0; + } + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_MSGPACK); + break; +#endif + + /* + PHP serialization + */ + default: + { + php_serialize_data_t var_hash; + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(buf, value, &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + + if (!buf->s) { + php_error_docref(NULL, E_WARNING, "could not serialize value"); + return 0; + } + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_SERIALIZED); } + break; } - memcached_result_free(&result); - if (Z_TYPE(values) != IS_UNDEF) { - ZVAL_ZVAL(return_value, &values, 0, 0); + /* Check for exceptions caused by serializers */ + if (EG(exception) && buf->s->len) { + return 0; } - return status; + return 1; } - static -void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) +zend_string *s_zval_to_payload(php_memc_object_t *intern, zval *value, uint32_t *flags) { - zend_long get_flags = 0; - zend_string *key; - zend_string *server_key = NULL; - memcached_return status = MEMCACHED_SUCCESS; - zend_fcall_info fci = empty_fcall_info; - zend_fcall_info_cache fcc = empty_fcall_info_cache; - HashTable keys; - zval values, tmp; - MEMC_METHOD_INIT_VARS; + zend_string *payload; + php_memc_user_data_t *memc_user_data = memcached_get_user_data(intern->memc); + zend_bool should_compress = memc_user_data->compression_enabled; - if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|f!l", &server_key, &key, &fci, &fcc, &get_flags) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|f!l", &key, &fci, &fcc, &get_flags) == FAILURE) { - return; + switch (Z_TYPE_P(value)) { + + case IS_STRING: + payload = zval_get_string(value); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_STRING); + break; + + case IS_LONG: + { + smart_str buffer = {0}; + smart_str_append_long (&buffer, Z_LVAL_P(value)); + smart_str_0(&buffer); + payload = buffer.s; + + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_LONG); + should_compress = 0; } - } + break; - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + case IS_DOUBLE: + { + char buffer[40]; + php_memcached_g_fmt(buffer, Z_DVAL_P(value)); + payload = zend_string_init (buffer, strlen (buffer), 0); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_DOUBLE); + should_compress = 0; + } + break; - zend_hash_init (&keys, 1, 0, NULL, 0); - ZVAL_STR(&tmp, key); - zend_hash_add(&keys, key, &tmp); + case IS_TRUE: + payload = zend_string_init ("1", 1, 0); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); + should_compress = 0; + break; - ZVAL_UNDEF(&values); - status = s_memcached_get_multi(intern->memc, &keys, server_key, get_flags, &values); - zend_hash_destroy (&keys); + case IS_FALSE: + payload = zend_string_alloc (0, 0); + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); + should_compress = 0; + break; - if (EG(exception)) { - zval_ptr_dtor(&values); - RETURN_FROM_GET; - } + default: + { + smart_str buffer = {0}; - if (status == MEMCACHED_SUCCESS) { - zval *zv = zend_hash_find (Z_ARRVAL(values), key); - if (zv) { - RETVAL_ZVAL(zv, 1, 0); - zval_ptr_dtor(&values); - return; - } - else { - status = MEMCACHED_NOTFOUND; + if (!s_serialize_value (memc_user_data->serializer, value, &buffer, flags)) { + smart_str_free(&buffer); + return NULL; + } + payload = buffer.s; } + break; } + zend_string_forget_hash_val(payload); - if (Z_TYPE(values) != IS_UNDEF) { - zval_ptr_dtor(&values); + /* turn off compression for values below the threshold */ + if (payload->len == 0 || payload->len < MEMC_G(compression_threshold)) { + should_compress = 0; } - if (status == MEMCACHED_NOTFOUND && fci.size > 0) { - // try to invoke cache cb - status = s_invoke_cache_callback(object, &fci, &fcc, key, return_value); + /* If we have compression flag, compress the value */ + if (should_compress) { + /* status */ + if (!s_compress_value (memc_user_data->compression_type, &payload, flags)) { + zend_string_release(payload); + return NULL; + } + MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSED); } - if (php_memc_handle_error(intern, status) < 0) { - RETURN_FROM_GET; + if (memc_user_data->set_udf_flags >= 0) { + MEMC_VAL_SET_USER_FLAGS(*flags, ((uint32_t) memc_user_data->set_udf_flags)); } -} -/* {{{ Memcached::get(string key [, mixed callback [, int get_flags = 0]) - Returns a value for the given key or false */ -PHP_METHOD(Memcached, get) -{ - php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + return payload; } -/* }}} */ -/* {{{ Memcached::getByKey(string server_key, string key [, mixed callback [, int get_flags = 0]) - Returns a value for key from the server identified by the server key or false */ -PHP_METHOD(Memcached, getByKey) +static +zend_bool s_should_retry_write (php_memc_object_t *intern, memcached_return status) { - php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + if (memcached_server_count (intern->memc) == 0) { + return 0; + } + + return s_memcached_return_is_error (status, 1); } -/* }}} */ -/* {{{ -- php_memc_getMulti_impl */ -static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) +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) { - zval *keys = NULL; - zend_string *server_key = NULL; - memcached_return rc; - zend_long flags = 0; - MEMC_METHOD_INIT_VARS; + uint32_t flags = 0; + zend_string *payload = NULL; + memcached_return status; + php_memc_user_data_t *memc_user_data = memcached_get_user_data(intern->memc); + zend_long retries = memc_user_data->store_retry_count; - if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|l", &server_key, - &keys, &flags) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &keys, &flags) == FAILURE) { - return; + if (value) { + payload = s_zval_to_payload(intern, value, &flags); + + if (!payload) { + s_memc_set_status(intern, MEMC_RES_PAYLOAD_FAILURE, 0); + return 0; } } - MEMC_METHOD_FETCH_OBJECT; +#define memc_write_using_fn(fn_name) payload ? fn_name(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags) : MEMC_RES_PAYLOAD_FAILURE; +#define memc_write_using_fn_by_key(fn_name) payload ? fn_name(intern->memc, server_key->val, server_key->len, key->val, key->len, payload->val, payload->len, expiration, flags) : MEMC_RES_PAYLOAD_FAILURE; - rc = s_memcached_get_multi(intern->memc, Z_ARRVAL_P(keys), server_key, flags, return_value); - php_memc_handle_error(intern, rc); + if (server_key) { + switch (op) { + case MEMC_OP_SET: + status = memc_write_using_fn_by_key(memcached_set_by_key); + break; - if (EG(exception)) { - zval_dtor(return_value); - RETURN_FALSE; - } -} -/* }}} */ + case MEMC_OP_TOUCH: + status = memcached_touch_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, expiration); + break; + + case MEMC_OP_ADD: + status = memc_write_using_fn_by_key(memcached_add_by_key); + break; + + case MEMC_OP_REPLACE: + status = memc_write_using_fn_by_key(memcached_replace_by_key); + break; + + case MEMC_OP_APPEND: + status = memc_write_using_fn_by_key(memcached_append_by_key); + break; -/* {{{ Memcached::getMulti(array keys[, long flags = 0 ]) - Returns values for the given keys or false */ -PHP_METHOD(Memcached, getMulti) -{ - php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); -} -/* }}} */ + case MEMC_OP_PREPEND: + status = memc_write_using_fn_by_key(memcached_prepend_by_key); + break; + } -/* {{{ Memcached::getMultiByKey(string server_key, array keys[, long flags = 0 ]) - Returns values for the given keys from the server identified by the server key or false */ -PHP_METHOD(Memcached, getMultiByKey) -{ - php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); -} -/* }}} */ + if (status == MEMCACHED_END) { + status = MEMCACHED_SUCCESS; + } + } + else { +retry: + switch (op) { + case MEMC_OP_SET: + status = memc_write_using_fn(memcached_set); + break; -/* {{{ Memcached::getDelayed(array keys [, bool with_cas [, mixed callback ] ]) - Sends a request for the given keys and returns immediately */ -PHP_METHOD(Memcached, getDelayed) -{ - php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); -} -/* }}} */ - -/* {{{ Memcached::getDelayedByKey(string server_key, array keys [, bool with_cas [, mixed callback ] ]) - Sends a request for the given keys from the server identified by the server key and returns immediately */ -PHP_METHOD(Memcached, getDelayedByKey) -{ - php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); -} -/* }}} */ - -/* {{{ -- php_memc_getDelayed_impl */ -static void php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) -{ - zval *keys = NULL; - zend_string *server_key = NULL; - zend_bool with_cas = 0; - size_t num_keys = 0; - zval *entry = NULL; - const char **mkeys = NULL; - size_t *mkeys_len = NULL; - uint64_t orig_cas_flag = 0; - zend_fcall_info fci = empty_fcall_info; - zend_fcall_info_cache fcc = empty_fcall_info_cache; - int i = 0; - memcached_return status = MEMCACHED_SUCCESS; - MEMC_METHOD_INIT_VARS; + case MEMC_OP_TOUCH: + status = memcached_touch(intern->memc, key->val, key->len, expiration); + break; + + case MEMC_OP_ADD: + status = memc_write_using_fn(memcached_add); + break; + + case MEMC_OP_REPLACE: + status = memc_write_using_fn(memcached_replace); + break; + + case MEMC_OP_APPEND: + status = memc_write_using_fn(memcached_append); + break; - if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|bf!", &server_key, - &keys, &with_cas, &fci, &fcc) == FAILURE) { - return; + case MEMC_OP_PREPEND: + status = memc_write_using_fn(memcached_prepend); + break; } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|bf!", &keys, &with_cas, - &fci, &fcc) == FAILURE) { - return; + if (status == MEMCACHED_END) { + status = MEMCACHED_SUCCESS; } } - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; - - /* - * Create the array of keys for libmemcached. If none of the keys were valid - * (strings), set bad key result code and return. - */ - num_keys = zend_hash_num_elements(Z_ARRVAL_P(keys)); - mkeys = safe_emalloc(num_keys, sizeof(*mkeys), 0); - mkeys_len = safe_emalloc(num_keys, sizeof(*mkeys_len), 0); - - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(keys), entry) { - if (Z_TYPE_P(entry) != IS_STRING) { - convert_to_string_ex(entry); - } - - if (Z_TYPE_P(entry) == IS_STRING && Z_STRLEN_P(entry) > 0) { - mkeys[i] = Z_STRVAL_P(entry); - mkeys_len[i] = Z_STRLEN_P(entry); - i++; - } - } ZEND_HASH_FOREACH_END(); - - if (i == 0) { - intern->rescode = MEMCACHED_BAD_KEY_PROVIDED; - efree(mkeys); - efree(mkeys_len); - zval_dtor(return_value); - RETURN_FALSE; + if (s_should_retry_write (intern, status) && retries-- > 0) { + goto retry; } - /* - * Enable CAS support, but only if it is currently disabled. - */ - if (with_cas) { - orig_cas_flag = memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); - if (orig_cas_flag == 0) { - memcached_behavior_set(intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); - } - } +#undef memc_write_using_fn +#undef memc_write_using_fn_by_key - /* - * Issue the request, but collect results only if the result callback is provided. - */ - if (by_key) { - status = memcached_mget_by_key(intern->memc, server_key->val, server_key->len, mkeys, mkeys_len, i); - } else { - status = memcached_mget(intern->memc, mkeys, mkeys_len, i); + if (payload) { + zend_string_release(payload); } - /* - * Restore the CAS support flag, but only if we had to turn it on. - */ - if (with_cas && orig_cas_flag == 0) { - memcached_behavior_set(intern->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + return 0; } + return 1; +} - efree(mkeys); - efree(mkeys_len); - if (php_memc_handle_error(intern, status) < 0) { - zval_dtor(return_value); - RETURN_FALSE; - } - if (fci.size != 0) { - /* - * We have a result callback. Iterate through the result set and invoke the - * callback for each one. - */ - memcached_result_st result; +/**************************************** + Methods +****************************************/ - memcached_result_create(intern->memc, &result); - while ((memcached_fetch_result(intern->memc, &result, &status)) != NULL) { - if (s_invoke_result_callback(getThis(), &fci, &fcc, &result) < 0) { - status = MEMCACHED_FAILURE; - break; - } - } - memcached_result_free(&result); - /* we successfully retrieved all rows */ - if (status == MEMCACHED_END) { - status = MEMCACHED_SUCCESS; - } - if (php_memc_handle_error(intern, status) < 0) { - RETURN_FALSE; - } - } +/* {{{ Memcached::__construct([string persistent_id[, callback on_new[, string connection_str]]])) + Creates a Memcached object, optionally using persistent memcache connection */ +static PHP_METHOD(Memcached, __construct) +{ + php_memc_object_t *intern; + php_memc_user_data_t *memc_user_data; - RETURN_TRUE; -} -/* }}} */ + zend_string *persistent_id = NULL; + zend_string *conn_str = NULL; + zend_string *plist_key = NULL; + zend_fcall_info fci = {0}; + zend_fcall_info_cache fci_cache; -/* {{{ Memcached::fetch() - Returns the next result from a previous delayed request */ -PHP_METHOD(Memcached, fetch) -{ - const char *res_key = NULL; - size_t res_key_len = 0; - const char *payload = NULL; - size_t payload_len = 0; - uint32_t flags = 0; - uint64_t cas = 0; - zval value, zv_cas; - memcached_result_st result; - memcached_return status = MEMCACHED_SUCCESS; - MEMC_METHOD_INIT_VARS; + zend_bool is_persistent = 0; - if (zend_parse_parameters_none() == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S!f!S", &persistent_id, &fci, &fci_cache, &conn_str) == FAILURE) { return; } - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + intern = Z_MEMC_OBJ_P(getThis()); + intern->is_pristine = 1; - memcached_result_create(intern->memc, &result); - if ((memcached_fetch_result(intern->memc, &result, &status)) == NULL) { - php_memc_handle_error(intern, status); - memcached_result_free(&result); - RETURN_FALSE; + if (persistent_id && persistent_id->len) { + zend_resource *le; + + plist_key = zend_string_alloc(sizeof("memcached:id=") + persistent_id->len - 1, 0); + snprintf(plist_key->val, plist_key->len + 1, "memcached:id=%s", persistent_id->val); + + if ((le = zend_hash_find_ptr(&EG(persistent_list), plist_key)) != NULL) { + if (le->type == php_memc_list_entry()) { + intern->memc = le->ptr; + intern->is_pristine = 0; + zend_string_release (plist_key); + return; + } + } + is_persistent = 1; } - if (!s_memcached_result_to_zval(intern->memc, &result, &value)) { - memcached_result_free(&result); - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FALSE; + if (conn_str && conn_str->len > 0) { + intern->memc = memcached (conn_str->val, conn_str->len); + } + else { + intern->memc = memcached (NULL, 0); } - array_init(return_value); + if (!intern->memc) { + // TODO: handle allocation fail + } + + memc_user_data = pecalloc (1, sizeof(*memc_user_data), is_persistent); + memc_user_data->serializer = MEMC_G(serializer_type); + memc_user_data->compression_type = MEMC_G(compression_type); + memc_user_data->compression_enabled = 1; + memc_user_data->store_retry_count = MEMC_G(store_retry_count); + memc_user_data->set_udf_flags = -1; + memc_user_data->is_persistent = is_persistent; + + memcached_set_user_data(intern->memc, memc_user_data); + + if (fci.size) { + if (!s_invoke_new_instance_cb(getThis(), &fci, &fci_cache, persistent_id) || EG(exception)) { + /* error calling or exception thrown from callback */ + if (plist_key) { + zend_string_release(plist_key); + } + /* + Setting intern->memc null prevents object destruction from freeing the memcached_st + We free it manually here because it might be persistent and has not been + registered to persistent_list yet + */ + php_memc_destroy(intern->memc, memc_user_data); + intern->memc = NULL; + return; + } + } - flags = memcached_result_flags(&result); - res_key = memcached_result_key_value(&result); - res_key_len = memcached_result_key_length(&result); - cas = memcached_result_cas(&result); + if (plist_key) { + zend_resource le; - add_assoc_stringl_ex(return_value, ZEND_STRL("key"), (char *) res_key, res_key_len); - add_assoc_zval_ex(return_value, ZEND_STRL("value"), &value); + le.type = php_memc_list_entry(); + le.ptr = intern->memc; - s_uint64_to_zval (&zv_cas, cas); - add_assoc_zval_ex(return_value, ZEND_STRL("cas"), &zv_cas); - add_assoc_long_ex(return_value, ZEND_STRL("flags"), MEMC_VAL_GET_USER_FLAGS(flags)); + GC_REFCOUNT(&le) = 1; - memcached_result_free(&result); + /* plist_key is not a persistent allocated key, thus we use str_update here */ + if (zend_hash_str_update_mem(&EG(persistent_list), plist_key->val, plist_key->len, &le, sizeof(le)) == NULL) { + zend_string_release(plist_key); + php_error_docref(NULL, E_ERROR, "could not register persistent entry"); + /* not reached */ + } + zend_string_release(plist_key); + } } /* }}} */ -/* {{{ Memcached::fetchAll() - Returns all the results from a previous delayed request */ -PHP_METHOD(Memcached, fetchAll) + + +static +void s_hash_to_keys(php_memc_keys_t *keys_out, HashTable *hash_in, zend_bool preserve_order, zval *return_value) { - const char *res_key = NULL; - size_t res_key_len = 0; - const char *payload = NULL; - size_t payload_len = 0; - uint32_t flags; - uint64_t cas = 0; - zval value, entry, zv_cas; - memcached_result_st result; - memcached_return status = MEMCACHED_SUCCESS; - MEMC_METHOD_INIT_VARS; + size_t idx = 0, alloc_count; + zval *zv; - if (zend_parse_parameters_none() == FAILURE) { + keys_out->num_valid_keys = 0; + + alloc_count = zend_hash_num_elements(hash_in); + if (!alloc_count) { return; } + keys_out->mkeys = ecalloc (alloc_count, sizeof (char *)); + keys_out->mkeys_len = ecalloc (alloc_count, sizeof (size_t)); + keys_out->strings = ecalloc (alloc_count, sizeof (zend_string *)); - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + ZEND_HASH_FOREACH_VAL(hash_in, zv) { + zend_string *key = zval_get_string(zv); - array_init(return_value); - memcached_result_create(intern->memc, &result); + if (preserve_order && return_value) { + add_assoc_null_ex(return_value, key->val, key->len); + } - while ((memcached_fetch_result(intern->memc, &result, &status)) != NULL) { + if (key->len > 0 && key->len < MEMCACHED_MAX_KEY) { + keys_out->mkeys[idx] = key->val; + keys_out->mkeys_len[idx] = key->len; - if (!s_memcached_result_to_zval(intern->memc, &result, &value)) { - memcached_result_free(&result); - zval_dtor(return_value); - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FALSE; + keys_out->strings[idx] = key; + idx++; + } + else { + zend_string_release (key); } - flags = memcached_result_flags(&result); - res_key = memcached_result_key_value(&result); - res_key_len = memcached_result_key_length(&result); - cas = memcached_result_cas(&result); + } ZEND_HASH_FOREACH_END(); - array_init(&entry); - add_assoc_stringl_ex(&entry, ZEND_STRL("key"), (char *)res_key, res_key_len); - add_assoc_zval_ex(&entry, ZEND_STRL("value"), &value); + if (!idx) { + efree (keys_out->mkeys); + efree (keys_out->mkeys_len); + efree (keys_out->strings); + } + keys_out->num_valid_keys = idx; +} - s_uint64_to_zval (&zv_cas, cas); - add_assoc_zval_ex(&entry, ZEND_STRL("cas"), &zv_cas); - add_assoc_long_ex(&entry, ZEND_STRL("flags"), MEMC_VAL_GET_USER_FLAGS(flags)); +static +void s_key_to_keys(php_memc_keys_t *keys_out, zend_string *key) +{ + zval zv_keys; - add_next_index_zval(return_value, &entry); - } + array_init (&zv_keys); + add_next_index_stringl (&zv_keys, key->val, key->len); - memcached_result_free(&result); + s_hash_to_keys(keys_out, Z_ARRVAL(zv_keys), 0, NULL); + zval_ptr_dtor(&zv_keys); +} - if (status != MEMCACHED_END && php_memc_handle_error(intern, status) < 0) { - zval_dtor(return_value); - RETURN_FALSE; +static +void s_clear_keys(php_memc_keys_t *keys) +{ + size_t i; + for (i = 0; i < keys->num_valid_keys; i++) { + zend_string_release (keys->strings[i]); } + efree(keys->strings); + efree(keys->mkeys); + efree(keys->mkeys_len); } -/* }}} */ -/* {{{ Memcached::set(string key, mixed value [, int expiration ]) - Sets the value for the given key */ -PHP_METHOD(Memcached, set) -{ - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 0); -} -/* }}} */ +typedef struct { + zend_bool extended; + zval *return_value; +} php_memc_get_ctx_t; -/* {{{ Memcached::setByKey(string server_key, string key, mixed value [, int expiration ]) - Sets the value for the given key on the server identified by the server key */ -PHP_METHOD(Memcached, setByKey) +static +zend_bool s_get_apply_fn(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *in_context) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 1); + php_memc_get_ctx_t *context = (php_memc_get_ctx_t *) in_context; + + if (context->extended) { + Z_TRY_ADDREF_P(value); + Z_TRY_ADDREF_P(cas); + + array_init (context->return_value); + add_assoc_zval (context->return_value, "value", value); + add_assoc_zval (context->return_value, "cas", cas); + add_assoc_long (context->return_value, "flags", (zend_long) MEMC_VAL_GET_USER_FLAGS(flags)); + } + else { + ZVAL_ZVAL(context->return_value, value, 1, 0); + } + return 0; /* Stop after one */ } -/* }}} */ -#ifdef HAVE_MEMCACHED_TOUCH -/* {{{ Memcached::touch(string key, [, int expiration ]) - Sets a new expiration for the given key */ -PHP_METHOD(Memcached, touch) +static +void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_TOUCH, 0); + php_memc_get_ctx_t context = {}; + php_memc_keys_t keys = {0}; + zend_long get_flags = 0; + zend_string *key; + zend_string *server_key = NULL; + zend_bool extended, mget_status; + memcached_return status = MEMCACHED_SUCCESS; + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + MEMC_METHOD_INIT_VARS; + + if (by_key) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|f!l", &server_key, &key, &fci, &fcc, &get_flags) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|f!l", &key, &fci, &fcc, &get_flags) == FAILURE) { + return; + } + } + + MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); + + context.extended = (get_flags & MEMC_GET_EXTENDED); + + ZVAL_UNDEF(return_value); + context.return_value = return_value; + + s_key_to_keys(&keys, key); + mget_status = php_memc_mget_apply(intern, server_key, &keys, s_get_apply_fn, context.extended, &context); + s_clear_keys(&keys); + + if (!mget_status) { + if (s_memc_status_has_result_code(intern, MEMCACHED_NOTFOUND) && fci.size > 0) { + status = s_invoke_cache_callback(object, &fci, &fcc, key, return_value); + + if (!status) { + zval_ptr_dtor(return_value); + RETURN_FROM_GET; + } + } + } + + if (s_memc_status_has_error(intern)) { + zval_ptr_dtor(return_value); + RETURN_FROM_GET; + } } -/* }}} */ -/* {{{ Memcached::touchbyKey(string key, [, int expiration ]) - Sets a new expiration for the given key */ -PHP_METHOD(Memcached, touchByKey) +/* {{{ Memcached::get(string key [, mixed callback [, int get_flags = 0]) + Returns a value for the given key or false */ +PHP_METHOD(Memcached, get) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_TOUCH, 1); + php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -#endif - -/* {{{ Memcached::setMulti(array items [, int expiration ]) - Sets the keys/values specified in the items array */ -PHP_METHOD(Memcached, setMulti) +/* {{{ Memcached::getByKey(string server_key, string key [, mixed callback [, int get_flags = 0]) + Returns a value for key from the server identified by the server key or false */ +PHP_METHOD(Memcached, getByKey) { - php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ Memcached::setMultiByKey(string server_key, array items [, int expiration ]) - Sets the keys/values specified in the items array on the server identified by the given server key */ -PHP_METHOD(Memcached, setMultiByKey) +static +zend_bool s_get_multi_apply_fn(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *in_context) { - php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); -} -/* }}} */ + php_memc_get_ctx_t *context = (php_memc_get_ctx_t *) in_context; + + Z_TRY_ADDREF_P(value); + + if (context->extended) { + zval node; + + Z_TRY_ADDREF_P(cas); + + array_init (&node); + add_assoc_zval (&node, "value", value); + add_assoc_zval (&node, "cas", cas); + add_assoc_long (&node, "flags", (zend_long) MEMC_VAL_GET_USER_FLAGS(flags)); -#define PHP_MEMC_FAILOVER_RETRY \ - if (!by_key && retry < memc_user_data->store_retry_count) { \ - switch (intern->rescode) { \ - case MEMCACHED_HOST_LOOKUP_FAILURE: \ - case MEMCACHED_CONNECTION_FAILURE: \ - case MEMCACHED_CONNECTION_BIND_FAILURE: \ - case MEMCACHED_WRITE_FAILURE: \ - case MEMCACHED_READ_FAILURE: \ - case MEMCACHED_UNKNOWN_READ_FAILURE: \ - case MEMCACHED_PROTOCOL_ERROR: \ - case MEMCACHED_SERVER_ERROR: \ - case MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE: \ - case MEMCACHED_TIMEOUT: \ - case MEMCACHED_FAIL_UNIX_SOCKET: \ - case MEMCACHED_SERVER_MARKED_DEAD: \ - case MEMCACHED_SERVER_TEMPORARILY_DISABLED: \ - if (memcached_server_count(intern->memc) > 0) { \ - retry++; \ - intern->rescode = 0; \ - goto retry; \ - } \ - break; \ - } \ + add_assoc_zval_ex(context->return_value, key->val, key->len, &node); } + else { + add_assoc_zval_ex(context->return_value, key->val, key->len, value); + } + return 1; +} -/* {{{ -- php_memc_setMulti_impl */ -static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) +/* {{{ -- php_memc_getMulti_impl */ +static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { - zval *entries; + php_memc_get_ctx_t context; + php_memc_keys_t keys_out; + + zval *keys = NULL; zend_string *server_key = NULL; - time_t expiration = 0; - zval *value; - zend_string *skey, *str_key = NULL; - ulong num_key; - zend_string *payload; - uint32_t flags = 0; - uint32_t retry = 0; - memcached_return status; - char tmp_key[MEMCACHED_MAX_KEY]; - int tmp_len = 0; + zend_long flags = 0; MEMC_METHOD_INIT_VARS; + zend_bool with_cas, retval, preserve_order, extended; + if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa|ll", &server_key, - &entries, &expiration) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa|l", &server_key, + &keys, &flags) == FAILURE) { return; } } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|ll", &entries, &expiration) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|l", &keys, &flags) == FAILURE) { return; } } MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - ZEND_HASH_FOREACH_KEY_VAL (Z_ARRVAL_P(entries), num_key, skey, value) { - if (skey) { - str_key = skey; - } else if (num_key || num_key == 0) { - /* Array keys are unsigned, but php integers are signed. - * Keys must be converted to signed strings that match - * php integers. */ - assert(sizeof(tmp_key) >= sizeof(ZEND_TOSTR(LONG_MIN))); - tmp_len = sprintf(tmp_key, "%ld", (long)num_key); - str_key = zend_string_init(tmp_key, tmp_len, 0); - } else { - continue; - } + array_init(return_value); - flags = 0; - if (memc_user_data->compression) { - MEMC_VAL_SET_FLAG(flags, MEMC_VAL_COMPRESSED); - } + preserve_order = (flags & MEMC_GET_PRESERVE_ORDER); + s_hash_to_keys(&keys_out, Z_ARRVAL_P(keys), preserve_order, return_value); - if (memc_user_data->set_udf_flags >= 0) { - MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) memc_user_data->set_udf_flags)); - } + context.extended = (flags & MEMC_GET_EXTENDED); + context.return_value = return_value; - payload = s_zval_to_payload(value, &flags, memc_user_data->serializer, memc_user_data->compression_type); - if (payload == NULL) { - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - if (!skey) { - zend_string_release(str_key); - } - RETURN_FALSE; - } + retval = php_memc_mget_apply(intern, server_key, &keys_out, s_get_multi_apply_fn, context.extended, &context); -retry: - if (!by_key) { - status = memcached_set(intern->memc, str_key->val, str_key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_set_by_key(intern->memc, server_key->val, server_key->len, str_key->val, str_key->len, payload->val, payload->len, expiration, flags); - } + s_clear_keys(&keys_out); - if (php_memc_handle_error(intern, status) < 0) { - PHP_MEMC_FAILOVER_RETRY - if (!skey) { - zend_string_release(str_key); - } - zend_string_release(payload); - RETURN_FALSE; - } - if (!skey) { - zend_string_release(str_key); - } - zend_string_release(payload); - } ZEND_HASH_FOREACH_END(); + if (!retval && (s_memc_status_has_result_code(intern, MEMCACHED_NOTFOUND) || s_memc_status_has_result_code(intern, MEMCACHED_SOME_ERRORS))) { + return; + } - RETURN_TRUE; + if (!retval || EG(exception)) { + zval_dtor(return_value); + RETURN_FROM_GET; + } } /* }}} */ -/* {{{ Memcached::add(string key, mixed value [, int expiration ]) - Sets the value for the given key, failing if the key already exists */ -PHP_METHOD(Memcached, add) +/* {{{ Memcached::getMulti(array keys[, long flags = 0 ]) + Returns values for the given keys or false */ +PHP_METHOD(Memcached, getMulti) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_ADD, 0); + php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::addByKey(string server_key, string key, mixed value [, int expiration ]) - Sets the value for the given key on the server identified by the sever key, failing if the key already exists */ -PHP_METHOD(Memcached, addByKey) +/* {{{ Memcached::getMultiByKey(string server_key, array keys[, long flags = 0 ]) + Returns values for the given keys from the server identified by the server key or false */ +PHP_METHOD(Memcached, getMultiByKey) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_ADD, 1); + php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ Memcached::append(string key, mixed value) - Appends the value to existing one for the key */ -PHP_METHOD(Memcached, append) +/* {{{ Memcached::getDelayed(array keys [, bool with_cas [, mixed callback ] ]) + Sends a request for the given keys and returns immediately */ +PHP_METHOD(Memcached, getDelayed) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_APPEND, 0); + php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::appendByKey(string server_key, string key, mixed value) - Appends the value to existing one for the key on the server identified by the server key */ -PHP_METHOD(Memcached, appendByKey) +/* {{{ Memcached::getDelayedByKey(string server_key, array keys [, bool with_cas [, mixed callback ] ]) + Sends a request for the given keys from the server identified by the server key and returns immediately */ +PHP_METHOD(Memcached, getDelayedByKey) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_APPEND, 1); + php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ Memcached::prepend(string key, mixed value) - Prepends the value to existing one for the key */ -PHP_METHOD(Memcached, prepend) -{ - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_PREPEND, 0); -} -/* }}} */ -/* {{{ Memcached::prependByKey(string server_key, string key, mixed value) - Prepends the value to existing one for the key on the server identified by the server key */ -PHP_METHOD(Memcached, prependByKey) +static +void s_create_result_array(zend_string *key, zval *value, zval *cas, uint32_t flags, zval *return_value) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_PREPEND, 1); -} -/* }}} */ + Z_TRY_ADDREF_P(value); + Z_TRY_ADDREF_P(cas); -/* {{{ Memcached::replace(string key, mixed value [, int expiration ]) - Replaces the value for the given key, failing if the key doesn't exist */ -PHP_METHOD(Memcached, replace) -{ - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_REPLACE, 0); + add_assoc_stringl_ex(return_value, ZEND_STRL("key"), key->val, key->len); + add_assoc_zval_ex(return_value, ZEND_STRL("value"), value); + + add_assoc_zval_ex(return_value, ZEND_STRL("cas"), cas); + add_assoc_long_ex(return_value, ZEND_STRL("flags"), MEMC_VAL_GET_USER_FLAGS(flags)); } -/* }}} */ -/* {{{ Memcached::replaceByKey(string server_key, string key, mixed value [, int expiration ]) - Replaces the value for the given key on the server identified by the server key, failing if the key doesn't exist */ -PHP_METHOD(Memcached, replaceByKey) +static +zend_bool s_result_callback_apply(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *in_context) { - php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_REPLACE, 1); + zend_bool status = 1; + + zval retval, zv_result; + php_memc_result_callback_ctx_t *context = (php_memc_result_callback_ctx_t *) in_context; + + array_init(&zv_result); + s_create_result_array(key, value, cas, flags, &zv_result); + + zend_fcall_info_argn(&context->fci, 2, context->object, &zv_result); + + context->fci.retval = &retval; + context->fci.param_count = 2; + + ZVAL_UNDEF(&retval); + if (zend_call_function(&context->fci, &context->fcc) == FAILURE) { + if (Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&retval); + } + php_error_docref(NULL, E_WARNING, "could not invoke result callback"); + status = 0; + } + + if (Z_TYPE(retval) != IS_UNDEF) { + zval_ptr_dtor(&retval); + } + + zend_fcall_info_args_clear(&context->fci, 2); + zval_ptr_dtor(&zv_result); + return status; } -/* }}} */ -/* {{{ -- php_memc_store_impl */ -static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool by_key) +/* {{{ -- php_memc_getDelayed_impl */ +static void php_memc_getDelayed_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { - zend_string *key; + php_memc_keys_t keys_out = {0}; + + zval *keys = NULL; zend_string *server_key = NULL; - zend_string *s_value; - zval s_zvalue; - zval *value; - zend_long expiration = 0; - zend_string *payload = NULL; - uint32_t flags = 0; - uint32_t retry = 0; - memcached_return status; + zend_bool with_cas = 0; + + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + memcached_return status = MEMCACHED_SUCCESS; MEMC_METHOD_INIT_VARS; + if (by_key) { - if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSS", &server_key, &key, &s_value) == FAILURE) { - return; - } - value = &s_zvalue; - ZVAL_STR(value, s_value); - } else if (op == MEMC_OP_TOUCH) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &server_key, &key, &expiration) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSz|l", &server_key, &key, &value, &expiration) == FAILURE) { - return; - } - } + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|bf!", &server_key, + &keys, &with_cas, &fci, &fcc) == FAILURE) { + return; + } } else { - if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &key, &s_value) == FAILURE) { - return; - } - value = &s_zvalue; - ZVAL_STR(value, s_value); - } else if (op == MEMC_OP_TOUCH) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &key, &expiration) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &key, &value, &expiration) == FAILURE) { - return; - } + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|bf!", &keys, &with_cas, + &fci, &fcc) == FAILURE) { + return; } } MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; - - if (memc_user_data->compression) { - /* - * When compression is enabled, we cannot do appends/prepends because that would - * corrupt the compressed values. It is up to the user to fetch the value, - * append/prepend new data, and store it again. - */ - if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { - php_error_docref(NULL, E_WARNING, "cannot append/prepend with compression turned on"); - return; - } - MEMC_VAL_SET_FLAG(flags, MEMC_VAL_COMPRESSED); - } + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - /* - * php_memcached uses 16 bits internally to store type, compression and serialization info. - * We use 16 upper bits to store user defined flags. - */ - if (memc_user_data->set_udf_flags >= 0) { - MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) memc_user_data->set_udf_flags)); - } + s_hash_to_keys(&keys_out, Z_ARRVAL_P(keys), 0, NULL); - if (op == MEMC_OP_TOUCH) { -#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x01000016 - if (memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { - php_error_docref(NULL, E_WARNING, "using touch command with binary protocol is not recommended with libmemcached versions below 1.0.16"); - } -#endif + if (fci.size > 0) { + php_memc_result_callback_ctx_t context = { + getThis(), fci, fcc + }; + status = php_memc_mget_apply(intern, server_key, &keys_out, &s_result_callback_apply, with_cas, (void *) &context); } else { - payload = s_zval_to_payload(value, &flags, memc_user_data->serializer, memc_user_data->compression_type); - if (payload == NULL) { - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FALSE; - } + status = php_memc_mget_apply(intern, server_key, &keys_out, NULL, with_cas, NULL); } -retry: - switch (op) { - case MEMC_OP_SET: - if (!server_key) { - status = memcached_set(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_set_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, payload->val, payload->len, expiration, flags); - } - break; -#ifdef HAVE_MEMCACHED_TOUCH - case MEMC_OP_TOUCH: - if (!server_key) { - status = memcached_touch(intern->memc, key->val, key->len, expiration); - } else { - status = memcached_touch_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, expiration); - } - break; -#endif - case MEMC_OP_ADD: - if (!server_key) { - status = memcached_add(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_add_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, payload->val, payload->len, expiration, flags); - } - break; - - case MEMC_OP_REPLACE: - if (!server_key) { - status = memcached_replace(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_replace_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, payload->val, payload->len, expiration, flags); - } - break; - - case MEMC_OP_APPEND: - if (!server_key) { - status = memcached_append(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_append_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, payload->val, payload->len, expiration, flags); - } - break; - - case MEMC_OP_PREPEND: - if (!server_key) { - status = memcached_prepend(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - } else { - status = memcached_prepend_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, payload->val, payload->len, expiration, flags); - } - break; - default: - /* not reached */ - status = 0; - assert(0); - break; - } + s_clear_keys(&keys_out); + RETURN_BOOL(status); +} +/* }}} */ - if (php_memc_handle_error(intern, status) < 0) { - PHP_MEMC_FAILOVER_RETRY - RETVAL_FALSE; - } else { - RETVAL_TRUE; - } +static +zend_bool s_fetch_apply(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *in_context) +{ + zval *return_value = (zval *) in_context; + s_create_result_array(key, value, cas, flags, return_value); - if (payload) { - zend_string_release(payload); - } + return 0; // stop iterating after one } -/* }}} */ -/* {{{ -- php_memc_cas_impl */ -static void php_memc_cas_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) +/* {{{ Memcached::fetch() + Returns the next result from a previous delayed request */ +PHP_METHOD(Memcached, fetch) { - zval *zv_cas; - uint64_t cas; - zend_string *key; - zend_string *server_key = NULL; - zval *value; - time_t expiration = 0; - zend_string *payload; + const char *res_key = NULL; + size_t res_key_len = 0; + const char *payload = NULL; + size_t payload_len = 0; uint32_t flags = 0; - memcached_return status; + uint64_t cas = 0; + zval value, zv_cas; + memcached_result_st result; + memcached_return status = MEMCACHED_SUCCESS; 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; - } + if (zend_parse_parameters_none() == FAILURE) { + return; } MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - cas = s_zval_to_uint64(zv_cas); + array_init(return_value); + status = php_memc_result_apply(intern, s_fetch_apply, return_value); - if (memc_user_data->compression) { - MEMC_VAL_SET_FLAG(flags, MEMC_VAL_COMPRESSED); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + zval_ptr_dtor(return_value); + RETURN_FROM_GET; } +} +/* }}} */ - /* - * php_memcached uses 16 bits internally to store type, compression and serialization info. - * We use 16 upper bits to store user defined flags. - */ - if (memc_user_data->set_udf_flags >= 0) { - MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) memc_user_data->set_udf_flags)); - } +static +zend_bool s_fetch_all_apply(php_memc_object_t *intern, zend_string *key, zval *value, zval *cas, uint32_t flags, void *in_context) +{ + zval zv; + zval *return_value = (zval *) in_context; - payload = s_zval_to_payload(value, &flags, memc_user_data->serializer, memc_user_data->compression_type); - if (payload == NULL) { - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FALSE; - } + array_init (&zv); + s_create_result_array(key, value, cas, flags, &zv); - if (by_key) { - status = memcached_cas_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, payload->val, payload->len, expiration, flags, cas); - } else { - status = memcached_cas(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags, cas); + add_next_index_zval(return_value, &zv); + return 1; +} + +/* {{{ Memcached::fetchAll() + Returns all the results from a previous delayed request */ +PHP_METHOD(Memcached, fetchAll) +{ + const char *res_key = NULL; + size_t res_key_len = 0; + const char *payload = NULL; + size_t payload_len = 0; + uint32_t flags; + uint64_t cas = 0; + zval value, entry, zv_cas; + memcached_result_st result; + memcached_return status = MEMCACHED_SUCCESS; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; } - zend_string_release(payload); - if (php_memc_handle_error(intern, status) < 0) { + MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); + + array_init(return_value); + status = php_memc_result_apply(intern, s_fetch_all_apply, return_value); + + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + zval_dtor(return_value); 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) +/* {{{ Memcached::set(string key, mixed value [, int expiration ]) + Sets the value for the given key */ +PHP_METHOD(Memcached, set) { - php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 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) +/* {{{ Memcached::setByKey(string server_key, string key, mixed value [, int expiration ]) + Sets the value for the given key on the server identified by the server key */ +PHP_METHOD(Memcached, setByKey) { - php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 1); } /* }}} */ -/* {{{ Memcached::delete(string key [, int time ]) - Deletes the given key */ -PHP_METHOD(Memcached, delete) +#ifdef HAVE_MEMCACHED_TOUCH +/* {{{ Memcached::touch(string key, [, int expiration ]) + Sets a new expiration for the given key */ +PHP_METHOD(Memcached, touch) { - php_memc_delete_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_TOUCH, 0); } /* }}} */ -/* {{{ Memcached::deleteMulti(array keys [, int time ]) - Deletes the given keys */ -PHP_METHOD(Memcached, deleteMulti) +/* {{{ Memcached::touchbyKey(string key, [, int expiration ]) + Sets a new expiration for the given key */ +PHP_METHOD(Memcached, touchByKey) { - php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_TOUCH, 1); } /* }}} */ +#endif -/* {{{ Memcached::deleteByKey(string server_key, string key [, int time ]) - Deletes the given key from the server identified by the server key */ -PHP_METHOD(Memcached, deleteByKey) + +/* {{{ Memcached::setMulti(array items [, int expiration ]) + Sets the keys/values specified in the items array */ +PHP_METHOD(Memcached, setMulti) { - php_memc_delete_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::deleteMultiByKey(array keys [, int time ]) - Deletes the given key from the server identified by the server key */ -PHP_METHOD(Memcached, deleteMultiByKey) +/* {{{ Memcached::setMultiByKey(string server_key, array items [, int expiration ]) + Sets the keys/values specified in the items array on the server identified by the given server key */ +PHP_METHOD(Memcached, setMultiByKey) { - php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ -- php_memc_delete_impl */ -static void php_memc_delete_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) +/* {{{ -- php_memc_setMulti_impl */ +static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { - zend_string *key, *server_key; + zval *entries; + zend_string *server_key = NULL; time_t expiration = 0; + zval *value; + zend_string *skey, *str_key = NULL; + ulong num_key; + zend_string *payload; + uint32_t flags = 0; + uint32_t retry = 0; memcached_return status; + char tmp_key[MEMCACHED_MAX_KEY]; + int tmp_len = 0; MEMC_METHOD_INIT_VARS; if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &server_key, &key, &expiration) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa|ll", &server_key, + &entries, &expiration) == FAILURE) { return; } } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &key, &expiration) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|ll", &entries, &expiration) == FAILURE) { return; } - server_key = key; } MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; - - if (by_key) { - status = memcached_delete_by_key(intern->memc, server_key->val, server_key->len, key->val, - key->len, expiration); - } else { - status = memcached_delete(intern->memc, key->val, key->len, expiration); - } + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - if (php_memc_handle_error(intern, status) < 0) { - RETURN_FALSE; - } + ZEND_HASH_FOREACH_KEY_VAL (Z_ARRVAL_P(entries), num_key, skey, value) { - RETURN_TRUE; -} -/* }}} */ + zend_string *str_key; -/* {{{ -- php_memc_deleteMulti_impl */ -static void php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) -{ - zval *entries; - zend_string *server_key = NULL; - time_t expiration = 0; - zval *entry; - - memcached_return status; - MEMC_METHOD_INIT_VARS; - - if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|l", &server_key, &entries, &expiration) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &entries, &expiration) == FAILURE) { - return; + if (skey) { + str_key = skey; } - } - - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + else { + char tmp_key[64]; - array_init(return_value); - ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P(entries), entry) { - if (Z_TYPE_P(entry) != IS_STRING) { - convert_to_string_ex(entry); + tmp_len = snprintf(tmp_key, sizeof(tmp_key) - 1, "%ld", (long)num_key); + str_key = zend_string_init(tmp_key, tmp_len, 0); } - if (Z_STRLEN_P(entry) == 0) { - continue; + if (!s_memc_write_zval (intern, MEMC_OP_SET, server_key, str_key, value, expiration)) { + php_error_docref(NULL, E_WARNING, "failed to set key %s", str_key->val); } - if (by_key) { - status = memcached_delete_by_key(intern->memc, server_key->val, server_key->len, Z_STRVAL_P(entry), Z_STRLEN_P(entry), expiration); - } else { - status = memcached_delete_by_key(intern->memc, Z_STRVAL_P(entry), Z_STRLEN_P(entry), Z_STRVAL_P(entry), Z_STRLEN_P(entry), expiration); + if (!skey) { + zend_string_release (str_key); } - - if (php_memc_handle_error(intern, status) < 0) { - add_assoc_long(return_value, Z_STRVAL_P(entry), status); - } else { - add_assoc_bool(return_value, Z_STRVAL_P(entry), 1); - } } ZEND_HASH_FOREACH_END(); - return; + RETURN_BOOL(!s_memc_status_has_error(intern)); } /* }}} */ -/* {{{ -- php_memc_incdec_impl */ -static void php_memc_incdec_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key, zend_bool incr) +/* {{{ Memcached::add(string key, mixed value [, int expiration ]) + Sets the value for the given key, failing if the key already exists */ +PHP_METHOD(Memcached, add) { - zend_string *key, *server_key = NULL; - long offset = 1; - uint64_t value, initial = 0; - time_t expiry = 0; - memcached_return status; - int n_args = ZEND_NUM_ARGS(); - uint32_t retry = 0; - - MEMC_METHOD_INIT_VARS; - - if (!by_key) { - if (zend_parse_parameters(n_args, "S|lll", &key, &offset, &initial, &expiry) == FAILURE) { - return; - } - } else { - if (zend_parse_parameters(n_args, "SS|lll", &server_key, &key, &offset, &initial, &expiry) == FAILURE) { - return; - } - } - - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; - - if (offset < 0) { - php_error_docref(NULL, E_WARNING, "offset has to be > 0"); - RETURN_FALSE; - } + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_ADD, 0); +} +/* }}} */ -retry: - if ((!by_key && n_args < 3) || (by_key && n_args < 4)) { - if (by_key) { - if (incr) { - status = memcached_increment_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, &value); - } else { - status = memcached_decrement_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, &value); - } - } else { - if (incr) { - status = memcached_increment(intern->memc, key->val, key->len, (unsigned int)offset, &value); - } else { - status = memcached_decrement(intern->memc, key->val, key->len, (unsigned int)offset, &value); - } - } - } else { - if (!memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { - php_error_docref(NULL, E_WARNING, "Initial value is only supported with binary protocol"); - RETURN_FALSE; - } - if (by_key) { - if (incr) { - status = memcached_increment_with_initial_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, initial, expiry, &value); - } else { - status = memcached_decrement_with_initial_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, initial, expiry, &value); - } - } else { - if (incr) { - status = memcached_increment_with_initial(intern->memc, key->val, key->len, (unsigned int)offset, initial, expiry, &value); - } else { - status = memcached_decrement_with_initial(intern->memc, key->val, key->len, (unsigned int)offset, initial, expiry, &value); - } - } - } +/* {{{ Memcached::addByKey(string server_key, string key, mixed value [, int expiration ]) + Sets the value for the given key on the server identified by the sever key, failing if the key already exists */ +PHP_METHOD(Memcached, addByKey) +{ + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_ADD, 1); +} +/* }}} */ - if (php_memc_handle_error(intern, status) < 0) { - PHP_MEMC_FAILOVER_RETRY - RETURN_FALSE; - } +/* {{{ Memcached::append(string key, mixed value) + Appends the value to existing one for the key */ +PHP_METHOD(Memcached, append) +{ + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_APPEND, 0); +} +/* }}} */ - RETURN_LONG((long)value); +/* {{{ Memcached::appendByKey(string server_key, string key, mixed value) + Appends the value to existing one for the key on the server identified by the server key */ +PHP_METHOD(Memcached, appendByKey) +{ + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_APPEND, 1); } /* }}} */ -/* {{{ Memcached::increment(string key [, int delta [, initial_value [, expiry time ] ] ]) - Increments the value for the given key by delta, defaulting to 1 */ -PHP_METHOD(Memcached, increment) +/* {{{ Memcached::prepend(string key, mixed value) + Prepends the value to existing one for the key */ +PHP_METHOD(Memcached, prepend) { - php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_PREPEND, 0); } /* }}} */ -/* {{{ Memcached::decrement(string key [, int delta [, initial_value [, expiry time ] ] ]) - Decrements the value for the given key by delta, defaulting to 1 */ -PHP_METHOD(Memcached, decrement) +/* {{{ Memcached::prependByKey(string server_key, string key, mixed value) + Prepends the value to existing one for the key on the server identified by the server key */ +PHP_METHOD(Memcached, prependByKey) { - php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_PREPEND, 1); } /* }}} */ -/* {{{ Memcached::decrementByKey(string server_key, string key [, int delta [, initial_value [, expiry time ] ] ]) - Decrements by server the value for the given key by delta, defaulting to 1 */ -PHP_METHOD(Memcached, decrementByKey) +/* {{{ Memcached::replace(string key, mixed value [, int expiration ]) + Replaces the value for the given key, failing if the key doesn't exist */ +PHP_METHOD(Memcached, replace) { - php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_REPLACE, 0); } /* }}} */ -/* {{{ Memcached::increment(string server_key, string key [, int delta [, initial_value [, expiry time ] ] ]) - Increments by server the value for the given key by delta, defaulting to 1 */ -PHP_METHOD(Memcached, incrementByKey) +/* {{{ Memcached::replaceByKey(string server_key, string key, mixed value [, int expiration ]) + Replaces the value for the given key on the server identified by the server key, failing if the key doesn't exist */ +PHP_METHOD(Memcached, replaceByKey) { - php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1); + php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_REPLACE, 1); } /* }}} */ -/* {{{ Memcached::addServer(string hostname, int port [, int weight ]) - Adds the given memcache server to the list */ -PHP_METHOD(Memcached, addServer) +/* {{{ -- php_memc_store_impl */ +static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool by_key) { - zend_string *host; - long port, weight = 0; + zend_string *key; + zend_string *server_key = NULL; + zend_string *s_value; + zval s_zvalue; + zval *value = NULL; + zend_long expiration = 0; + zend_string *payload = NULL; + uint32_t flags = 0; + uint32_t retry = 0; memcached_return status; MEMC_METHOD_INIT_VARS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|l", &host, &port, &weight) == FAILURE) { - return; + if (by_key) { + if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSS", &server_key, &key, &s_value) == FAILURE) { + return; + } + value = &s_zvalue; + ZVAL_STR(value, s_value); + } else if (op == MEMC_OP_TOUCH) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &server_key, &key, &expiration) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSz|l", &server_key, &key, &value, &expiration) == FAILURE) { + return; + } + } + } else { + if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &key, &s_value) == FAILURE) { + return; + } + value = &s_zvalue; + ZVAL_STR(value, s_value); + } else if (op == MEMC_OP_TOUCH) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &key, &expiration) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sz|l", &key, &value, &expiration) == FAILURE) { + return; + } + } } MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); -#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x01000002 - if (host->val[0] == '/') { /* unix domain socket */ - status = memcached_server_add_unix_socket_with_weight(intern->memc, host->val, weight); - } else if (memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_USE_UDP)) { - status = memcached_server_add_udp_with_weight(intern->memc, host->val, port, weight); - } else { - status = memcached_server_add_with_weight(intern->memc, host->val, port, weight); + if (memc_user_data->compression_enabled) { + /* + * When compression is enabled, we cannot do appends/prepends because that would + * corrupt the compressed values. It is up to the user to fetch the value, + * append/prepend new data, and store it again. + */ + if (op == MEMC_OP_APPEND || op == MEMC_OP_PREPEND) { + php_error_docref(NULL, E_WARNING, "cannot append/prepend with compression turned on"); + RETURN_NULL(); + } } -#else - status = memcached_server_add_with_weight(intern->memc, host->val, port, weight); + + + if (op == MEMC_OP_TOUCH) { +#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x01000016 + if (memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { + php_error_docref(NULL, E_WARNING, "using touch command with binary protocol is not recommended with libmemcached versions below 1.0.16"); + } #endif + } - if (php_memc_handle_error(intern, status) < 0) { + if (!s_memc_write_zval (intern, op, server_key, key, value, expiration)) { RETURN_FALSE; } - RETURN_TRUE; } /* }}} */ -/* {{{ Memcached::addServers(array servers) - Adds the given memcache servers to the server list */ -PHP_METHOD(Memcached, addServers) +/* {{{ -- php_memc_cas_impl */ +static void php_memc_cas_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { - zval *servers; - zval *entry; - zval *z_host, *z_port, *z_weight = NULL; - uint32_t weight = 0; - HashPosition pos; - int entry_size, i = 0; - memcached_server_st *list = NULL; - memcached_return status; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &servers) == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; + 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; - ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P(servers), entry) { - if (Z_TYPE_P(entry) != IS_ARRAY) { - php_error_docref(NULL, E_WARNING, "server list entry #%d is not an array", i+1); - i++; - continue; + 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; + } + } - entry_size = zend_hash_num_elements(Z_ARRVAL_P(entry)); - - if (entry_size > 1) { - zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(entry), &pos); - - /* Check that we have a host */ - if ((z_host = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { - php_error_docref(NULL, E_WARNING, "could not get server host for entry #%d", i+1); - i++; - continue; - } - - /* Check that we have a port */ - if (zend_hash_move_forward_ex(Z_ARRVAL_P(entry), &pos) == FAILURE || - (z_port = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { - php_error_docref(NULL, E_WARNING, "could not get server port for entry #%d", i+1); - i++; - continue; - } - - convert_to_string_ex(z_host); - convert_to_long_ex(z_port); - - weight = 0; - if (entry_size > 2) { - /* Try to get weight */ - if (zend_hash_move_forward_ex(Z_ARRVAL_P(entry), &pos) == FAILURE || - (z_weight = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { - php_error_docref(NULL, E_WARNING, "could not get server weight for entry #%d", i+1); - } + MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - convert_to_long_ex(z_weight); - weight = Z_LVAL_P(z_weight); - } + cas = s_zval_to_uint64(zv_cas); - list = memcached_server_list_append_with_weight(list, Z_STRVAL_P(z_host), - Z_LVAL_P(z_port), weight, &status); + payload = s_zval_to_payload(intern, value, &flags); + if (payload == NULL) { + intern->rescode = MEMC_RES_PAYLOAD_FAILURE; + RETURN_FALSE; + } - if (php_memc_handle_error(intern, status) == 0) { - i++; - continue; - } - } - i++; - /* catch-all for all errors */ - php_error_docref(NULL, E_WARNING, "could not add entry #%d to the server list", i+1); - } ZEND_HASH_FOREACH_END(); + if (by_key) { + status = memcached_cas_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, payload->val, payload->len, expiration, flags, cas); + } else { + status = memcached_cas(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags, cas); + } - status = memcached_server_push(intern->memc, list); - memcached_server_list_free(list); - if (php_memc_handle_error(intern, status) < 0) { + zend_string_release(payload); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { RETURN_FALSE; } @@ -2076,351 +1997,725 @@ PHP_METHOD(Memcached, addServers) } /* }}} */ -/* {{{ Memcached::getServerList() - Returns the list of the memcache servers in use */ -PHP_METHOD(Memcached, getServerList) +/* {{{ 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) { - memcached_server_function callbacks[1]; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - callbacks[0] = s_server_cursor_list_servers_cb; - array_init(return_value); - memcached_server_cursor(intern->memc, callbacks, return_value, 1); + php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::getServerByKey(string server_key) - Returns the server identified by the given server key */ -PHP_METHOD(Memcached, getServerByKey) +/* {{{ 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) { - zend_string *server_key; - php_memcached_instance_st server_instance; - memcached_return error; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &server_key) == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - intern->rescode = MEMCACHED_SUCCESS; - - server_instance = memcached_server_by_key(intern->memc, server_key->val, server_key->len, &error); - if (server_instance == NULL) { - php_memc_handle_error(intern, error); - RETURN_FALSE; - } - - array_init(return_value); - add_assoc_string(return_value, "host", (char*) memcached_server_name(server_instance)); - add_assoc_long(return_value, "port", memcached_server_port(server_instance)); - add_assoc_long(return_value, "weight", 0); + php_memc_cas_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ Memcached::resetServerList() - Reset the server list in use */ -PHP_METHOD(Memcached, resetServerList) +/* {{{ Memcached::delete(string key [, int time ]) + Deletes the given key */ +PHP_METHOD(Memcached, delete) { - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - memcached_servers_reset(intern->memc); - RETURN_TRUE; + php_memc_delete_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::quit() - Close any open connections */ -PHP_METHOD(Memcached, quit) +/* {{{ Memcached::deleteMulti(array keys [, int time ]) + Deletes the given keys */ +PHP_METHOD(Memcached, deleteMulti) { - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - memcached_quit(intern->memc); - RETURN_TRUE; + php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::flushBuffers() - Flush and senf buffered commands */ -PHP_METHOD(Memcached, flushBuffers) +/* {{{ Memcached::deleteByKey(string server_key, string key [, int time ]) + Deletes the given key from the server identified by the server key */ +PHP_METHOD(Memcached, deleteByKey) { - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - RETURN_BOOL(memcached_flush_buffers(intern->memc) == MEMCACHED_SUCCESS); + php_memc_delete_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -#ifdef HAVE_LIBMEMCACHED_CHECK_CONFIGURATION -/* {{{ Memcached::getLastErrorMessage() - Returns the last error message that occurred */ -PHP_METHOD(Memcached, getLastErrorMessage) +/* {{{ Memcached::deleteMultiByKey(array keys [, int time ]) + Deletes the given key from the server identified by the server key */ +PHP_METHOD(Memcached, deleteMultiByKey) { - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - RETURN_STRING(memcached_last_error_message(intern->memc)); + php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ -/* {{{ Memcached::getLastErrorCode() - Returns the last error code that occurred */ -PHP_METHOD(Memcached, getLastErrorCode) +/* {{{ -- php_memc_delete_impl */ +static void php_memc_delete_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { + zend_string *key, *server_key; + time_t expiration = 0; + memcached_return status; MEMC_METHOD_INIT_VARS; - if (zend_parse_parameters_none() == FAILURE) { - return; + if (by_key) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|l", &server_key, &key, &expiration) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|l", &key, &expiration) == FAILURE) { + return; + } + server_key = key; } MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - RETURN_LONG(memcached_last_error(intern->memc)); + if (by_key) { + status = memcached_delete_by_key(intern->memc, server_key->val, server_key->len, key->val, + key->len, expiration); + } else { + status = memcached_delete(intern->memc, key->val, key->len, expiration); + } + + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + RETURN_FALSE; + } + + RETURN_TRUE; } /* }}} */ -/* {{{ Memcached::getLastErrorErrno() - Returns the last error errno that occurred */ -PHP_METHOD(Memcached, getLastErrorErrno) +/* {{{ -- php_memc_deleteMulti_impl */ +static void php_memc_deleteMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { + zval *entries; + zend_string *server_key = NULL; + time_t expiration = 0; + zval *entry; + + memcached_return status; MEMC_METHOD_INIT_VARS; - if (zend_parse_parameters_none() == FAILURE) { - return; + if (by_key) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|l", &server_key, &entries, &expiration) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &entries, &expiration) == FAILURE) { + return; + } } MEMC_METHOD_FETCH_OBJECT; - - RETURN_LONG(memcached_last_error_errno(intern->memc)); -} -/* }}} */ -#endif - -/* {{{ Memcached::getLastDisconnectedServer() - Returns the last disconnected server - Was added in 0.34 according to libmemcached's Changelog */ -PHP_METHOD(Memcached, getLastDisconnectedServer) -{ - php_memcached_instance_st server_instance; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - server_instance = memcached_server_get_last_disconnect(intern->memc); - if (server_instance == NULL) { - RETURN_FALSE; - } + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); array_init(return_value); - add_assoc_string(return_value, "host", (char*) memcached_server_name(server_instance)); - add_assoc_long(return_value, "port", memcached_server_port(server_instance)); -} -/* }}} */ - -#include - -static -zend_bool s_long_value(const char *str, zend_long *value) -{ - char *end = (char *) str; - - errno = 0; - *value = strtol(str, &end, 10); - - if (errno || str == end || *end != '\0') { - return 0; - } - return 1; -} - -static -zend_bool s_double_value(const char *str, double *value) -{ - char *end = (char *) str; - - errno = 0; - *value = strtod(str, &end); - - if (errno || str == end || *end != '\0') { - return 0; - } - return 1; -} - -static -memcached_return s_stat_execute_cb (php_memcached_instance_st instance, const char *key, size_t key_length, const char *value, size_t value_length, void *context) -{ - char *server_key; - size_t server_key_len; - zend_long long_val; - double d_val; - char *buffer; - - zval *return_value = (zval *) context; - zval *server_values; + ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P(entries), entry) { + if (Z_TYPE_P(entry) != IS_STRING) { + convert_to_string_ex(entry); + } - server_key_len = spprintf (&server_key, 0, "%s:%d", memcached_server_name(instance), memcached_server_port(instance)); - server_values = zend_hash_str_find(Z_ARRVAL_P(return_value), server_key, server_key_len); + if (Z_STRLEN_P(entry) == 0) { + continue; + } - if (!server_values) { - zval zv; - array_init(&zv); + if (by_key) { + status = memcached_delete_by_key(intern->memc, server_key->val, server_key->len, Z_STRVAL_P(entry), Z_STRLEN_P(entry), expiration); + } else { + status = memcached_delete_by_key(intern->memc, Z_STRVAL_P(entry), Z_STRLEN_P(entry), Z_STRVAL_P(entry), Z_STRLEN_P(entry), expiration); + } - server_values = &zv; - add_assoc_zval_ex(return_value, server_key, server_key_len, server_values); - } - spprintf (&buffer, 0, "%.*s", value_length, value); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + add_assoc_long(return_value, Z_STRVAL_P(entry), status); + } else { + add_assoc_bool(return_value, Z_STRVAL_P(entry), 1); + } + } ZEND_HASH_FOREACH_END(); - /* Check type */ - if (s_long_value (buffer, &long_val)) { - add_assoc_long(server_values, key, long_val); - } - else if (s_double_value (buffer, &d_val)) { - add_assoc_double(server_values, key, d_val); - } - else { - add_assoc_stringl_ex(server_values, key, key_length, value, value_length); - } - efree (buffer); - efree (server_key); - return MEMCACHED_SUCCESS; + return; } +/* }}} */ -/* {{{ Memcached::getStats() - Returns statistics for the memcache servers */ -PHP_METHOD(Memcached, getStats) +/* {{{ -- php_memc_incdec_impl */ +static void php_memc_incdec_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key, zend_bool incr) { + zend_string *key, *server_key = NULL; + long offset = 1; + uint64_t value = UINT64_MAX, initial = 0; + time_t expiry = 0; memcached_return status; + int n_args = ZEND_NUM_ARGS(); + MEMC_METHOD_INIT_VARS; - if (zend_parse_parameters_none() == FAILURE) { - return; + if (!by_key) { + if (zend_parse_parameters(n_args, "S|lll", &key, &offset, &initial, &expiry) == FAILURE) { + return; + } + } else { + if (zend_parse_parameters(n_args, "SS|lll", &server_key, &key, &offset, &initial, &expiry) == FAILURE) { + return; + } } MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); - array_init(return_value); - status = memcached_stat_execute(intern->memc, NULL, s_stat_execute_cb, return_value); - if (php_memc_handle_error(intern, status) < 0) { - zval_ptr_dtor(return_value); + if (offset < 0) { + php_error_docref(NULL, E_WARNING, "offset has to be > 0"); RETURN_FALSE; } -} -/* }}} */ -/* {{{ Memcached::getVersion() - Returns the version of each memcached server in the pool */ -PHP_METHOD(Memcached, getVersion) -{ - memcached_return rc; - memcached_server_function callbacks[1]; - MEMC_METHOD_INIT_VARS; + if ((!by_key && n_args < 3) || (by_key && n_args < 4)) { + if (by_key) { + if (incr) { + status = memcached_increment_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, &value); + } else { + status = memcached_decrement_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, &value); + } + } else { + if (incr) { + status = memcached_increment(intern->memc, key->val, key->len, (unsigned int)offset, &value); + } else { + status = memcached_decrement(intern->memc, key->val, key->len, (unsigned int)offset, &value); + } + } - if (zend_parse_parameters_none() == FAILURE) { - return; - } + } else { + zend_long retries = memc_user_data->store_retry_count; - MEMC_METHOD_FETCH_OBJECT; +retry_inc_dec: + if (!memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { + php_error_docref(NULL, E_WARNING, "Initial value is only supported with binary protocol"); + RETURN_FALSE; + } + if (by_key) { + if (incr) { + status = memcached_increment_with_initial_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, initial, expiry, &value); + } else { + status = memcached_decrement_with_initial_by_key(intern->memc, server_key->val, server_key->len, key->val, key->len, (unsigned int)offset, initial, expiry, &value); + } + } else { + if (incr) { + status = memcached_increment_with_initial(intern->memc, key->val, key->len, (unsigned int)offset, initial, expiry, &value); + } else { + status = memcached_decrement_with_initial(intern->memc, key->val, key->len, (unsigned int)offset, initial, expiry, &value); + } + } + if (s_should_retry_write(intern, status) && retries-- > 0) { + goto retry_inc_dec; + } + } - rc = memcached_version(intern->memc); - if (php_memc_handle_error(intern, rc) < 0) { + if (value == UINT64_MAX) { RETURN_FALSE; } - callbacks[0] = s_server_cursor_version_cb; - - array_init(return_value); - rc = memcached_server_cursor(intern->memc, callbacks, return_value, 1); - if (php_memc_handle_error(intern, rc) < 0) { - zval_dtor(return_value); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { RETURN_FALSE; } + + RETURN_LONG((long)value); } /* }}} */ -/* {{{ Memcached::getAllKeys() - Returns the keys stored on all the servers */ -static -memcached_return s_dump_keys_cb(const memcached_st *ptr, const char *key, size_t key_length, void *in_context) +/* {{{ Memcached::increment(string key [, int delta [, initial_value [, expiry time ] ] ]) + Increments the value for the given key by delta, defaulting to 1 */ +PHP_METHOD(Memcached, increment) { - zval *return_value = (zval*) in_context; - add_next_index_stringl(return_value, key, key_length); - - return MEMCACHED_SUCCESS; + php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1); } +/* }}} */ -PHP_METHOD(Memcached, getAllKeys) +/* {{{ Memcached::decrement(string key [, int delta [, initial_value [, expiry time ] ] ]) + Decrements the value for the given key by delta, defaulting to 1 */ +PHP_METHOD(Memcached, decrement) { - memcached_return rc; - memcached_dump_func callback[1]; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - callback[0] = s_dump_keys_cb; - MEMC_METHOD_FETCH_OBJECT; + php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0); +} +/* }}} */ - array_init(return_value); +/* {{{ Memcached::decrementByKey(string server_key, string key [, int delta [, initial_value [, expiry time ] ] ]) + Decrements by server the value for the given key by delta, defaulting to 1 */ +PHP_METHOD(Memcached, decrementByKey) +{ + php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0); +} +/* }}} */ - rc = memcached_dump(intern->memc, callback, return_value, 1); - if (php_memc_handle_error(intern, rc) < 0) { - zval_dtor(return_value); - RETURN_FALSE; - } +/* {{{ Memcached::increment(string server_key, string key [, int delta [, initial_value [, expiry time ] ] ]) + Increments by server the value for the given key by delta, defaulting to 1 */ +PHP_METHOD(Memcached, incrementByKey) +{ + php_memc_incdec_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 1); } /* }}} */ -/* {{{ Memcached::flush([ int delay ]) - Flushes the data on all the servers */ -static PHP_METHOD(Memcached, flush) +/* {{{ Memcached::addServer(string hostname, int port [, int weight ]) + Adds the given memcache server to the list */ +PHP_METHOD(Memcached, addServer) { - time_t delay = 0; + zend_string *host; + long port, weight = 0; memcached_return status; MEMC_METHOD_INIT_VARS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &delay) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl|l", &host, &port, &weight) == FAILURE) { return; } MEMC_METHOD_FETCH_OBJECT; intern->rescode = MEMCACHED_SUCCESS; - status = memcached_flush(intern->memc, delay); - if (php_memc_handle_error(intern, status) < 0) { +#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX < 0x01000002 + if (host->val[0] == '/') { /* unix domain socket */ + status = memcached_server_add_unix_socket_with_weight(intern->memc, host->val, weight); + } else if (memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_USE_UDP)) { + status = memcached_server_add_udp_with_weight(intern->memc, host->val, port, weight); + } else { + status = memcached_server_add_with_weight(intern->memc, host->val, port, weight); + } +#else + status = memcached_server_add_with_weight(intern->memc, host->val, port, weight); +#endif + + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Memcached::addServers(array servers) + Adds the given memcache servers to the server list */ +PHP_METHOD(Memcached, addServers) +{ + zval *servers; + zval *entry; + zval *z_host, *z_port, *z_weight = NULL; + uint32_t weight = 0; + HashPosition pos; + int entry_size, i = 0; + memcached_server_st *list = NULL; + memcached_return status; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/", &servers) == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); + + ZEND_HASH_FOREACH_VAL (Z_ARRVAL_P(servers), entry) { + if (Z_TYPE_P(entry) != IS_ARRAY) { + php_error_docref(NULL, E_WARNING, "server list entry #%d is not an array", i+1); + i++; + continue; + } + + entry_size = zend_hash_num_elements(Z_ARRVAL_P(entry)); + + if (entry_size > 1) { + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(entry), &pos); + + /* Check that we have a host */ + if ((z_host = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { + php_error_docref(NULL, E_WARNING, "could not get server host for entry #%d", i+1); + i++; + continue; + } + + /* Check that we have a port */ + if (zend_hash_move_forward_ex(Z_ARRVAL_P(entry), &pos) == FAILURE || + (z_port = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { + php_error_docref(NULL, E_WARNING, "could not get server port for entry #%d", i+1); + i++; + continue; + } + + convert_to_string_ex(z_host); + convert_to_long_ex(z_port); + + weight = 0; + if (entry_size > 2) { + /* Try to get weight */ + if (zend_hash_move_forward_ex(Z_ARRVAL_P(entry), &pos) == FAILURE || + (z_weight = zend_hash_get_current_data_ex(Z_ARRVAL_P(entry), &pos)) == NULL) { + php_error_docref(NULL, E_WARNING, "could not get server weight for entry #%d", i+1); + } + + convert_to_long_ex(z_weight); + weight = Z_LVAL_P(z_weight); + } + + list = memcached_server_list_append_with_weight(list, Z_STRVAL_P(z_host), + Z_LVAL_P(z_port), weight, &status); + + if (s_memc_status_handle_result_code(intern, status) == SUCCESS) { + i++; + continue; + } + } + i++; + /* catch-all for all errors */ + php_error_docref(NULL, E_WARNING, "could not add entry #%d to the server list", i+1); + } ZEND_HASH_FOREACH_END(); + + status = memcached_server_push(intern->memc, list); + memcached_server_list_free(list); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Memcached::getServerList() + Returns the list of the memcache servers in use */ +PHP_METHOD(Memcached, getServerList) +{ + memcached_server_function callbacks[1]; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + callbacks[0] = s_server_cursor_list_servers_cb; + array_init(return_value); + memcached_server_cursor(intern->memc, callbacks, return_value, 1); +} +/* }}} */ + +/* {{{ Memcached::getServerByKey(string server_key) + Returns the server identified by the given server key */ +PHP_METHOD(Memcached, getServerByKey) +{ + zend_string *server_key; + php_memcached_instance_st server_instance; + memcached_return error; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &server_key) == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + s_memc_set_status(intern, MEMCACHED_SUCCESS, 0); + + server_instance = memcached_server_by_key(intern->memc, server_key->val, server_key->len, &error); + if (server_instance == NULL) { + s_memc_status_handle_result_code(intern, error); + RETURN_FALSE; + } + + array_init(return_value); + add_assoc_string(return_value, "host", (char*) memcached_server_name(server_instance)); + add_assoc_long(return_value, "port", memcached_server_port(server_instance)); + add_assoc_long(return_value, "weight", 0); +} +/* }}} */ + +/* {{{ Memcached::resetServerList() + Reset the server list in use */ +PHP_METHOD(Memcached, resetServerList) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + memcached_servers_reset(intern->memc); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Memcached::quit() + Close any open connections */ +PHP_METHOD(Memcached, quit) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + memcached_quit(intern->memc); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ Memcached::flushBuffers() + Flush and senf buffered commands */ +PHP_METHOD(Memcached, flushBuffers) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + RETURN_BOOL(memcached_flush_buffers(intern->memc) == MEMCACHED_SUCCESS); +} +/* }}} */ + +#ifdef HAVE_LIBMEMCACHED_CHECK_CONFIGURATION +/* {{{ Memcached::getLastErrorMessage() + Returns the last error message that occurred */ +PHP_METHOD(Memcached, getLastErrorMessage) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + RETURN_STRING(memcached_last_error_message(intern->memc)); +} +/* }}} */ + +/* {{{ Memcached::getLastErrorCode() + Returns the last error code that occurred */ +PHP_METHOD(Memcached, getLastErrorCode) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + RETURN_LONG(memcached_last_error(intern->memc)); +} +/* }}} */ + +/* {{{ Memcached::getLastErrorErrno() + Returns the last error errno that occurred */ +PHP_METHOD(Memcached, getLastErrorErrno) +{ + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + RETURN_LONG(memcached_last_error_errno(intern->memc)); +} +/* }}} */ +#endif + +/* {{{ Memcached::getLastDisconnectedServer() + Returns the last disconnected server + Was added in 0.34 according to libmemcached's Changelog */ +PHP_METHOD(Memcached, getLastDisconnectedServer) +{ + php_memcached_instance_st server_instance; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + server_instance = memcached_server_get_last_disconnect(intern->memc); + if (server_instance == NULL) { + RETURN_FALSE; + } + + array_init(return_value); + add_assoc_string(return_value, "host", (char*) memcached_server_name(server_instance)); + add_assoc_long(return_value, "port", memcached_server_port(server_instance)); +} +/* }}} */ + + + +static +zend_bool s_long_value(const char *str, zend_long *value) +{ + char *end = (char *) str; + + errno = 0; + *value = strtol(str, &end, 10); + + if (errno || str == end || *end != '\0') { + return 0; + } + return 1; +} + +static +zend_bool s_double_value(const char *str, double *value) +{ + char *end = (char *) str; + + errno = 0; + *value = strtod(str, &end); + + if (errno || str == end || *end != '\0') { + return 0; + } + return 1; +} + +static +memcached_return s_stat_execute_cb (php_memcached_instance_st instance, const char *key, size_t key_length, const char *value, size_t value_length, void *context) +{ + char *server_key; + size_t server_key_len; + zend_long long_val; + double d_val; + char *buffer; + + zval *return_value = (zval *) context; + zval *server_values; + + server_key_len = spprintf (&server_key, 0, "%s:%d", memcached_server_name(instance), memcached_server_port(instance)); + server_values = zend_hash_str_find(Z_ARRVAL_P(return_value), server_key, server_key_len); + + if (!server_values) { + zval zv; + array_init(&zv); + + server_values = &zv; + add_assoc_zval_ex(return_value, server_key, server_key_len, server_values); + } + + spprintf (&buffer, 0, "%.*s", value_length, value); + + /* Check type */ + if (s_long_value (buffer, &long_val)) { + add_assoc_long(server_values, key, long_val); + } + else if (s_double_value (buffer, &d_val)) { + add_assoc_double(server_values, key, d_val); + } + else { + add_assoc_stringl_ex(server_values, key, key_length, value, value_length); + } + efree (buffer); + efree (server_key); + return MEMCACHED_SUCCESS; +} + +/* {{{ Memcached::getStats() + Returns statistics for the memcache servers */ +PHP_METHOD(Memcached, getStats) +{ + memcached_return status; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + array_init(return_value); + status = memcached_stat_execute(intern->memc, NULL, s_stat_execute_cb, return_value); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + zval_ptr_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Memcached::getVersion() + Returns the version of each memcached server in the pool */ +PHP_METHOD(Memcached, getVersion) +{ + memcached_return status; + memcached_server_function callbacks[1]; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + + status = memcached_version(intern->memc); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + RETURN_FALSE; + } + + callbacks[0] = s_server_cursor_version_cb; + + array_init(return_value); + status = memcached_server_cursor(intern->memc, callbacks, return_value, 1); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Memcached::getAllKeys() + Returns the keys stored on all the servers */ +static +memcached_return s_dump_keys_cb(const memcached_st *ptr, const char *key, size_t key_length, void *in_context) +{ + zval *return_value = (zval*) in_context; + add_next_index_stringl(return_value, key, key_length); + + return MEMCACHED_SUCCESS; +} + +PHP_METHOD(Memcached, getAllKeys) +{ + memcached_return rc; + memcached_dump_func callback[1]; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + callback[0] = s_dump_keys_cb; + MEMC_METHOD_FETCH_OBJECT; + + array_init(return_value); + + rc = memcached_dump(intern->memc, callback, return_value, 1); + if (s_memc_status_handle_result_code(intern, rc) == FAILURE) { + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Memcached::flush([ int delay ]) + Flushes the data on all the servers */ +static PHP_METHOD(Memcached, flush) +{ + time_t delay = 0; + memcached_return status; + MEMC_METHOD_INIT_VARS; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &delay) == FAILURE) { + return; + } + + MEMC_METHOD_FETCH_OBJECT; + intern->rescode = MEMCACHED_SUCCESS; + + status = memcached_flush(intern->memc, delay); + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { RETURN_FALSE; } @@ -2448,7 +2743,7 @@ static PHP_METHOD(Memcached, getOption) RETURN_LONG(memc_user_data->compression_type); case MEMC_OPT_COMPRESSION: - RETURN_BOOL(memc_user_data->compression); + RETURN_BOOL(memc_user_data->compression_enabled); case MEMC_OPT_PREFIX_KEY: { @@ -2507,7 +2802,7 @@ int php_memc_set_option(php_memc_object_t *intern, long option, zval *value) switch (option) { case MEMC_OPT_COMPRESSION: convert_to_long(value); - memc_user_data->compression = Z_LVAL_P(value) ? 1 : 0; + memc_user_data->compression_enabled = Z_LVAL_P(value) ? 1 : 0; break; case MEMC_OPT_COMPRESSION_TYPE: @@ -2557,7 +2852,7 @@ int php_memc_set_option(php_memc_object_t *intern, long option, zval *value) convert_to_long(value); rc = memcached_behavior_set(intern->memc, flag, (uint64_t) Z_LVAL_P(value)); - if (php_memc_handle_error(intern, rc) < 0) { + if (s_memc_status_handle_result_code(intern, rc) == FAILURE) { php_error_docref(NULL, E_WARNING, "error setting memcached option: %s", memcached_strerror (intern->memc, rc)); return 0; } @@ -2652,7 +2947,7 @@ int php_memc_set_option(php_memc_object_t *intern, long option, zval *value) } } - if (php_memc_handle_error(intern, rc) < 0) { + if (s_memc_status_handle_result_code(intern, rc) == FAILURE) { php_error_docref(NULL, E_WARNING, "error setting memcached option: %s", memcached_strerror (intern->memc, rc)); return 0; } @@ -2751,7 +3046,7 @@ PHP_METHOD(Memcached, setBucket) rc = memcached_bucket_set (intern->memc, server_map, forward_map, (uint32_t) server_map_len, replicas); - if (php_memc_handle_error(intern, rc) < 0) { + if (s_memc_status_handle_result_code(intern, rc) == FAILURE) { retval = 0;; } @@ -2790,546 +3085,286 @@ static PHP_METHOD(Memcached, setOptions) zval copy; ZVAL_DUP(©, value); - if (!php_memc_set_option(intern, (long) key_index, ©)) { - ok = 0; - } - - zval_dtor(©); - } - } ZEND_HASH_FOREACH_END(); - - RETURN_BOOL(ok); -} -/* }}} */ - -/* {{{ Memcached::setOption(int option, mixed value) - Sets the value for the given option constant */ -static PHP_METHOD(Memcached, setOption) -{ - long option; - zval *value; - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz/", &option, &value) == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - RETURN_BOOL(php_memc_set_option(intern, option, value)); -} -/* }}} */ - -#ifdef HAVE_MEMCACHED_SASL -/* {{{ Memcached::setSaslAuthData(string user, string pass) - Sets sasl credentials */ -static PHP_METHOD(Memcached, setSaslAuthData) -{ - MEMC_METHOD_INIT_VARS; - memcached_return status; - zend_string *user, *pass; - - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &user, &pass) == FAILURE) { - return; - } - - if (!php_memc_init_sasl_if_needed()) { - RETURN_FALSE; - } - - MEMC_METHOD_FETCH_OBJECT; - - if (!memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { - php_error_docref(NULL, E_WARNING, "SASL is only supported with binary protocol"); - RETURN_FALSE; - } - memc_user_data->has_sasl_data = 1; - status = memcached_set_sasl_auth_data(intern->memc, user->val, pass->val); - - if (php_memc_handle_error(intern, status) < 0) { - RETURN_FALSE; - } - RETURN_TRUE; -} -/* }}} */ -#endif /* HAVE_MEMCACHED_SASL */ - -/* {{{ Memcached::getResultCode() - Returns the result code from the last operation */ -static PHP_METHOD(Memcached, getResultCode) -{ - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - RETURN_LONG(intern->rescode); -} -/* }}} */ - -/* {{{ Memcached::getResultMessage() - Returns the result message from the last operation */ -static PHP_METHOD(Memcached, getResultMessage) -{ - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - switch (intern->rescode) { - case MEMC_RES_PAYLOAD_FAILURE: - RETURN_STRING("PAYLOAD FAILURE"); - break; - - case MEMCACHED_ERRNO: - case MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE: - case MEMCACHED_UNKNOWN_READ_FAILURE: - if (intern->memc_errno) { - zend_string *str = strpprintf(0, "%s: %s", - memcached_strerror(intern->memc, (memcached_return)intern->rescode), strerror(intern->memc_errno)); - RETURN_STR(str); - } - /* Fall through */ - default: - RETURN_STRING(memcached_strerror(intern->memc, (memcached_return)intern->rescode)); - break; - } - -} -/* }}} */ - -/* {{{ Memcached::isPersistent() - Returns the true if instance uses a persistent connection */ -static PHP_METHOD(Memcached, isPersistent) -{ - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - RETURN_BOOL(memc_user_data->is_persistent); -} -/* }}} */ - -/* {{{ Memcached::isPristine() - Returns the true if instance is recently created */ -static PHP_METHOD(Memcached, isPristine) -{ - MEMC_METHOD_INIT_VARS; - - if (zend_parse_parameters_none() == FAILURE) { - return; - } - - MEMC_METHOD_FETCH_OBJECT; - - RETURN_BOOL(intern->is_pristine); -} -/* }}} */ - -/**************************************** - Internal support code -****************************************/ - -/* {{{ constructor/destructor */ -static -void php_memc_destroy(memcached_st *memc, php_memc_user_data_t *memc_user_data) -{ -#if HAVE_MEMCACHED_SASL - if (memc_user_data->has_sasl_data) { - memcached_destroy_sasl_auth_data(memc); - } -#endif - - memcached_set_memory_allocators(memc, NULL, NULL, NULL, NULL, NULL); - memcached_free(memc); - pefree(memc_user_data, memc_user_data->is_persistent); -} - -static -void php_memc_object_free_storage(zend_object *object) -{ - php_memc_object_t *intern = php_memc_fetch_object(object); - - if (intern->memc) { - php_memc_user_data_t *memc_user_data = memcached_get_user_data(intern->memc); - - if (!memc_user_data->is_persistent) { - php_memc_destroy(intern->memc, memc_user_data); - } - } - - intern->memc = NULL; - zend_object_std_dtor(&intern->zo); -} - -static -zend_object *php_memc_object_new(zend_class_entry *ce) -{ - php_memc_object_t *intern = ecalloc(1, sizeof(php_memc_object_t) + zend_object_properties_size(ce)); - - zend_object_std_init(&intern->zo, ce); - object_properties_init(&intern->zo, ce); - - intern->zo.handlers = &memcached_object_handlers; - return &intern->zo; -} - -#ifdef HAVE_MEMCACHED_PROTOCOL -static -void php_memc_server_free_storage(php_memc_server_t *intern) -{ - zend_object_std_dtor(&intern->zo); - efree (intern); -} - -zend_object_value php_memc_server_new(zend_class_entry *ce) -{ - zend_object_value retval; - php_memc_server_t *intern; - zval *tmp; - - intern = ecalloc(1, sizeof(php_memc_server_t)); - zend_object_std_init(&intern->zo, ce); - object_properties_init(&intern->zo, ce); - - intern->handler = php_memc_proto_handler_new (); - - retval.handle = zend_objects_store_put(intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)php_memc_server_free_storage, NULL); - retval.handlers = &memcached_server_object_handlers; - - return retval; -} -#endif - -ZEND_RSRC_DTOR_FUNC(php_memc_dtor) -{ - if (res->ptr) { - memcached_st *memc = (memcached_st *) res->ptr; - php_memc_destroy(memc, memcached_get_user_data(memc)); - res->ptr = NULL; - } -} - -/* }}} */ - -/* {{{ internal API functions */ -static -memcached_return s_server_cursor_list_servers_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context) -{ - zval array; - zval *return_value = (zval *) in_context; - - array_init(&array); - add_assoc_string(&array, "host", memcached_server_name(instance)); - add_assoc_long(&array, "port", memcached_server_port(instance)); - add_assoc_string(&array, "type", memcached_server_type(instance)); - /* - * API does not allow to get at this field. - add_assoc_long(array, "weight", instance->weight); - */ + if (!php_memc_set_option(intern, (long) key_index, ©)) { + ok = 0; + } - add_next_index_zval(return_value, &array); - return MEMCACHED_SUCCESS; + zval_dtor(©); + } + } ZEND_HASH_FOREACH_END(); + + RETURN_BOOL(ok); } +/* }}} */ -static -memcached_return s_server_cursor_version_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context) +/* {{{ Memcached::setOption(int option, mixed value) + Sets the value for the given option constant */ +static PHP_METHOD(Memcached, setOption) { - char *address, *version; - size_t address_len, version_len; - - zval *return_value = (zval *) in_context; + long option; + zval *value; + MEMC_METHOD_INIT_VARS; -#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX >= 0x01000009 - version_len = spprintf(&version, sizeof(version), "%d.%d.%d", - memcached_server_major_version(instance), - memcached_server_minor_version(instance), - memcached_server_micro_version(instance)); -#else - version_len = spprintf(&version, sizeof(version) - 1, "%d.%d.%d", - instance->major_version, - instance->minor_version, - instance->micro_version); -#endif + if (zend_parse_parameters(ZEND_NUM_ARGS(), "lz/", &option, &value) == FAILURE) { + return; + } - address_len = spprintf(&address, 0, "%s:%d", memcached_server_name(instance), memcached_server_port(instance) - 1); - add_assoc_stringl_ex(return_value, address, address_len, version, version_len); + MEMC_METHOD_FETCH_OBJECT; - efree(address); - efree(version); - return MEMCACHED_SUCCESS; + RETURN_BOOL(php_memc_set_option(intern, option, value)); } +/* }}} */ -static int php_memc_handle_error(php_memc_object_t *intern, memcached_return status) +#ifdef HAVE_MEMCACHED_SASL +/* {{{ Memcached::setSaslAuthData(string user, string pass) + Sets sasl credentials */ +static PHP_METHOD(Memcached, setSaslAuthData) { - int result = 0; + MEMC_METHOD_INIT_VARS; + memcached_return status; + zend_string *user, *pass; - switch (status) { - case MEMCACHED_SUCCESS: - case MEMCACHED_STORED: - case MEMCACHED_DELETED: - case MEMCACHED_STAT: - result = 0; - intern->memc_errno = 0; - break; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &user, &pass) == FAILURE) { + return; + } - case MEMCACHED_END: - case MEMCACHED_BUFFERED: - intern->rescode = status; - intern->memc_errno = 0; - result = 0; - break; + if (!php_memc_init_sasl_if_needed()) { + RETURN_FALSE; + } - case MEMCACHED_SOME_ERRORS: - intern->rescode = status; - intern->memc_errno = memcached_last_error_errno(intern->memc); - result = 0; - break; + MEMC_METHOD_FETCH_OBJECT; - default: - intern->rescode = status; - intern->memc_errno = memcached_last_error_errno(intern->memc); - result = -1; - break; + if (!memcached_behavior_get(intern->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL)) { + php_error_docref(NULL, E_WARNING, "SASL is only supported with binary protocol"); + RETURN_FALSE; } + memc_user_data->has_sasl_data = 1; + status = memcached_set_sasl_auth_data(intern->memc, user->val, pass->val); - return result; + if (s_memc_status_handle_result_code(intern, status) == FAILURE) { + RETURN_FALSE; + } + RETURN_TRUE; } +/* }}} */ +#endif /* HAVE_MEMCACHED_SASL */ -static -zend_bool s_compress_value (enum memcached_compression_type compression_type, zend_string **payload_in, uint32_t *flags) +/* {{{ Memcached::getResultCode() + Returns the result code from the last operation */ +static PHP_METHOD(Memcached, getResultCode) { - /* status */ - zend_bool compress_status = 0; - zend_string *payload = *payload_in; + MEMC_METHOD_INIT_VARS; - /* Additional 5% for the data */ - size_t buffer_size = (size_t) (((double) payload->len * 1.05) + 1.0); - char *buffer = emalloc(buffer_size); + if (zend_parse_parameters_none() == FAILURE) { + return; + } - /* Store compressed size here */ - size_t compressed_size = 0; - uint32_t original_size = payload->len; + MEMC_METHOD_FETCH_OBJECT; - switch (compression_type) { + RETURN_LONG(intern->rescode); +} +/* }}} */ - case COMPRESSION_TYPE_FASTLZ: - { - compressed_size = fastlz_compress(payload->val, payload->len, buffer); +/* {{{ Memcached::getResultMessage() + Returns the result message from the last operation */ +static PHP_METHOD(Memcached, getResultMessage) +{ + MEMC_METHOD_INIT_VARS; - if (compressed_size > 0) { - compress_status = 1; - MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_FASTLZ); - } - } - break; + if (zend_parse_parameters_none() == FAILURE) { + return; + } - case COMPRESSION_TYPE_ZLIB: - { - compressed_size = buffer_size; - int status = compress((Bytef *) buffer, &compressed_size, (Bytef *) payload->val, payload->len); + MEMC_METHOD_FETCH_OBJECT; - if (status == Z_OK) { - compress_status = 1; - MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_ZLIB); - } - } + switch (intern->rescode) { + case MEMC_RES_PAYLOAD_FAILURE: + RETURN_STRING("PAYLOAD FAILURE"); break; + case MEMCACHED_ERRNO: + case MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE: + case MEMCACHED_UNKNOWN_READ_FAILURE: + if (intern->memc_errno) { + zend_string *str = strpprintf(0, "%s: %s", + memcached_strerror(intern->memc, (memcached_return)intern->rescode), strerror(intern->memc_errno)); + RETURN_STR(str); + } + /* Fall through */ default: - compress_status = 0; + RETURN_STRING(memcached_strerror(intern->memc, (memcached_return)intern->rescode)); break; } - if (!compress_status) { - php_error_docref(NULL, E_WARNING, "could not compress value"); - MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); - efree (buffer); - return 0; - } +} +/* }}} */ - /* This means the value was too small to be compressed, still a success */ - if (compressed_size > (payload->len * MEMC_G(compression_factor))) { - efree(buffer); - MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); - return 1; - } +/* {{{ Memcached::isPersistent() + Returns the true if instance uses a persistent connection */ +static PHP_METHOD(Memcached, isPersistent) +{ + MEMC_METHOD_INIT_VARS; - payload = zend_string_realloc(payload, compressed_size + sizeof(uint32_t), 0); + if (zend_parse_parameters_none() == FAILURE) { + return; + } - /* Copy the uin32_t at the beginning */ - memcpy(payload->val, &original_size, sizeof(uint32_t)); - memcpy(payload->val + sizeof (uint32_t), buffer, compressed_size); - efree(buffer); + MEMC_METHOD_FETCH_OBJECT; - zend_string_forget_hash_val(payload); - *payload_in = payload; - return 1; + RETURN_BOOL(memc_user_data->is_persistent); } +/* }}} */ -static -zend_bool s_serialize_value (enum memcached_serializer serializer, zval *value, smart_str *buf, uint32_t *flags) +/* {{{ Memcached::isPristine() + Returns the true if instance is recently created */ +static PHP_METHOD(Memcached, isPristine) { - switch (serializer) { + MEMC_METHOD_INIT_VARS; - /* - Igbinary serialization - */ -#ifdef HAVE_MEMCACHED_IGBINARY - case SERIALIZER_IGBINARY: - { - uint8_t *buffer; - size_t buffer_len; + if (zend_parse_parameters_none() == FAILURE) { + return; + } - if (igbinary_serialize(&buffer, &buffer_len, value) != 0) { - php_error_docref(NULL, E_WARNING, "could not serialize value with igbinary"); - return 0; - } - smart_str_appendl (buf, buffer, buffer_len); - efree(buffer); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_IGBINARY); - } - break; -#endif + MEMC_METHOD_FETCH_OBJECT; - /* - JSON serialization - */ -#ifdef HAVE_JSON_API - case SERIALIZER_JSON: - case SERIALIZER_JSON_ARRAY: - { - php_json_encode(buf, value, 0); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_JSON); - } - break; -#endif + RETURN_BOOL(intern->is_pristine); +} +/* }}} */ - /* - msgpack serialization - */ -#ifdef HAVE_MEMCACHED_MSGPACK - case SERIALIZER_MSGPACK: - php_msgpack_serialize(buf, value); - if (!buf->s) { - php_error_docref(NULL, E_WARNING, "could not serialize value with msgpack"); - return 0; - } - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_MSGPACK); - break; +/**************************************** + Internal support code +****************************************/ + +/* {{{ constructor/destructor */ +static +void php_memc_destroy(memcached_st *memc, php_memc_user_data_t *memc_user_data) +{ +#if HAVE_MEMCACHED_SASL + if (memc_user_data->has_sasl_data) { + memcached_destroy_sasl_auth_data(memc); + } #endif - /* - PHP serialization - */ - default: - { - php_serialize_data_t var_hash; - PHP_VAR_SERIALIZE_INIT(var_hash); - php_var_serialize(buf, value, &var_hash); - PHP_VAR_SERIALIZE_DESTROY(var_hash); + memcached_free(memc); + pefree(memc_user_data, memc_user_data->is_persistent); +} - if (!buf->s) { - php_error_docref(NULL, E_WARNING, "could not serialize value"); - return 0; - } - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_SERIALIZED); +static +void php_memc_object_free_storage(zend_object *object) +{ + php_memc_object_t *intern = php_memc_fetch_object(object); + + if (intern->memc) { + php_memc_user_data_t *memc_user_data = memcached_get_user_data(intern->memc); + + if (!memc_user_data->is_persistent) { + php_memc_destroy(intern->memc, memc_user_data); } - break; } - /* Check for exceptions caused by serializers */ - if (EG(exception) && buf->s->len) { - return 0; - } - return 1; + intern->memc = NULL; + zend_object_std_dtor(&intern->zo); } static -zend_string *s_zval_to_payload(zval *value, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type) +zend_object *php_memc_object_new(zend_class_entry *ce) { - zend_string *payload; + php_memc_object_t *intern = ecalloc(1, sizeof(php_memc_object_t) + zend_object_properties_size(ce)); - switch (Z_TYPE_P(value)) { + zend_object_std_init(&intern->zo, ce); + object_properties_init(&intern->zo, ce); - case IS_STRING: - payload = zval_get_string(value); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_STRING); - MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED); - break; + intern->zo.handlers = &memcached_object_handlers; + return &intern->zo; +} - case IS_LONG: - { - smart_str buffer = {0}; - smart_str_append_long (&buffer, Z_LVAL_P(value)); - smart_str_0(&buffer); - payload = buffer.s; +#ifdef HAVE_MEMCACHED_PROTOCOL +static +void php_memc_server_free_storage(php_memc_server_t *intern) +{ + zend_object_std_dtor(&intern->zo); + efree (intern); +} - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_LONG); - } - break; +zend_object_value php_memc_server_new(zend_class_entry *ce) +{ + zend_object_value retval; + php_memc_server_t *intern; + zval *tmp; - case IS_DOUBLE: - { - char buffer[40]; - php_memcached_g_fmt(buffer, Z_DVAL_P(value)); - payload = zend_string_init (buffer, strlen (buffer), 0); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_DOUBLE); - } - break; + intern = ecalloc(1, sizeof(php_memc_server_t)); + zend_object_std_init(&intern->zo, ce); + object_properties_init(&intern->zo, ce); - case IS_TRUE: - payload = zend_string_init ("1", 1, 0); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); - break; + intern->handler = php_memc_proto_handler_new (); - case IS_FALSE: - payload = zend_string_alloc (0, 0); - MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); - break; + retval.handle = zend_objects_store_put(intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, (zend_objects_free_object_storage_t)php_memc_server_free_storage, NULL); + retval.handlers = &memcached_server_object_handlers; - default: - { - smart_str buffer = {0}; + return retval; +} +#endif - if (!s_serialize_value (serializer, value, &buffer, flags)) { - smart_str_free(&buffer); - return NULL; - } - payload = buffer.s; - MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED); - } - break; +ZEND_RSRC_DTOR_FUNC(php_memc_dtor) +{ + if (res->ptr) { + memcached_st *memc = (memcached_st *) res->ptr; + php_memc_destroy(memc, memcached_get_user_data(memc)); + res->ptr = NULL; } - zend_string_forget_hash_val(payload); +} - /* turn off compression for values below the threshold */ - if (payload->len == 0 || payload->len < MEMC_G(compression_threshold)) { - MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); - } +/* }}} */ - /* If we have compression flag, compress the value */ - if (MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED)) { - /* status */ - if (!s_compress_value (compression_type, &payload, flags)) { - zend_string_release(payload); - return NULL; - } - } +/* {{{ internal API functions */ +static +memcached_return s_server_cursor_list_servers_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context) +{ + zval array; + zval *return_value = (zval *) in_context; - return payload; + array_init(&array); + add_assoc_string(&array, "host", memcached_server_name(instance)); + add_assoc_long(&array, "port", memcached_server_port(instance)); + add_assoc_string(&array, "type", memcached_server_type(instance)); + /* + * API does not allow to get at this field. + add_assoc_long(array, "weight", instance->weight); + */ + + add_next_index_zval(return_value, &array); + return MEMCACHED_SUCCESS; +} + +static +memcached_return s_server_cursor_version_cb(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context) +{ + char *address, *version; + size_t address_len, version_len; + + zval *return_value = (zval *) in_context; + +#if defined(LIBMEMCACHED_VERSION_HEX) && LIBMEMCACHED_VERSION_HEX >= 0x01000009 + version_len = spprintf(&version, sizeof(version), "%d.%d.%d", + memcached_server_major_version(instance), + memcached_server_minor_version(instance), + memcached_server_micro_version(instance)); +#else + version_len = spprintf(&version, sizeof(version) - 1, "%d.%d.%d", + instance->major_version, + instance->minor_version, + instance->micro_version); +#endif + + address_len = spprintf(&address, 0, "%s:%d", memcached_server_name(instance), memcached_server_port(instance) - 1); + add_assoc_stringl_ex(return_value, address, address_len, version, version_len); + + efree(address); + efree(version); + return MEMCACHED_SUCCESS; } + static zend_string *s_decompress_value (const char *payload, size_t payload_len, uint32_t flags) { @@ -3559,145 +3594,6 @@ zend_class_entry *php_memc_get_exception_base(int root) #endif } -static -memcached_return s_invoke_cache_callback(zval *zobject, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string *key, zval *value) -{ - memcached_return rc = MEMCACHED_SUCCESS; - int result; - - zval params[4]; - zval retval; - zval zv_key, ref_val; - zval ref_expiration, zv_expiration; - - /* Prepare params */ - ZVAL_STR(&zv_key, key); - - ZVAL_NULL(&ref_val); - ZVAL_NULL(&zv_expiration); - - ZVAL_NEW_REF(&ref_val, value); - ZVAL_NEW_REF(&ref_expiration, &zv_expiration); - - ZVAL_COPY(¶ms[0], zobject); - ZVAL_COPY(¶ms[1], &zv_key); - ZVAL_COPY_VALUE(¶ms[2], &ref_val); - ZVAL_COPY_VALUE(¶ms[3], &ref_expiration); - - fci->retval = &retval; - fci->params = params; - fci->param_count = 4; - - result = zend_call_function(fci, fcc); - ZVAL_DUP(value, Z_REFVAL(ref_val)); - - if (result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) { - - if (zend_is_true(&retval)) { - time_t expiration; - zend_string *payload; - uint32_t flags = 0; - php_memc_object_t *intern; - php_memc_user_data_t *memc_user_data; - - intern = Z_MEMC_OBJ_P(zobject); - memc_user_data = memcached_get_user_data(intern->memc); - - expiration = zval_get_long(Z_REFVAL(ref_expiration)); - payload = s_zval_to_payload(value, &flags, memc_user_data->serializer, memc_user_data->compression_type); - - if (payload == NULL) { - rc = (memcached_return) MEMC_RES_PAYLOAD_FAILURE; - } else { - if (memc_user_data->set_udf_flags >= 0) { - MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) memc_user_data->set_udf_flags)); - } - rc = memcached_set(intern->memc, key->val, key->len, payload->val, payload->len, expiration, flags); - if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_BUFFERED) { - rc = MEMCACHED_SOME_ERRORS; - } - zend_string_release(payload); - } - } else { - rc = MEMCACHED_NOTFOUND; - zval_dtor(value); - ZVAL_NULL(value); - } - } - - if (!Z_ISUNDEF(retval)) { - zval_ptr_dtor(&retval); - } - - zval_ptr_dtor(&zv_key); - zval_ptr_dtor(&ref_val); - zval_ptr_dtor(&zv_expiration); - zval_ptr_dtor(&ref_expiration); - zval_ptr_dtor(zobject); - return rc; -} - - -static -int s_invoke_result_callback(zval *zmemc_obj, zend_fcall_info *fci, zend_fcall_info_cache *fcc, memcached_result_st *result) -{ - const char *res_key = NULL; - size_t res_key_len = 0; - const char *payload = NULL; - size_t payload_len = 0; - zval value; - zval retval; - uint64_t cas = 0; - zval z_result; - uint32_t flags = 0; - int rc = 0; - php_memc_object_t *intern = NULL; - - fci->retval = &retval; - fci->param_count = 2; - - payload = memcached_result_value(result); - payload_len = memcached_result_length(result); - flags = memcached_result_flags(result); - res_key = memcached_result_key_value(result); - res_key_len = memcached_result_key_length(result); - cas = memcached_result_cas(result); - - intern = Z_MEMC_OBJ_P(zmemc_obj); - - if (!s_memcached_result_to_zval(intern->memc, result, &value)) { - intern->rescode = MEMC_RES_PAYLOAD_FAILURE; - return -1; - } - - array_init(&z_result); - add_assoc_stringl_ex(&z_result, ZEND_STRL("key"), (char *)res_key, res_key_len); - add_assoc_zval_ex(&z_result, ZEND_STRL("value"), &value); - if (cas != 0) { - add_assoc_double_ex(&z_result, ZEND_STRL("cas"), (double)cas); - } - if (MEMC_VAL_GET_USER_FLAGS(flags) != 0) { - add_assoc_long_ex(&z_result, ZEND_STRL("flags"), MEMC_VAL_GET_USER_FLAGS(flags)); - } - - ZVAL_UNDEF(&retval); - zend_fcall_info_argn(fci, 2, zmemc_obj, &z_result); - - if (zend_call_function(fci, fcc) == FAILURE) { - php_error_docref(NULL, E_WARNING, "could not invoke result callback"); - rc = -1; - } - - if (Z_TYPE(retval) != IS_UNDEF) { - zval_ptr_dtor(&retval); - } - - zend_fcall_info_args_clear(fci, 1); - zval_ptr_dtor(&z_result); - - return rc; -} -/* }}} */ #ifdef HAVE_MEMCACHED_PROTOCOL @@ -4181,7 +4077,6 @@ PHP_GINIT_FUNCTION(php_memcached) php_memcached_globals->session.lock_wait_min = 1000; php_memcached_globals->session.lock_retries = 5; php_memcached_globals->session.lock_expiration = 30; - php_memcached_globals->session.compression_enabled = 1; php_memcached_globals->session.binary_protocol_enabled = 1; php_memcached_globals->session.consistent_hash_enabled = 1; php_memcached_globals->session.number_of_replicas = 0; @@ -4196,10 +4091,10 @@ PHP_GINIT_FUNCTION(php_memcached) #endif php_memcached_globals->memc.serializer_name = NULL; - php_memcached_globals->memc.serializer = SERIALIZER_DEFAULT; - php_memcached_globals->memc.compression_type = NULL; + php_memcached_globals->memc.serializer_type = SERIALIZER_DEFAULT; + php_memcached_globals->memc.compression_name = NULL; php_memcached_globals->memc.compression_threshold = 2000; - php_memcached_globals->memc.compression_type_real = COMPRESSION_TYPE_FASTLZ; + php_memcached_globals->memc.compression_type = COMPRESSION_TYPE_FASTLZ; php_memcached_globals->memc.compression_factor = 1.30; php_memcached_globals->memc.store_retry_count = 2; diff --git a/php_memcached_private.h b/php_memcached_private.h index c6c0b77c..d19fe974 100644 --- a/php_memcached_private.h +++ b/php_memcached_private.h @@ -75,13 +75,24 @@ typedef unsigned long int uint32_t; /**************************************** Structures and definitions ****************************************/ -enum memcached_serializer { - SERIALIZER_PHP = 1, - SERIALIZER_IGBINARY = 2, - SERIALIZER_JSON = 3, +typedef enum { + SERIALIZER_PHP = 1, + SERIALIZER_IGBINARY = 2, + SERIALIZER_JSON = 3, SERIALIZER_JSON_ARRAY = 4, - SERIALIZER_MSGPACK = 5, -}; + SERIALIZER_MSGPACK = 5 +} php_memc_serializer_type; + +typedef enum { + COMPRESSION_TYPE_ZLIB = 1, + COMPRESSION_TYPE_FASTLZ = 2 +} php_memc_compression_type; + +typedef struct { + const char *name; + php_memc_serializer_type type; +} php_memc_serializer; + #ifdef HAVE_MEMCACHED_IGBINARY #define SERIALIZER_DEFAULT SERIALIZER_IGBINARY #define SERIALIZER_DEFAULT_NAME "igbinary" @@ -160,14 +171,14 @@ ZEND_BEGIN_MODULE_GLOBALS(php_memcached) struct { char *serializer_name; - char *compression_type; + char *compression_name; zend_long compression_threshold; double compression_factor; zend_long store_retry_count; /* Converted values*/ - enum memcached_serializer serializer; - zend_long compression_type_real; + php_memc_serializer_type serializer_type; + php_memc_compression_type compression_type; /* Whether we have initialised sasl for this process */ zend_bool sasl_initialised; diff --git a/tests/getdelayed.phpt b/tests/getdelayed.phpt index 7479e97b..c07acada 100644 --- a/tests/getdelayed.phpt +++ b/tests/getdelayed.phpt @@ -22,42 +22,61 @@ foreach ($data as $k => $v) { function myfunc() { $datas = func_get_args(); if (isset($datas[1])) { - unset($datas[1]['cas']); var_dump($datas[1]); } } -$m->getDelayed(array_keys($data), false, 'myfunc'); +$m->getDelayed(array_keys($data), true, 'myfunc'); ?> ---EXPECT-- -array(2) { +--EXPECTF-- +array(4) { ["key"]=> string(3) "foo" ["value"]=> string(8) "foo-data" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) } -array(2) { +array(4) { ["key"]=> string(3) "bar" ["value"]=> string(8) "bar-data" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) } -array(2) { +array(4) { ["key"]=> string(3) "baz" ["value"]=> string(8) "baz-data" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) } -array(2) { +array(4) { ["key"]=> string(3) "lol" ["value"]=> string(8) "lol-data" + ["cas"]=> + int(%d) + ["flags"]=> + int(0) } -array(2) { +array(4) { ["key"]=> string(3) "kek" ["value"]=> string(8) "kek-data" -} + ["cas"]=> + int(%d) + ["flags"]=> + int(0) +} \ No newline at end of file diff --git a/tests/incrdecr_invalid_key.phpt b/tests/incrdecr_invalid_key.phpt index 8d4ab671..cd8c6b9b 100644 --- a/tests/incrdecr_invalid_key.phpt +++ b/tests/incrdecr_invalid_key.phpt @@ -9,6 +9,7 @@ $m = memc_get_instance (); var_dump($m->increment('', 1)); var_dump($m->decrement('', 1)); +?> --EXPECT-- bool(false) bool(false)