diff -NaupB module-2.2.2/memcache.c module-tag.2.2.2/memcache.c
--- module-2.2.2/memcache.c	2008-02-13 15:24:49.000000000 +0800
+++ module-tag.2.2.2/memcache.c	2008-02-13 15:24:49.000000000 +0800
@@ -74,6 +74,8 @@ zend_function_entry memcache_functions[]
 	PHP_FE(memcache_decrement,		NULL)
 	PHP_FE(memcache_close,			NULL)
 	PHP_FE(memcache_flush,			NULL)
+	PHP_FE(memcache_tag_add,		NULL)		//tag related
+	PHP_FE(memcache_tag_delete,		NULL)		//tag related
 	{NULL, NULL, NULL}
 };
 
@@ -96,6 +98,8 @@ static zend_function_entry php_memcache_
 	PHP_FALIAS(decrement,		memcache_decrement,			NULL)
 	PHP_FALIAS(close,			memcache_close,				NULL)
 	PHP_FALIAS(flush,			memcache_flush,				NULL)
+	PHP_FALIAS(tag_add,			memcache_tag_add,			NULL)		//tag related
+	PHP_FALIAS(tag_delete,		memcache_tag_delete,		NULL)		//tag related
 	{NULL, NULL, NULL}
 };
 
@@ -512,6 +516,34 @@ static int mmc_server_store(mmc_t *mmc, 
 }
 /* }}} */
 
