Add support for optional table attributes to CI_DB_forge::create_table()

Supersedes PRs #989, #2776
Related issue: #41
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index 1cebb18..fc4a923 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -314,9 +314,10 @@
 	 *
 	 * @param	string	$table		Table name
 	 * @param	bool	$if_not_exists	Whether to add IF NOT EXISTS condition
+	 * @param	array	$attributes	Associative array of table attributes
 	 * @return	bool
 	 */
-	public function create_table($table = '', $if_not_exists = FALSE)
+	public function create_table($table = '', $if_not_exists = FALSE, array $attributes = array())
 	{
 		if ($table === '')
 		{
@@ -332,7 +333,7 @@
 			show_error('Field information is required.');
 		}
 
-		$sql = $this->_create_table($table, $if_not_exists);
+		$sql = $this->_create_table($table, $if_not_exists, $attributes);
 
 		if (is_bool($sql))
 		{
@@ -368,9 +369,10 @@
 	 *
 	 * @param	string	$table		Table name
 	 * @param	bool	$if_not_exists	Whether to add 'IF NOT EXISTS' condition
+	 * @param	array	$attributes	Associative array of table attributes
 	 * @return	mixed
 	 */
-	protected function _create_table($table, $if_not_exists)
+	protected function _create_table($table, $if_not_exists, $attributes)
 	{
 		if ($if_not_exists === TRUE && $this->_create_table_if === FALSE)
 		{
@@ -406,10 +408,11 @@
 		}
 
 		// _create_table will usually have the following format: "%s %s (%s\n)"
-		$sql = sprintf($this->_create_table.';',
+		$sql = sprintf($this->_create_table.'%s;',
 			$sql,
 			$this->db->escape_identifiers($table),
-			$columns
+			$columns,
+			$this->_create_table_attr($attributes)
 		);
 
 		return $sql;
@@ -418,6 +421,29 @@
 	// --------------------------------------------------------------------
 
 	/**
+	 * CREATE TABLE attributes
+	 *
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	string
+	 */
+	protected function _create_table_attr($attributes)
+	{
+		$sql = '';
+
+		foreach (array_keys($attributes) as $key)
+		{
+			if (is_string($key))
+			{
+				$sql .= ' '.strtoupper($key).' '.$attributes[$key];
+			}
+		}
+
+		return $sql;
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
 	 * Drop Table
 	 *
 	 * @param	string	$table_name	Table name
diff --git a/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php
index e251c0e..3b3cbde 100644
--- a/system/database/drivers/mysql/mysql_forge.php
+++ b/system/database/drivers/mysql/mysql_forge.php
@@ -82,16 +82,34 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Class constructor
+	 * CREATE TABLE attributes
 	 *
-	 * @param	object	&$db	Database object
-	 * @return	void
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	string
 	 */
-	public function __construct(&$db)
+	protected function _create_table_attr($attributes)
 	{
-		parent::__construct($db);
+		$sql = '';
 
-		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+		foreach (array_keys($attributes) as $key)
+		{
+			if (is_string($key))
+			{
+				$sql .= ' '.strtoupper($key).' = '.$attributes[$key];
+			}
+		}
+
+		if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET'))
+		{
+			$sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set;
+		}
+
+		if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE'))
+		{
+			$sql .= ' COLLATE = '.$this->db->dbcollat;
+		}
+
+		return $sql;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php
index d1e5e20..1a568cc 100644
--- a/system/database/drivers/mysqli/mysqli_forge.php
+++ b/system/database/drivers/mysqli/mysqli_forge.php
@@ -82,16 +82,34 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Class constructor
+	 * CREATE TABLE attributes
 	 *
-	 * @param	object	&$db	Database object
-	 * @return	void
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	string
 	 */
-	public function __construct(&$db)
+	protected function _create_table_attr($attributes)
 	{
-		parent::__construct($db);
+		$sql = '';
 
-		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+		foreach (array_keys($attributes) as $key)
+		{
+			if (is_string($key))
+			{
+				$sql .= ' '.strtoupper($key).' = '.$attributes[$key];
+			}
+		}
+
+		if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET'))
+		{
+			$sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set;
+		}
+
+		if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE'))
+		{
+			$sql .= ' COLLATE = '.$this->db->dbcollat;
+		}
+
+		return $sql;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
index 74689d9..3ac98e6 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
@@ -96,16 +96,34 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * Class constructor
+	 * CREATE TABLE attributes
 	 *
-	 * @param	object	&$db	Database object
-	 * @return	void
+	 * @param	array	$attributes	Associative array of table attributes
+	 * @return	string
 	 */
-	public function __construct(&$db)
+	protected function _create_table_attr($attributes)
 	{
-		parent::__construct($db);
+		$sql = '';
 
-		$this->_create_table .= ' DEFAULT CHARSET '.$this->db->char_set.' COLLATE '.$this->db->dbcollat;
+		foreach (array_keys($attributes) as $key)
+		{
+			if (is_string($key))
+			{
+				$sql .= ' '.strtoupper($key).' = '.$attributes[$key];
+			}
+		}
+
+		if ( ! empty($this->db->char_set) && ! strpos($sql, 'CHARACTER SET') && ! strpos($sql, 'CHARSET'))
+		{
+			$sql .= ' DEFAULT CHARACTER SET = '.$this->db->char_set;
+		}
+
+		if ( ! empty($this->db->dbcollat) && ! strpos($sql, 'COLLATE'))
+		{
+			$sql .= ' COLLATE = '.$this->db->dbcollat;
+		}
+
+		return $sql;
 	}
 
 	// --------------------------------------------------------------------
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index c68258e..7dc3586 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -240,8 +240,9 @@
 
       - Added an optional second parameter to ``drop_table()`` that allows adding the **IF EXISTS** condition, which is no longer the default.
       - Added support for passing a custom database object to the loader.
-      - Deprecated ``add_column()``'s third method. *AFTER* clause should now be added to the field definition array instead.
+      - Added support for passing custom table attributes (such as ``ENGINE`` for MySQL) to ``create_table()``.
       - Added support for usage of the *FIRST* clause in ``add_column()`` for MySQL and CUBRID.
+      - Deprecated ``add_column()``'s third method. *AFTER* clause should now be added to the field definition array instead.
       - Overall improved support for all of the drivers.
 
    -  :doc:`Database Utility <database/utilities>` changes include:
diff --git a/user_guide_src/source/database/forge.rst b/user_guide_src/source/database/forge.rst
index ca904ed..48642ad 100644
--- a/user_guide_src/source/database/forge.rst
+++ b/user_guide_src/source/database/forge.rst
@@ -201,6 +201,15 @@
 	$this->dbforge->create_table('table_name', TRUE);
 	// gives CREATE TABLE IF NOT EXISTS table_name
 
+You could also pass optional table attributes, such as MySQL's ``ENGINE``::
+
+	$attributes = array('ENGINE' => 'InnoDB');
+	$this->dbforge->create_table('table_name', FALSE, $attributes);
+	// produces: CREATE TABLE `table_name` (...) ENGINE = InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci
+
+.. note:: Unless you specify the ``CHARACTER SET`` and/or ``COLLATE`` attributes,
+	``create_table()`` will always add them with your configured *char_set*
+	and *dbcollat* values, as long as they are not empty (MySQL only).
 
 Dropping a table
 ================