Index: wp-includes/post.php
===================================================================
--- wp-includes/post.php	(revision 11913)
+++ wp-includes/post.php	(working copy)
@@ -511,30 +511,11 @@
  * @return bool False for failure. True for success.
  */
 function add_post_meta($post_id, $meta_key, $meta_value, $unique = false) {
-	if ( !$meta_key )
-		return false;
-
-	global $wpdb;
-
 	// make sure meta is added to the post, not a revision
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$post_id = $the_post;
 
-	// expected_slashed ($meta_key)
-	$meta_key = stripslashes($meta_key);
-
-	if ( $unique && $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->postmeta WHERE meta_key = %s AND post_id = %d", $meta_key, $post_id ) ) )
-		return false;
-
-	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
-
-	$wpdb->insert( $wpdb->postmeta, compact( 'post_id', 'meta_key', 'meta_value' ) );
-
-	wp_cache_delete($post_id, 'post_meta');
-
-	do_action( 'added_post_meta', $wpdb->insert_id, $post_id, $meta_key, $meta_value );
-
-	return true;
+	return add_metadata('post', $post_id, $meta_key, $meta_value, $unique);
 }
 
 /**
@@ -554,39 +535,11 @@
  * @return bool False for failure. True for success.
  */
 function delete_post_meta($post_id, $meta_key, $meta_value = '') {
-	global $wpdb;
-
 	// make sure meta is added to the post, not a revision
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$post_id = $the_post;
 
-	// expected_slashed ($meta_key, $meta_value)
-	$meta_key = stripslashes( $meta_key );
-	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
-
-	if ( !$meta_key )
-		return false;
-
-	if ( empty( $meta_value ) )
-		$meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s", $post_id, $meta_key ) );
-	else
-		$meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s AND meta_value = %s", $post_id, $meta_key, $meta_value ) );
-
-	if ( !$meta_id )
-		return false;
-
-	do_action( 'delete_post_meta', $meta_id, $post_id, $meta_key, $meta_value );
-
-	if ( empty( $meta_value ) )
-		$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s", $post_id, $meta_key ) );
-	else
-		$wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s AND meta_value = %s", $post_id, $meta_key, $meta_value ) );
-
-	wp_cache_delete($post_id, 'post_meta');
-	
-	do_action( 'deleted_post_meta', $meta_id, $post_id, $meta_key, $meta_value );
-
-	return true;
+	return delete_metadata('post', $post_id, $meta_key, $meta_value);
 }
 
 /**
@@ -603,27 +556,7 @@
  *  is true.
  */
 function get_post_meta($post_id, $key, $single = false) {
-	if ( !$key )
-		return '';
-
-	$post_id = (int) $post_id;
-
-	$meta_cache = wp_cache_get($post_id, 'post_meta');
-
-	if ( !$meta_cache ) {
-		update_postmeta_cache($post_id);
-		$meta_cache = wp_cache_get($post_id, 'post_meta');
-	}
-
-	if ( isset($meta_cache[$key]) ) {
-		if ( $single ) {
-			return maybe_unserialize( $meta_cache[$key][0] );
-		} else {
-			return array_map('maybe_unserialize', $meta_cache[$key]);
-		}
-	}
-
-	return '';
+	return get_metadata('post', $post_id, $key, $single);
 }
 
 /**
@@ -645,40 +578,11 @@
  * @return bool False on failure, true if success.
  */
 function update_post_meta($post_id, $meta_key, $meta_value, $prev_value = '') {
-	global $wpdb;
-
 	// make sure meta is added to the post, not a revision
 	if ( $the_post = wp_is_post_revision($post_id) )
 		$post_id = $the_post;
 
-	// expected_slashed ($meta_key)
-	$meta_key = stripslashes($meta_key);
-
-	if ( !$meta_key )
-		return false;
-
-	$meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = %s AND post_id = %d", $meta_key, $post_id ) );
-	if ( ! $meta_id )
-		return add_post_meta($post_id, $meta_key, $meta_value);
-
-	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
-
-	$data  = compact( 'meta_value' );
-	$where = compact( 'meta_key', 'post_id' );
-
-	if ( !empty( $prev_value ) ) {
-		$prev_value = maybe_serialize($prev_value);
-		$where['meta_value'] = $prev_value;
-	}
-
-	do_action( 'update_post_meta', $meta_id, $post_id, $meta_key, $meta_value );
-	
-	$wpdb->update( $wpdb->postmeta, $data, $where );
-	wp_cache_delete($post_id, 'post_meta');
-	
-	do_action( 'updated_post_meta', $meta_id, $post_id, $meta_key, $meta_value );
-	
-	return true;
+	return update_metadata('post', $post_id, $meta_key, $meta_value, $prev_value);
 }
 
 /**
@@ -3304,56 +3208,7 @@
  * @return bool|array Returns false if there is nothing to update or an array of metadata.
  */
 function update_postmeta_cache($post_ids) {
-	global $wpdb;
-
-	if ( empty( $post_ids ) )
-		return false;
-
-	if ( !is_array($post_ids) ) {
-		$post_ids = preg_replace('|[^0-9,]|', '', $post_ids);
-		$post_ids = explode(',', $post_ids);
-	}
-
-	$post_ids = array_map('intval', $post_ids);
-
-	$ids = array();
-	foreach ( (array) $post_ids as $id ) {
-		if ( false === wp_cache_get($id, 'post_meta') )
-			$ids[] = $id;
-	}
-
-	if ( empty( $ids ) )
-		return false;
-
-	// Get post-meta info
-	$id_list = join(',', $ids);
-	$cache = array();
-	if ( $meta_list = $wpdb->get_results("SELECT post_id, meta_key, meta_value FROM $wpdb->postmeta WHERE post_id IN ($id_list)", ARRAY_A) ) {
-		foreach ( (array) $meta_list as $metarow) {
-			$mpid = (int) $metarow['post_id'];
-			$mkey = $metarow['meta_key'];
-			$mval = $metarow['meta_value'];
-
-			// Force subkeys to be array type:
-			if ( !isset($cache[$mpid]) || !is_array($cache[$mpid]) )
-				$cache[$mpid] = array();
-			if ( !isset($cache[$mpid][$mkey]) || !is_array($cache[$mpid][$mkey]) )
-				$cache[$mpid][$mkey] = array();
-
-			// Add a value to the current pid/key:
-			$cache[$mpid][$mkey][] = $mval;
-		}
-	}
-
-	foreach ( (array) $ids as $id ) {
-		if ( ! isset($cache[$id]) )
-			$cache[$id] = array();
-	}
-
-	foreach ( (array) array_keys($cache) as $post)
-		wp_cache_set($post, $cache[$post], 'post_meta');
-
-	return $cache;
+	return update_meta_cache('post', $post_ids);
 }
 
 //
Index: wp-includes/version.php
===================================================================
--- wp-includes/version.php	(revision 11913)
+++ wp-includes/version.php	(working copy)
@@ -15,7 +15,7 @@
  *
  * @global int $wp_db_version
  */
-$wp_db_version = 11557;
+$wp_db_version = 11558;
 
 /**
  * Holds the TinyMCE version
Index: wp-includes/comment.php
===================================================================
--- wp-includes/comment.php	(revision 11913)
+++ wp-includes/comment.php	(working copy)
@@ -361,7 +361,89 @@
 	return $comment_count;
 }
 
+//
+// Comment meta functions
+//
+
 /**
+ * Add meta data field to a comment.
+ *
+ * Post meta data is called "Custom Fields" on the Administration Panels.
+ *
+ * @since 2.9
+ * @uses add_metadata
+ * @link http://codex.wordpress.org/Function_Reference/add_comment_meta
+ *
+ * @param int $comment_id Post ID.
+ * @param string $key Metadata name.
+ * @param mixed $value Metadata value.
+ * @param bool $unique Optional, default is false. Whether the same key should not be added.
+ * @return bool False for failure. True for success.
+ */
+function add_comment_meta($comment_id, $meta_key, $meta_value, $unique = false) {
+	return add_metadata('comment', $comment_id, $meta_key, $meta_value, $unique);
+}
+
+/**
+ * Remove metadata matching criteria from a comment.
+ *
+ * You can match based on the key, or key and value. Removing based on key and
+ * value, will keep from removing duplicate metadata with the same key. It also
+ * allows removing all metadata matching key, if needed.
+ *
+ * @since 2.9
+ * @uses delete_metadata
+ * @link http://codex.wordpress.org/Function_Reference/delete_comment_meta
+ *
+ * @param int $comment_id comment ID
+ * @param string $meta_key Metadata name.
+ * @param mixed $meta_value Optional. Metadata value.
+ * @return bool False for failure. True for success.
+ */
+function delete_comment_meta($comment_id, $meta_key, $meta_value = '') {
+	return delete_metadata('comment', $comment_id, $meta_key, $meta_value);
+}
+
+/**
+ * Retrieve comment meta field for a comment.
+ *
+ * @since 2.9
+ * @uses get_metadata
+ * @link http://codex.wordpress.org/Function_Reference/get_comment_meta
+ *
+ * @param int $comment_id Post ID.
+ * @param string $key The meta key to retrieve.
+ * @param bool $single Whether to return a single value.
+ * @return mixed Will be an array if $single is false. Will be value of meta data field if $single
+ *  is true.
+ */
+function get_comment_meta($comment_id, $key, $single = false) {
+	return get_metadata('comment', $comment_id, $key, $single);
+}
+
+/**
+ * Update comment meta field based on comment ID.
+ *
+ * Use the $prev_value parameter to differentiate between meta fields with the
+ * same key and comment ID.
+ *
+ * If the meta field for the comment does not exist, it will be added.
+ *
+ * @since 2.9
+ * @uses update_metadata
+ * @link http://codex.wordpress.org/Function_Reference/update_comment_meta
+ *
+ * @param int $comment_id Post ID.
+ * @param string $key Metadata key.
+ * @param mixed $value Metadata value.
+ * @param mixed $prev_value Optional. Previous value to check before removing.
+ * @return bool False on failure, true if success.
+ */
+function update_comment_meta($comment_id, $meta_key, $meta_value, $prev_value = '') {
+	return update_metadata('comment', $comment_id, $meta_key, $meta_value, $prev_value);
+}
+
+/**
  * Sanitizes the cookies sent to the user already.
  *
  * Will only do anything if the cookies have already been created for the user.
Index: wp-includes/wp-db.php
===================================================================
--- wp-includes/wp-db.php	(revision 11913)
+++ wp-includes/wp-db.php	(working copy)
@@ -155,24 +155,6 @@
 	var $users;
 
 	/**
-	 * WordPress Categories table
-	 *
-	 * @since 1.5.0
-	 * @access public
-	 * @var string
-	 */
-	var $categories;
-
-	/**
-	 * WordPress Post to Category table
-	 *
-	 * @since 1.5.0
-	 * @access public
-	 * @var string
-	 */
-	var $post2cat;
-
-	/**
 	 * WordPress Comments table
 	 *
 	 * @since 1.5.0
@@ -209,6 +191,15 @@
 	var $postmeta;
 
 	/**
+	 * WordPress Comment Metadata table
+	 *
+	 * @since 2.9
+	 * @access public
+	 * @var string
+	 */
+	var $commentmeta;
+
+	/**
 	 * WordPress User Metadata table
 	 *
 	 * @since 2.3.0
@@ -251,8 +242,8 @@
 	 * @access private
 	 * @var array
 	 */
-	var $tables = array('users', 'usermeta', 'posts', 'categories', 'post2cat', 'comments', 'links', 'link2cat', 'options',
-			'postmeta', 'terms', 'term_taxonomy', 'term_relationships');
+	var $tables = array('users', 'usermeta', 'posts', 'postmeta', 'comments', 'commentmeta', 
+		'links', 'options',	'terms', 'term_taxonomy', 'term_relationships');
 
 	/**
 	 * List of deprecated WordPress tables
Index: wp-includes/meta.php
===================================================================
--- wp-includes/meta.php	(revision 0)
+++ wp-includes/meta.php	(revision 0)
@@ -0,0 +1,210 @@
+<?php
+/**
+ * Meta API
+ *
+ * Functions for retrieving and manipulating metadata
+ *
+ * @package WordPress
+ */
+
+function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) {
+	if ( !$meta_type || !$meta_key )
+		return false;
+
+	if ( ! $table = _get_meta_table($meta_type) )
+		return false;
+
+	global $wpdb;
+
+	$column = esc_sql($meta_type . '_id');
+
+	// expected_slashed ($meta_key)
+	$meta_key = stripslashes($meta_key);
+
+	if ( $unique && $wpdb->get_var( $wpdb->prepare(
+		"SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d", 
+		$meta_key, $object_id ) ) )
+		return false;
+
+	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
+
+	$wpdb->insert( $table, array(
+		$column => $object_id, 
+		'meta_key' => $meta_key, 
+		'meta_value' => $meta_value
+	) );
+
+	wp_cache_delete($object_id, $meta_type . '_meta');
+
+	do_action( "added_{$meta_type}_meta", $wpdb->insert_id, $object_id, $meta_key, $meta_value );
+
+	return true;
+}
+
+function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {
+	if ( !$meta_type || !$meta_key )
+		return false;
+
+	if ( ! $table = _get_meta_table($meta_type) )
+		return false;
+
+	global $wpdb;
+
+	$column = esc_sql($meta_type . '_id');
+
+	// expected_slashed ($meta_key)
+	$meta_key = stripslashes($meta_key);
+
+	if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ) )
+		return add_metadata($meta_type, $object_id, $meta_key, $meta_value);
+
+	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
+
+	$data  = compact( 'meta_value' );
+	$where = array( $column => $object_id, 'meta_key' => $meta_key );
+
+	if ( !empty( $prev_value ) ) {
+		$prev_value = maybe_serialize($prev_value);
+		$where['meta_value'] = $prev_value;
+	}
+
+	do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $meta_value );
+
+	$wpdb->update( $table, $data, $where );
+	wp_cache_delete($object_id, $meta_type . '_meta');
+
+	do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $meta_value );
+
+	return true;
+}
+
+function delete_metadata($meta_type, $object_id, $meta_key, $meta_value = '') {
+	if ( !$meta_type || !$meta_key )
+		return false;
+
+	if ( ! $table = _get_meta_table($meta_type) )
+		return false;
+
+	global $wpdb;
+
+	$column = esc_sql($meta_type . '_id');
+
+	// expected_slashed ($meta_key)
+	$meta_key = stripslashes($meta_key);
+	$meta_value = maybe_serialize( stripslashes_deep($meta_value) );
+
+	$query = $wpdb->prepare( "DELETE FROM $table WHERE meta_key = %s", $meta_key );
+
+	if ( $meta_value )
+		$query .= $wpdb->prepare("AND meta_value = %s", $meta_value );
+	
+	$count = $wpdb->query($query);
+	
+	if ( !$count )
+		return false;
+
+	wp_cache_delete($object_id, $meta_type . '_meta');
+
+	do_action( "deleted_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $meta_value );
+
+	return true;
+}
+
+function get_metadata($meta_type, $object_id, $meta_key = '', $single = false) {
+	if ( !$meta_type )
+		return false;
+
+	$meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
+
+	if ( !$meta_cache ) {
+		update_meta_cache($meta_type, $object_id);
+		$meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
+	}
+
+	if ( ! $meta_key )
+		return $meta_cache;
+
+	if ( isset($meta_cache[$meta_key]) ) {
+		if ( $single ) {
+			return maybe_unserialize( $meta_cache[$meta_key][0] );
+		} else {
+			return array_map('maybe_unserialize', $meta_cache[$meta_key]);
+		}
+	}
+
+	return false;
+}
+
+function update_meta_cache($meta_type, $object_ids) {
+	if ( empty( $meta_type ) || empty( $object_ids ) )
+		return false;
+
+	if ( ! $table = _get_meta_table($meta_type) )
+		return false;
+
+	$column = esc_sql($meta_type . '_id');
+
+	global $wpdb;
+
+	if ( !is_array($object_ids) ) {
+		$object_ids = preg_replace('|[^0-9,]|', '', $object_ids);
+		$object_ids = explode(',', $object_ids);
+	}
+
+	$object_ids = array_map('intval', $object_ids);
+
+	$cache_key = $meta_type . '_meta';
+	$ids = array();
+	foreach ( $object_ids as $id ) {
+		if ( false === wp_cache_get($id, $cache_key) )
+			$ids[] = $id;
+	}
+
+	if ( empty( $ids ) )
+		return false;
+
+	// Get meta info
+	$id_list = join(',', $ids);
+	$cache = array();
+	$meta_list = $wpdb->get_results( $wpdb->prepare("SELECT $column, meta_key, meta_value FROM $table WHERE $column IN ($id_list)", 
+		$meta_type), ARRAY_A );
+
+	if ( !empty($meta_list) ) {
+		foreach ( $meta_list as $metarow) {
+			$mpid = intval($metarow[$column]);
+			$mkey = $metarow['meta_key'];
+			$mval = $metarow['meta_value'];
+
+			// Force subkeys to be array type:
+			if ( !isset($cache[$mpid]) || !is_array($cache[$mpid]) )
+				$cache[$mpid] = array();
+			if ( !isset($cache[$mpid][$mkey]) || !is_array($cache[$mpid][$mkey]) )
+				$cache[$mpid][$mkey] = array();
+
+			// Add a value to the current pid/key:
+			$cache[$mpid][$mkey][] = $mval;
+		}
+	}
+
+	foreach ( $ids as $id ) {
+		if ( ! isset($cache[$id]) )
+			$cache[$id] = array();
+	}
+
+	foreach ( array_keys($cache) as $object)
+		wp_cache_set($object, $cache[$object], $cache_key);
+
+	return $cache;
+}
+
+function _get_meta_table($type) {
+	global $wpdb;
+
+	$table_name = $type . 'meta';
+
+	if ( empty($wpdb->$table_name) )
+		return false;
+
+	return $wpdb->$table_name;
+}
+
Index: wp-settings.php
===================================================================
--- wp-settings.php	(revision 11913)
+++ wp-settings.php	(working copy)
@@ -332,6 +332,7 @@
 require (ABSPATH . WPINC . '/query.php');
 require (ABSPATH . WPINC . '/theme.php');
 require (ABSPATH . WPINC . '/user.php');
+require (ABSPATH . WPINC . '/meta.php');
 require (ABSPATH . WPINC . '/general-template.php');
 require (ABSPATH . WPINC . '/link-template.php');
 require (ABSPATH . WPINC . '/author-template.php');
Index: wp-admin/includes/schema.php
===================================================================
--- wp-admin/includes/schema.php	(revision 11913)
+++ wp-admin/includes/schema.php	(working copy)
@@ -111,6 +111,15 @@
   KEY post_id (post_id),
   KEY meta_key (meta_key)
 ) $charset_collate;
+CREATE TABLE $wpdb->commentmeta (
+  meta_id bigint(20) unsigned NOT NULL auto_increment,
+  comment_id bigint(20) unsigned NOT NULL default '0',
+  meta_key varchar(255) default NULL,
+  meta_value longtext,
+  PRIMARY KEY  (meta_id),
+  KEY post_id (comment_id),
+  KEY meta_key (meta_key)
+) $charset_collate;
 CREATE TABLE $wpdb->posts (
   ID bigint(20) unsigned NOT NULL auto_increment,
   post_author bigint(20) unsigned NOT NULL default '0',
Index: wp-admin/includes/comment.php
===================================================================
--- wp-admin/includes/comment.php	(revision 11913)
+++ wp-admin/includes/comment.php	(working copy)
@@ -130,6 +130,7 @@
 	return $pending_keyed;
 }
 
+
 /**
  * Add avatars to relevant places in admin, or try to.
  *
@@ -164,4 +165,3 @@
 		add_filter( 'comment_author', 'floated_admin_avatar' );
 }
 
-?>