+//tag related
+static int mmc_server_tag_add(mmc_t *mmc, const char *request, int request_len TSRMLS_DC) { /* {{{ */
+	int response_len;
+
+	if (php_stream_write(mmc->stream, request, request_len) != request_len) {
+		mmc_server_seterror(mmc, "Failed sending command and value to stream", 0);
+		return -1;
+	}
+	
+	if ((response_len = mmc_readline(mmc TSRMLS_CC)) < 0) {
+		return -1;
+	}
+
+	if(mmc_str_left(mmc->inbuf, "TAG_STORED", response_len, sizeof("TAG_STORED") - 1)) {
+		return 1;
+	}
+
+	/* return FALSE without failover */
+	if (mmc_str_left(mmc->inbuf, "SERVER_ERROR out of memory or param error", response_len, sizeof("SERVER_ERROR out of memory or param error") - 1)) {
+		return 0;
+	}
+	
+	mmc_server_received_error(mmc, response_len);
+	return -1;
+}
+/* }}} */
+//tag related
+
 int mmc_prepare_key_ex(const char *key, unsigned int key_len, char *result, unsigned int *result_len TSRMLS_DC)  /* {{{ */
 {
 	unsigned int i;
@@ -786,6 +818,45 @@ int mmc_pool_store(mmc_pool_t *pool, con
 }
 /* }}} */
 
+//tag related
+#define TA_BEGIN 	0x00
+#define TA_MID		0x01
+#define TA_END		0x02
+void mmc_pool_tag_add(mmc_pool_t *pool, const char *str, int str_len, int pos TSRMLS_DC) /* {{{ */
+{
+	char *str_copy = NULL;
+
+	if (str_len > MMC_KEY_MAX_SIZE) {
+		str = str_copy = estrndup(str, MMC_KEY_MAX_SIZE);
+		str_len = MMC_KEY_MAX_SIZE;
+	}
+
+	if (TA_BEGIN == pos) {
+		memset(&pool->request, 0, sizeof(pool->request));
+
+		smart_str_appendl(&(pool->request), "tag_add", sizeof("tag_add")-1);
+
+		smart_str_appendl(&(pool->request), " ", 1);
+		smart_str_appendl(&(pool->request), str, str_len);
+	}
+	else if (TA_MID == pos) {
+		smart_str_appendl(&(pool->request), " ", 1);
+		smart_str_appendl(&(pool->request), str, str_len);
+	}
+	else if (TA_END == pos) {
+		smart_str_appendl(&(pool->request), "\r\n", sizeof("\r\n")-1);
+	}
+
+	if (str_copy != NULL) {
+		efree(str_copy);
+		str_copy = NULL;
+	}
+
+	return;
+}
+/* }}} */
+//tag related
+
 static int mmc_compress(char **result, unsigned long *result_len, const char *data, int data_len TSRMLS_DC) /* {{{ */
 {
 	int status, level = MEMCACHE_G(compression_level);
@@ -1473,6 +1544,43 @@ int mmc_delete(mmc_t *mmc, const char *k
 }
 /* }}} */
 
+//tag related
+int mmc_tag_delete(mmc_t *mmc, const char *tag, int tag_len TSRMLS_DC) /* {{{ */
+{
+	char *command;
+	int command_len, response_len;
+
+	command_len = spprintf(&command, 0, "tag_delete %s", tag);
+
+	MMC_DEBUG(("mmc_tag_delete: trying to tag_delete '%s'", tag));
+
+	if (mmc_sendcmd(mmc, command, command_len TSRMLS_CC) < 0) {
+		efree(command);
+		return -1;
+	}
+	efree(command);
+
+	if ((response_len = mmc_readline(mmc TSRMLS_CC)) < 0){
+		MMC_DEBUG(("failed to read the server's response"));
+		return -1;
+	}
+
+	MMC_DEBUG(("mmc_tag_delete: server's response is '%s'", mmc->inbuf));
+
+	if(mmc_str_left(mmc->inbuf,"TAG_DELETED", response_len, sizeof("TAG_DELETED") - 1)) {
+		return 1;
+	}
+
+	if(mmc_str_left(mmc->inbuf,"TAG_NOT_FOUND", response_len, sizeof("TAG_NOT_FOUND") - 1)) {
+		return 0;
+	}
+
+	mmc_server_received_error(mmc, response_len);
+	return -1;
+}
+/* }}} */
+//tag related
+
 static int mmc_flush(mmc_t *mmc, int timestamp TSRMLS_DC) /* {{{ */
 {
 	char *command;
@@ -2340,6 +2448,165 @@ PHP_FUNCTION(memcache_delete)
 }
 /* }}} */
 
+//tag related
+/* {{{ protozbool memcache_tag_add( object memcache, string tag, mixed var )
+   Add keys to an tag. Tag may exist or not */
+PHP_FUNCTION(memcache_tag_add)
+{
+	mmc_pool_t *pool = NULL;
+	mmc_t *mmc = NULL;
+	zval *mmc_object = getThis(), *tags = NULL, *keys = NULL, **key = NULL;
+	char tag_name[MMC_KEY_MAX_SIZE];
+	unsigned int tag_name_len = 0;
+	char key_name[MMC_KEY_MAX_SIZE];
+	unsigned int key_name_len = 0;
+	HashPosition pos;
+	int i = 0;
+
+	if (mmc_object == NULL) {
+		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ozz", &mmc_object, memcache_class_entry_ptr, &tags, &keys) == FAILURE) {
+			return;
+		}
+	}
+	else {
+		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &tags, &keys) == FAILURE) {
+			return;
+		}
+	}
+
+	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
+		RETURN_FALSE;
+	}
+
+	if (mmc_prepare_key(tags, tag_name, &tag_name_len TSRMLS_CC) != MMC_OK) {
+		RETURN_FALSE;
+	}
+
+	mmc_pool_tag_add(pool, tag_name, tag_name_len, TA_BEGIN TSRMLS_CC);
+
+	if (IS_ARRAY == Z_TYPE_P(keys)) {
+		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);
+
+		while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&key, &pos) == SUCCESS) {
+			zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
+
+			if (mmc_prepare_key(*key, key_name, &key_name_len) != MMC_OK) {
+				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
+				continue;
+			}
+
+			mmc_pool_tag_add(pool, key_name, key_name_len, TA_MID TSRMLS_CC);
+		}
+	}
+	else {
+		if (mmc_prepare_key(keys, key_name, &key_name_len) != MMC_OK) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
+			RETURN_FALSE;
+		}
+
+		mmc_pool_tag_add(pool, key_name, key_name_len, TA_MID TSRMLS_CC);
+	}
+
+	mmc_pool_tag_add(pool, NULL, 0, TA_END TSRMLS_CC);
+
+	for (i=0; i<pool->num_servers; i++) {
+		mmc = pool->servers[i];
+		if (MMC_STATUS_CONNECTED != mmc->status) {
+			if (0 == mmc_open(mmc, 1, NULL, NULL TSRMLS_CC)) {
+				continue;
+			}
+		}
+		if (NULL==mmc->host
+				|| mmc_server_tag_add(mmc, pool->request.c, pool->request.len TSRMLS_CC)<0) {
+			mmc_server_failure(mmc TSRMLS_CC);
+		}
+	}
+
+	smart_str_free(&pool->request);
+
+	RETURN_TRUE;
+}
+/* }}} */
+
+/* {{{ proto bool memcache_tag_delete( object memcache, string tag)
+   Deletes existing tag */
+PHP_FUNCTION(memcache_tag_delete)
+{
+	mmc_t *mmc = NULL;
+	mmc_pool_t *pool = NULL;
+	zval *tags = NULL, **tag = NULL;
+	zval *mmc_object = getThis();
+	HashPosition pos = NULL;
+	char tag_tmp[MMC_KEY_MAX_SIZE];
+	unsigned int tag_tmp_len = 0, i = 0;
+
+	if (NULL == mmc_object) {
+		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz", &mmc_object, memcache_class_entry_ptr, &tags) == FAILURE) {
+			return;
+		}
+	}
+	else {
+		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &tags) == FAILURE) {
+			return;
+		}
+	}
+
+	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
+		RETURN_FALSE;
+	}
+
+	if (IS_ARRAY == Z_TYPE_P(tags)) {
+		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(tags), &pos);
+
+		while (zend_hash_get_current_data_ex(Z_ARRVAL_P(tags), (void **)&tag, &pos) == SUCCESS) {
+			zend_hash_move_forward_ex(Z_ARRVAL_P(tags), &pos);
+
+ 			if (mmc_prepare_key(*tag, tag_tmp, &tag_tmp_len) != MMC_OK) {
+ 				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid tag");
+ 				continue;
+ 			}
+
+			//multi-server : ignore TAG_NOT_FOUND
+			for (i=0; i<pool->num_servers; i++) {
+				mmc = pool->servers[i];
+				if (MMC_STATUS_CONNECTED != mmc->status) {
+					if (0 == mmc_open(mmc, 1, NULL, NULL TSRMLS_CC)) {
+						continue;
+					}
+				}
+				if (NULL==mmc->host
+						|| mmc_tag_delete(mmc, tag_tmp, tag_tmp_len TSRMLS_CC)<0) {
+					mmc_server_failure(mmc TSRMLS_CC);
+				}
+			}
+ 		}
+ 	}
+ 	else {
+		if (mmc_prepare_key(tags, tag_tmp, &tag_tmp_len TSRMLS_CC) != MMC_OK) {
+			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid tag");
+			RETURN_FALSE;
+		}
+
+		//multi-server : ignore TAG_NOT_FOUND
+		for (i=0; i<pool->num_servers; i++) {
+			mmc = pool->servers[i];
+			if (MMC_STATUS_CONNECTED != mmc->status) {
+				if (0 == mmc_open(mmc, 1, NULL, NULL TSRMLS_CC)) {
+					continue;
+				}
+			}
+			if (NULL==mmc->host
+					|| mmc_tag_delete(mmc, tag_tmp, tag_tmp_len TSRMLS_CC)<0) {
+				mmc_server_failure(mmc TSRMLS_CC);
+			}
+		}
+  	}
+
+	RETURN_TRUE;
+}
+/* }}} */
+//tag related
+
 /* {{{ proto bool memcache_debug( bool onoff )
    Turns on/off internal debugging */
 PHP_FUNCTION(memcache_debug)
diff -NaupB module-2.2.2/php_memcache.h module-tag.2.2.2/php_memcache.h
--- module-2.2.2/php_memcache.h	2008-02-13 15:24:49.000000000 +0800
+++ module-tag.2.2.2/php_memcache.h	2008-02-13 15:24:49.000000000 +0800
@@ -61,6 +61,8 @@ PHP_FUNCTION(memcache_increment);
 PHP_FUNCTION(memcache_decrement);
 PHP_FUNCTION(memcache_close);
 PHP_FUNCTION(memcache_flush);
+PHP_FUNCTION(memcache_tag_add);		//tag related
+PHP_FUNCTION(memcache_tag_delete);	//tag related
 
 #define MMC_BUF_SIZE 4096
 #define MMC_SERIALIZED 1
@@ -134,6 +136,7 @@ typedef struct mmc_pool {
 	zend_bool				in_free;
 	mmc_hash_t				*hash;
 	void					*hash_state;
+	smart_str				request;				//tag related
 } mmc_pool_t;
 
 /* our globals */
