diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc
index 9ba1c73..755ebd4 100644
--- a/includes/database/mysql/schema.inc
+++ b/includes/database/mysql/schema.inc
@@ -335,15 +335,24 @@ class DatabaseSchema_mysql extends DatabaseSchema {
     if ($this->fieldExists($table, $field)) {
       throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
     }
+    // Fields that are part of a PRIMARY KEY must be added as NOT NULL.
+    $is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
 
     $fixnull = FALSE;
-    if (!empty($spec['not null']) && !isset($spec['default'])) {
+    if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
       $fixnull = TRUE;
       $spec['not null'] = FALSE;
     }
     $query = 'ALTER TABLE {' . $table . '} ADD ';
     $query .= $this->createFieldSql($field, $this->processField($spec));
     if ($keys_sql = $this->createKeysSql($keys_new)) {
+      // Make sure to drop the existing primary key before adding a new one.
+      // This is only needed when adding a field because this method, unlike
+      // changeField(), is supposed to handle primary keys automatically.
+      if (isset($keys_new['primary key']) && $this->indexExists($table, 'PRIMARY')) {
+        $query .= ', DROP PRIMARY KEY';
+      }
+
       $query .= ', ADD ' . implode(', ADD ', $keys_sql);
     }
     $this->connection->query($query);
diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc
index f5774de..5ab54d0 100644
--- a/includes/database/pgsql/schema.inc
+++ b/includes/database/pgsql/schema.inc
@@ -357,8 +357,11 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
       throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
     }
 
+    // Fields that are part of a PRIMARY KEY must be added as NOT NULL.
+    $is_primary_key = isset($keys_new['primary key']) && in_array($field, $keys_new['primary key'], TRUE);
+
     $fixnull = FALSE;
-    if (!empty($spec['not null']) && !isset($spec['default'])) {
+    if (!empty($spec['not null']) && !isset($spec['default']) && !$is_primary_key) {
       $fixnull = TRUE;
       $spec['not null'] = FALSE;
     }
@@ -374,6 +377,12 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
       $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
     }
     if (isset($new_keys)) {
+      // Make sure to drop the existing primary key before adding a new one.
+      // This is only needed when adding a field because this method, unlike
+      // changeField(), is supposed to handle primary keys automatically.
+      if (isset($new_keys['primary key']) && $this->constraintExists($table, 'pkey')) {
+        $this->dropPrimaryKey($table);
+      }
       $this->_createKeys($table, $new_keys);
     }
     // Add column comment.
diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc
index df19d2f..434c712 100644
--- a/includes/database/sqlite/schema.inc
+++ b/includes/database/sqlite/schema.inc
@@ -324,7 +324,7 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
       }
 
       // Add the new indexes.
-      $new_schema += $keys_new;
+      $new_schema = array_merge($new_schema, $keys_new);
 
       $this->alterTable($table, $old_schema, $new_schema, $mapping);
     }
diff --git a/modules/simpletest/tests/schema.test b/modules/simpletest/tests/schema.test
index 4199428..ecd6df1 100644
--- a/modules/simpletest/tests/schema.test
+++ b/modules/simpletest/tests/schema.test
@@ -117,6 +117,29 @@ class SchemaTestCase extends DrupalWebTestCase {
     $count = db_query('SELECT COUNT(*) FROM {test_table}')->fetchField();
     $this->assertEqual($count, 2, 'There were two rows.');
 
+    // Test adding a serial field to an existing table.
+    db_drop_table('test_table');
+    db_create_table('test_table', $table_specification);
+    db_field_set_default('test_table', 'test_field', 0);
+    db_add_field('test_table', 'test_serial', array('type' => 'serial', 'not null' => TRUE), array('primary key' => array('test_serial')));
+
+    $this->assertPrimaryKeyColumns('test_table', array('test_serial'));
+
+    $this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
+    $max1 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
+    $this->assertTrue($this->tryInsert(), 'Insert with a serial succeeded.');
+    $max2 = db_query('SELECT MAX(test_serial) FROM {test_table}')->fetchField();
+    $this->assertTrue($max2 > $max1, 'The serial is monotone.');
+
+    $count = db_query('SELECT COUNT(*) FROM {test_table}')->fetchField();
+    $this->assertEqual($count, 2, 'There were two rows.');
+
+    // Test adding a new column and form a composite primary key with it.
+    db_add_field('test_table', 'test_composite_primary_key', array('type' => 'int', 'not null' => TRUE, 'default' => 0), array('primary key' => array('test_serial', 'test_composite_primary_key')));
+
+    $this->assertPrimaryKeyColumns('test_table', array('test_serial', 'test_composite_primary_key'));
+
+
     // Use database specific data type and ensure that table is created.
     $table_specification = array(
       'description' => 'Schema table description.',
@@ -381,4 +404,47 @@ class SchemaTestCase extends DrupalWebTestCase {
 
     db_drop_field($table_name, $field_name);
   }
+
+  /**
+   * Tests the primary keys of a table.
+   *
+   * @param string $table_name
+   *   The name of the table to check.
+   * @param array $primary_key
+   *   The expected key column specifier for a table's primary key.
+   */
+  protected function assertPrimaryKeyColumns($table_name, array $primary_key = array()) {
+    $db_type = Database::getConnection()->databaseType();
+
+    switch ($db_type) {
+      case 'mysql':
+        $result = Database::getConnection()->query("SHOW KEYS FROM {" . $table_name . "} WHERE Key_name = 'PRIMARY'")->fetchAllAssoc('Column_name');
+        $this->assertEqual($primary_key, array_keys($result), 'mysql assert expected key column for a tables primary key.');
+
+        break;
+      case 'pgsql':
+        $result = Database::getConnection()->query("SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
+          FROM pg_index i
+          JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
+          WHERE i.indrelid = '{" . $table_name . "}'::regclass AND i.indisprimary")
+          ->fetchAllAssoc('attname');
+        $this->assertEqual($primary_key, array_keys($result), 'pgsql assert expected key column for a tables primary key.');
+
+        break;
+      case 'sqlite':
+        // For SQLite we need access to the protected
+        // \Drupal\includes\database\sqlite\Schema::introspectSchema() method
+        // because we have no other way of getting the table prefixes needed for
+        // running a straight PRAGMA query.
+        $schema_object = Database::getConnection()->schema();
+        $reflection = new \ReflectionMethod($schema_object, 'introspectSchema');
+        $reflection->setAccessible(TRUE);
+
+        $table_info = $reflection->invoke($schema_object, $table_name);
+        $this->assertEqual($primary_key, array_keys($result), 'sqlite assert expected key column for a tables primary key.');
+
+        break;
+    }
+  }
+
 }
