[glom/mysql2: 1/13] Initial MySQL support



commit f6881e5271235898e6e394e7dce14b01ed9e9d66
Author: Murray Cumming <murrayc murrayc com>
Date:   Tue Jan 1 15:22:16 2013 +0100

    Initial MySQL support

 configure.ac                                       |   49 +-
 glom/libglom/connectionpool.cc                     |   20 +
 glom/libglom/connectionpool_backends/mysql.cc      |  872 ++++++++++++++++++++
 glom/libglom/connectionpool_backends/mysql.h       |  114 +++
 .../connectionpool_backends/mysql_central.cc       |  170 ++++
 .../connectionpool_backends/mysql_central.h        |   66 ++
 glom/libglom/connectionpool_backends/mysql_self.cc |  735 +++++++++++++++++
 glom/libglom/connectionpool_backends/mysql_self.h  |  100 +++
 glom/libglom/connectionpool_backends/postgres.cc   |    1 +
 glom/libglom/db_utils.cc                           |    8 +-
 glom/libglom/db_utils.h                            |    1 +
 glom/libglom/document/document.cc                  |   34 +-
 glom/libglom/document/document.h                   |    6 +-
 glom/libglom/filelist.am                           |    8 +-
 glom/main.cc                                       |   63 ++-
 glom/mode_design/users/dialog_users_list.cc        |    2 +
 tests/test_selfhosting_new_empty.cc                |    7 +
 tests/test_selfhosting_utils.cc                    |    2 +
 18 files changed, 2243 insertions(+), 15 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e75b566..38e19f1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -110,6 +110,8 @@ AC_ARG_ENABLE([ui-tests],
               [glom_enable_ui_tests=yes])
 AM_CONDITIONAL([GLOM_ENABLE_UI_TESTS], [test "x$glom_enable_ui_tests" = xyes])
 
+
+# SQLite support:
 AC_ARG_ENABLE([sqlite],
               [AS_HELP_STRING([--enable-sqlite],
                               [Allow creation of SQLite databases and opening
@@ -125,6 +127,22 @@ AS_IF([test "x$glom_enable_sqlite" = xyes],
       [AC_DEFINE([GLOM_ENABLE_SQLITE], [1],
                  [Whether to enable support for SQLite databases.])])
 
+
+# MySQL support:
+AC_ARG_ENABLE([mysql],
+              [AS_HELP_STRING([--disable-mysql],
+                              [do not build with support for MySQL databases])],
+              [glom_enable_mysql=$enableval],
+              [glom_enable_mysql=no])
+
+AM_CONDITIONAL([GLOM_ENABLE_MYSQL], [test "x$glom_enable_mysql" = xyes])
+
+AS_IF([test "x$glom_enable_mysql" = xyes],
+      [AC_DEFINE([GLOM_ENABLE_MYSQL], [1],
+                 [Whether to enable support for MySQL databases.])])
+
+
+# PostgreSQL support:
 AC_ARG_ENABLE([postgresql],
               [AS_HELP_STRING([--disable-postgresql],
                               [do not build with support for PostgreSQL databases])],
@@ -137,8 +155,9 @@ AS_IF([test "x$glom_enable_postgresql" = xyes],
       [AC_DEFINE([GLOM_ENABLE_POSTGRESQL], [1],
                  [Whether to enable support for PostgreSQL databases.])])
 
+
 # Libraries used by libglom:
-REQUIRED_LIBGLOM_LIBS='giomm-2.4 >= 2.32.0 libxml++-2.6 >= 2.23.1 libxslt >= 1.1.10 pygobject-3.0 >= 2.29.0 libgdamm-5.0 >= 4.99.6 libgda-5.0 >= 5.0.3 libgda-postgres-5.0'
+REQUIRED_LIBGLOM_LIBS='giomm-2.4 >= 2.32.0 libxml++-2.6 >= 2.23.1 libxslt >= 1.1.10 pygobject-3.0 >= 2.29.0 libgdamm-5.0 >= 4.99.6 libgda-5.0 >= 5.0.3 libgda-postgres-5.0 libgda-postgres-5.0 libgda-mysql-5.0'
 
 AS_IF([test "x$glom_host_win32" != xyes],
       [REQUIRED_LIBGLOM_LIBS="$REQUIRED_LIBGLOM_LIBS libepc-1.0 >= 0.4.0"])
@@ -224,8 +243,9 @@ AC_ARG_ENABLE([update-mime-database],
 
 AM_CONDITIONAL([UPDATE_MIME_DATABASE], [test "x$glom_update_mime_database" != xno])
 
-# Locate the directory containing the postgresql utilities, such as the
-# postmaster executable, so we can self-host postgresql databases.
+
+# Locate the directory containing the PostgreSQL utilities, such as the
+# postmaster executable, so we can self-host PostgreSQL databases.
 AC_ARG_WITH([postgres-utils],
             [AS_HELP_STRING([--with-postgres-utils=DIR],
                             [path to PostgreSQL utilities (overriding pg_config)])],
@@ -238,8 +258,8 @@ AS_IF([test "x$glom_host_win32" != xyes && test "x$glom_enable_client_only" != x
   POSTGRES_UTILS_PATH=`pg_config --bindir 2>&AS_MESSAGE_LOG_FD`
   AS_IF(["$POSTGRES_UTILS_PATH/pg_ctl" --version >/dev/null 2>&AS_MESSAGE_LOG_FD],,
         [AC_MSG_ERROR([[
-The Postgres utilities could not be found. They are needed for
-self-hosting of Glom databases. Please make sure that Postgres
+The PostgreSQL utilities could not be found. They are needed for
+self-hosting of Glom databases. Please make sure that PostgreSQL
 is installed, and if necessary specify the correct directory
 explicitly with the --with-postgres-utils option.
 ]])])])])
@@ -248,6 +268,25 @@ AC_DEFINE_UNQUOTED([POSTGRES_UTILS_PATH], ["$POSTGRES_UTILS_PATH"],
 AC_DEFINE_UNQUOTED([EXEEXT], ["$EXEEXT"],
                    [Define to the file extension of executables on the target.])
 
+
+# Locate the directory containing the MySQL utilities, such as the
+# postmaster executable, so we can self-host MySQL databases.
+AC_ARG_WITH([mysql-utils],
+            [AS_HELP_STRING([--with-mysql-utils=DIR],
+                            [path to MySQL utilities.])],
+            [MYSQL_UTILS_PATH=$withval])
+# Path not needed on Windows
+AS_IF([test "x$glom_host_win32" != xyes && test "x$glom_enable_client_only" != xyes],
+      [AS_CASE([$MYSQL_UTILS_PATH], [""|no|yes],
+[
+  # TODO: Check properly instead of hard-coding /usr/bin
+  MYSQL_UTILS_PATH="/usr/bin"
+])])
+
+AC_DEFINE_UNQUOTED([MYSQL_UTILS_PATH], ["$MYSQL_UTILS_PATH"],
+                   [Define to the location of the MySQL utilities.])
+
+
 GNOME_DOC_INIT([0.9.0],,
   [AC_MSG_WARN([[gnome-doc-utils not found: documentation will not be built.]])])
 
diff --git a/glom/libglom/connectionpool.cc b/glom/libglom/connectionpool.cc
index f06c3cf..9224433 100644
--- a/glom/libglom/connectionpool.cc
+++ b/glom/libglom/connectionpool.cc
@@ -29,6 +29,8 @@
 #include <libglom/connectionpool_backends/postgres_central.h>
 #include <libglom/connectionpool_backends/postgres_self.h>
 #include <libglom/connectionpool_backends/sqlite.h>
+#include <libglom/connectionpool_backends/mysql_central.h>
+#include <libglom/connectionpool_backends/mysql_self.h>
 
 #include <glibmm/main.h>
 
@@ -164,6 +166,22 @@ void ConnectionPool::setup_from_document(const Document* document)
       set_backend(std::auto_ptr<ConnectionPool::Backend>(backend));
     }
     break;
+  case Document::HOSTING_MODE_MYSQL_SELF:
+    {
+      ConnectionPoolBackends::MySQLSelfHosted* backend = new ConnectionPoolBackends::MySQLSelfHosted;
+      backend->set_database_directory_uri(document->get_connection_self_hosted_directory_uri());
+      set_backend(std::auto_ptr<ConnectionPool::Backend>(backend));
+    }
+    break;
+  case Document::HOSTING_MODE_MYSQL_CENTRAL:
+    {
+      ConnectionPoolBackends::MySQLCentralHosted* backend = new ConnectionPoolBackends::MySQLCentralHosted;
+      backend->set_host(document->get_connection_server());
+      backend->set_port(document->get_connection_port());
+      backend->set_try_other_ports(document->get_connection_try_other_ports());
+      set_backend(std::auto_ptr<ConnectionPool::Backend>(backend));
+    }
+    break;
 
   default:
     //on_document_load() should have checked for this already, informing the user.
@@ -420,6 +438,7 @@ bool ConnectionPool::save_backup(const SlotProgress& slot_progress, const std::s
   std::string uri;
   try
   {
+    //TODO_MySQL:
     //TODO: Avoid the copy/paste of glom_postgres_data and make it work for sqlite too.
     const std::string subdir = Glib::build_filename(path_dir, "glom_postgres_data");
     uri = Glib::filename_to_uri(subdir);
@@ -440,6 +459,7 @@ bool ConnectionPool::convert_backup(const SlotProgress& slot_progress, const std
 {
   g_assert(m_backend.get());
 
+  //TODO_MySQL:
   //TODO: Avoid this copy/paste of the directory name:
   std::string path_dir_to_use = path_dir;
   if(!path_dir_to_use.empty())
diff --git a/glom/libglom/connectionpool_backends/mysql.cc b/glom/libglom/connectionpool_backends/mysql.cc
new file mode 100644
index 0000000..18ad52b
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql.cc
@@ -0,0 +1,872 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2013 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h" // For MYSQL_UTILS_PATH
+#include <libglom/libglom_config.h>
+
+#include <libglom/connectionpool_backends/mysql.h>
+#include <libglom/spawn_with_feedback.h>
+#include <libglom/utils.h>
+#include <libglom/db_utils.h>
+#include <libgdamm/config.h>
+#include <giomm/file.h>
+#include <glibmm/convert.h>
+#include <glibmm/fileutils.h> //For Glib::file_test().
+#include <glibmm/miscutils.h>
+#include <glibmm/shell.h>
+#include <glib/gstdio.h> /* For g_rename(). TODO: Wrap this in glibmm? */
+#include <glibmm/i18n.h>
+
+#include <iostream>
+
+// Uncomment to see debug messages
+//#define GLOM_CONNECTION_DEBUG
+
+namespace
+{
+
+static Glib::ustring create_auth_string(const Glib::ustring& username, const Glib::ustring& password)
+{
+  if(username.empty() and password.empty())
+    return Glib::ustring();
+  else
+  {
+    Glib::ustring result = "USERNAME=" + Glom::DbUtils::gda_cnc_string_encode(username);
+
+    if(!password.empty()) //There is no password initially, at least with MySQL 5.5:
+    {
+      result +=";PASSWORD=" + Glom::DbUtils::gda_cnc_string_encode(password);
+    }
+
+    return result;
+  }
+}
+
+} //anonymous namespace
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+MySQL::MySQL()
+: m_port(0),
+  m_mysql_server_version(0.0f)
+{
+}
+
+//TODO: Avoid copy/paste with Postgres::attempt_connect()
+Glib::RefPtr<Gnome::Gda::Connection> MySQL::attempt_connect(const Glib::ustring& port, const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection)
+{
+  if(database.empty())
+  {
+    std::cerr << G_STRFUNC << ": The database name is empty. This is strange." << std::endl;
+    return Glib::RefPtr<Gnome::Gda::Connection>();
+  }
+
+  const Glib::ustring default_database = "INFORMATION_SCHEMA";
+  //const Glib::ustring& actual_database = (!database.empty()) ? database : default_database;;
+  const Glib::ustring cnc_string_main = "HOST=" + DbUtils::gda_cnc_string_encode(m_host)
+   + ";PORT=" + DbUtils::gda_cnc_string_encode(port);
+  const Glib::ustring cnc_string = cnc_string_main +";DB_NAME=" + DbUtils::gda_cnc_string_encode(database);
+
+  Glib::RefPtr<Gnome::Gda::Connection> connection;
+  Glib::RefPtr<Gnome::Gda::DataModel> data_model;
+
+  const Glib::ustring auth_string = create_auth_string(username, password);
+
+#ifdef GLOM_CONNECTION_DEBUG
+  std::cout << std::endl << "DEBUG: Glom: trying to connect on port=" << port << std::endl;
+  std::cout << "debug: " << G_STRFUNC << ": cnc_string=" << cnc_string << std::endl;
+  std::cout << "  DEBUG: auth_string=" << auth_string << std::endl;
+#endif
+
+  try
+  {
+    if(fake_connection)
+    {
+      connection = Gnome::Gda::Connection::create_from_string("MySQL",
+        cnc_string, auth_string,
+        Gnome::Gda::CONNECTION_OPTIONS_SQL_IDENTIFIERS_CASE_SENSITIVE);
+    }
+    else
+    {
+      connection = Gnome::Gda::Connection::open_from_string("MySQL",
+        cnc_string, auth_string,
+        Gnome::Gda::CONNECTION_OPTIONS_SQL_IDENTIFIERS_CASE_SENSITIVE);
+
+      //connection->statement_execute_non_select("SET DATESTYLE = 'ISO'");
+      data_model = connection->statement_execute_select("SELECT version()");
+    }
+  }
+  catch(const Glib::Error& ex)
+  {
+#ifdef GLOM_CONNECTION_DEBUG
+    std::cout << "debug: " << G_STRFUNC << ": Attempt to connect to database failed on port=" << port << ", database=" << database << ": " << "error code=" << ex.code() << ", error message: " <<  ex.what() << std::endl;
+    std::cout << "debug: " << G_STRFUNC << ": Attempting to connect without specifying the database." << std::endl;
+#endif
+
+    const Glib::ustring cnc_string = cnc_string_main + ";DB_NAME=" + DbUtils::gda_cnc_string_encode(default_database);
+    Glib::RefPtr<Gnome::Gda::Connection> temp_conn;
+    Glib::ustring auth_string = create_auth_string(username, password);
+    try
+    {
+      temp_conn = Gnome::Gda::Connection::open_from_string("MySQL",
+        cnc_string, auth_string,
+        Gnome::Gda::CONNECTION_OPTIONS_SQL_IDENTIFIERS_CASE_SENSITIVE);
+    }
+    catch(const Glib::Error& /* ex */)
+    {
+      //Show this on stderr because it can contain useful clues such as a hostname that cannot be resolved.
+      std::cerr << G_STRFUNC << ": Attempt to connect to default database failed on port=" << port << " : " << "error code=" << ex.code() << ", error message: " <<  ex.what() << std::endl;
+    }
+
+#ifdef GLOM_CONNECTION_DEBUG
+    if(temp_conn)
+      std::cout << "  (Connection succeeds, but not to the specific database,  database=" << database << std::endl;
+    else
+      std::cerr << "  (Could not connect even to the default database, database=" << database  << std::endl;
+#endif
+
+    throw ExceptionConnection(temp_conn ? ExceptionConnection::FAILURE_NO_DATABASE : ExceptionConnection::FAILURE_NO_SERVER);
+    return Glib::RefPtr<Gnome::Gda::Connection>();
+  }
+
+  if(data_model && data_model->get_n_rows() && data_model->get_n_columns())
+  {
+    const Gnome::Gda::Value value = data_model->get_value_at(0, 0);
+    if(value.get_value_type() == G_TYPE_STRING)
+    {
+      const Glib::ustring version_text = value.get_string();
+      //This seems to have the format "MySQL 7.4.11 on i486-pc-linux"
+      const Glib::ustring namePart = "MySQL ";
+      const Glib::ustring::size_type posName = version_text.find(namePart);
+      if(posName != Glib::ustring::npos)
+      {
+        const Glib::ustring versionPart = version_text.substr(namePart.size());
+        m_mysql_server_version = strtof(versionPart.c_str(), 0);
+
+#ifdef GLOM_CONNECTION_DEBUG
+        std::cout << "  MySQL Server version: " << m_mysql_server_version << std::endl;
+#endif
+      }
+    }
+  }
+
+  return connection;
+}
+
+bool MySQL::change_columns(const Glib::RefPtr<Gnome::Gda::Connection>& connection, const Glib::ustring& table_name, const type_vec_const_fields& old_fields, const type_vec_const_fields& new_fields) throw()
+{
+  static const char TRANSACTION_NAME[] = "glom_change_columns_transaction";
+  static const char TEMP_COLUMN_NAME[] = "glom_temp_column"; // TODO: Find a unique name.
+
+  try
+  {
+    connection->begin_transaction(TRANSACTION_NAME, Gnome::Gda::TRANSACTION_ISOLATION_UNKNOWN); // TODO: What does the transaction isolation do?
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": begin_transaction failed: " << ex.what() << std::endl;
+  }
+
+  //Do this all in one big try/catch, block, 
+  //reverting the transaction if anything fails:
+  try
+  {
+		for(unsigned int i = 0; i < old_fields.size(); ++ i)
+		{
+		  // If the type did change, then we need to recreate the column. See
+		  // http://www.mysqlql.org/docs/faqs.FAQ.html#item4.3
+		  if(old_fields[i]->get_field_info()->get_g_type() != new_fields[i]->get_field_info()->get_g_type())
+		  {
+		    // Create a temporary column
+		    sharedptr<Field> temp_field = glom_sharedptr_clone(new_fields[i]);
+		    temp_field->set_name(TEMP_COLUMN_NAME);
+		    // The temporary column must not be primary key as long as the original
+		    // (primary key) column is still present, because there cannot be two
+		    // primary key columns.
+		    temp_field->set_primary_key(false);
+
+		    const bool added = add_column(connection, table_name, temp_field);
+                    if(!added)
+                    {
+                      std::cerr << G_STRFUNC << ": add_column() failed." << std::endl;
+                      //TODO: Stop the transaction and return?
+                    }
+
+		    Glib::ustring conversion_command;
+		    const Glib::ustring field_name_old_quoted = DbUtils::escape_sql_id(old_fields[i]->get_name());
+		    const Field::glom_field_type old_field_type = old_fields[i]->get_glom_type();
+
+		    if(Field::get_conversion_possible(old_fields[i]->get_glom_type(), new_fields[i]->get_glom_type()))
+		    {
+		      //TODO: mysql seems to give an error if the data cannot be converted (for instance if the text is not a numeric digit when converting to numeric) instead of using 0.
+		      /*
+		      Maybe, for instance:
+		      http://groups.google.de/groups?hl=en&lr=&ie=UTF-8&frame=right&th=a7a62337ad5a8f13&seekm=23739.1073660245%40sss.pgh.pa.us#link5
+		      UPDATE _table
+		      SET _bbb = to_number(substring(_aaa from 1 for 5), '99999')
+		      WHERE _aaa <> '     ';  
+		      */
+
+		      switch(new_fields[i]->get_glom_type())
+		      {
+		        case Field::TYPE_BOOLEAN:
+		        {
+		          if(old_field_type == Field::TYPE_NUMERIC)
+		          {
+		            conversion_command = "(CASE WHEN " + field_name_old_quoted + " > 0 THEN true "
+		                                       "WHEN " + field_name_old_quoted + " = 0 THEN false "
+		                                       "WHEN " + field_name_old_quoted + " IS NULL THEN false END)";
+		          }
+		          else if(old_field_type == Field::TYPE_TEXT)
+		            conversion_command = '(' + field_name_old_quoted + " !~~* \'false\')"; // !~~* means ! ILIKE
+		          else // Dates and Times:
+		            conversion_command = '(' + field_name_old_quoted + " IS NOT NULL)";
+		          break;
+		        }
+
+		        case Field::TYPE_NUMERIC: // CAST does not work if the destination type is numeric
+		        {
+		          if(old_field_type == Field::TYPE_BOOLEAN)
+		          {
+		            conversion_command = "(CASE WHEN " + field_name_old_quoted + " = true THEN 1 "
+		                                       "WHEN " + field_name_old_quoted + " = false THEN 0 "
+		                                       "WHEN " + field_name_old_quoted + " IS NULL THEN 0 END)";
+		          }
+		          else
+		          {
+		            //We use to_number, with textcat() so that to_number always has usable data.
+		            //Otherwise, it says 
+		            //invalid input syntax for type numeric: " "
+		            //
+		            //We must use single quotes with the 0, otherwise it says "column 0 does not exist.".
+		            conversion_command = "to_number( textcat(\'0\', " + field_name_old_quoted + "), '999999999.99999999' )";
+		          }
+
+		          break;
+		        }
+
+		        case Field::TYPE_DATE: // CAST does not work if the destination type is date.
+		        {
+		          conversion_command = "to_date( " + field_name_old_quoted + ", 'YYYYMMDD' )"; // TODO: Standardise date storage format.
+		          break;
+		        }
+		        case Field::TYPE_TIME: // CAST does not work if the destination type is timestamp.
+		        {
+		          conversion_command = "to_timestamp( " + field_name_old_quoted + ", 'HHMMSS' )"; // TODO: Standardise time storage format.
+		          break;
+		        }
+
+		        default:
+		        {
+		          // To Text:
+
+		          // bool to text:
+		          if(old_field_type == Field::TYPE_BOOLEAN)
+		          {
+		            conversion_command = "(CASE WHEN " + field_name_old_quoted + " = true THEN \'true\' "
+		                                       "WHEN " + field_name_old_quoted + " = false THEN \'false\' "
+		                                       "WHEN " + field_name_old_quoted + " IS NULL THEN \'false\' END)";
+		          }
+		          else
+		          {
+		            // This works for most to-text conversions:
+		            conversion_command = "CAST(" + field_name_old_quoted + " AS " + new_fields[i]->get_sql_type() + ")";
+		          }
+
+		          break;
+		        }
+		      }
+
+                      //TODO: Use SqlBuilder here?
+		      connection->statement_execute_non_select("UPDATE " + DbUtils::escape_sql_id(table_name) + " SET " + DbUtils::escape_sql_id(TEMP_COLUMN_NAME) + " = " + conversion_command);
+		    }
+		    else
+		    {
+		      // The conversion is not possible, so drop data in that column
+		    }
+
+		    drop_column(connection, table_name, old_fields[i]->get_name());
+
+		    connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " RENAME COLUMN " + DbUtils::escape_sql_id(TEMP_COLUMN_NAME) + " TO " + DbUtils::escape_sql_id(new_fields[i]->get_name()));
+
+		    // Read primary key constraint
+		    if(new_fields[i]->get_primary_key())
+		    {
+		      connection->statement_execute_non_select("ALTER TABLE  " + DbUtils::escape_sql_id(table_name) + " ADD PRIMARY KEY (" + DbUtils::escape_sql_id(new_fields[i]->get_name()) + ")");
+		    }
+		  }
+		  else
+		  {
+		    // The type did not change. What could have changed: The field being a
+		    // unique key, primary key, its name or its default value.
+
+		    // Primary key
+		    // TODO: Test whether this is able to remove unique key constraints
+		    // added via libgda's DDL API in add_column(). Maybe override
+		    // add_column() if we can't.
+		    bool primary_key_was_set = false;
+		    bool primary_key_was_unset = false;
+		    if(old_fields[i]->get_primary_key() != new_fields[i]->get_primary_key())
+		    {
+		      if(new_fields[i]->get_primary_key())
+		      {
+		        primary_key_was_set = true;
+
+		        // Primary key was added
+		       connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " ADD PRIMARY KEY (" + DbUtils::escape_sql_id(old_fields[i]->get_name()) + ")");
+
+		        // Remove unique key constraint, because this is already implied in
+		        // the field being primary key.
+		        if(old_fields[i]->get_unique_key())
+		          connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " DROP CONSTRAINT " + DbUtils::escape_sql_id(old_fields[i]->get_name() + "_key"));
+		      }
+		      else
+		      {
+		        primary_key_was_unset = true;
+
+		        // Primary key was removed
+		        connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " DROP CONSTRAINT " + DbUtils::escape_sql_id(table_name + "_pkey"));
+		      }
+		    }
+
+		    // Uniqueness
+		    if(old_fields[i]->get_unique_key() != new_fields[i]->get_unique_key())
+		    {
+		      // MySQL automatically makes primary keys unique, so we do not need
+		      // to do that separately if we already made it a primary key
+		      if(!primary_key_was_set && new_fields[i]->get_unique_key())
+		      {
+		        connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " ADD CONSTRAINT " + DbUtils::escape_sql_id(old_fields[i]->get_name() + "_key") + " UNIQUE (" + DbUtils::escape_sql_id(old_fields[i]->get_name()) + ")");
+		      }
+		      else if(!primary_key_was_unset && !new_fields[i]->get_unique_key() && !new_fields[i]->get_primary_key())
+		      {
+		        connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " DROP CONSTRAINT " + DbUtils::escape_sql_id(old_fields[i]->get_name() + "_key"));
+		      }
+		    }
+
+		    if(!new_fields[i]->get_auto_increment()) // Auto-increment fields have special code as their default values.
+		    {
+		      if(old_fields[i]->get_default_value() != new_fields[i]->get_default_value())
+		      {
+		        connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " ALTER COLUMN " + DbUtils::escape_sql_id(old_fields[i]->get_name()) + " SET DEFAULT " + new_fields[i]->sql(new_fields[i]->get_default_value(), connection));
+		      }
+		    }
+
+		    if(old_fields[i]->get_name() != new_fields[i]->get_name())
+		    {
+		      connection->statement_execute_non_select("ALTER TABLE " + DbUtils::escape_sql_id(table_name) + " RENAME COLUMN " + DbUtils::escape_sql_id(old_fields[i]->get_name()) + " TO " + DbUtils::escape_sql_id(new_fields[i]->get_name()));
+		    }
+		  }
+		}
+		
+		connection->commit_transaction(TRANSACTION_NAME);
+		return true;
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << "Exception: " << ex.what() << std::endl;
+    std::cerr << "Reverting the transaction." << std::endl;
+    
+    try
+    {
+      connection->rollback_transaction(TRANSACTION_NAME);
+    }
+    catch(const Glib::Error& ex)
+    {
+      std::cerr << "Could not rollback the transaction: Exception: " << ex.what() << std::endl;
+    }
+  }
+  
+  return false;
+}
+
+bool MySQL::attempt_create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& host, const Glib::ustring& port, const Glib::ustring& username, const Glib::ustring& password)
+{
+  slot_progress();
+
+  Glib::RefPtr<Gnome::Gda::ServerOperation> op = 
+    Gnome::Gda::ServerOperation::prepare_create_database("MySQL", database_name);
+
+  slot_progress();
+
+  g_assert(op);
+  try
+  {
+    op->set_value_at("/SERVER_CNX_P/HOST", host);
+    op->set_value_at("/SERVER_CNX_P/PORT", port);
+    op->set_value_at("/SERVER_CNX_P/ADM_LOGIN", username);
+    op->set_value_at("/SERVER_CNX_P/ADM_PASSWORD", password);
+    op->perform_create_database("MySQL");
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception: " << ex.what() << std::endl;
+    return false;
+  }
+
+  slot_progress();
+
+  return true;
+}
+
+bool MySQL::check_mysql_gda_client_is_available()
+{
+  //This API is horrible.
+  //See libgda bug http://bugzilla.gnome.org/show_bug.cgi?id=575754
+  Glib::RefPtr<Gnome::Gda::DataModel> model = Gnome::Gda::Config::list_providers();
+  if(model && model->get_n_columns() && model->get_n_rows())
+  {
+    Glib::RefPtr<Gnome::Gda::DataModelIter> iter = model->create_iter();
+
+    do
+    {
+      //See http://library.gnome.org/devel/libgda/unstable/libgda-40-Configuration.html#gda-config-list-providers
+      //about the columns of this DataModel:
+      Gnome::Gda::Value name;
+      try
+      {
+        name = iter->get_value_at(0);
+      }
+      catch(const Glib::Error& ex)
+      {
+        std::cerr << G_STRFUNC << ": exception: " << ex.what() << std::endl;
+      }
+
+      if(name.get_value_type() != G_TYPE_STRING)
+        continue;
+
+      const Glib::ustring name_as_string = name.get_string();
+      //std::cout << "DEBUG: Provider name:" << name_as_string << std::endl;
+      if(name_as_string == "MySQL")
+        return true;
+    }
+    while(iter->move_next());
+  }
+
+  return false;
+}
+
+std::string MySQL::get_path_to_mysql_executable(const std::string& program, bool quoted)
+{
+#ifdef G_OS_WIN32
+  // Add the .exe extension on Windows:
+  std::string real_program = program + EXEEXT;
+
+  // Have a look at the bin directory of the application executable first.
+  // The installer installs mysql there. mysql needs to be installed
+  // in a directory called bin for its relocation stuff to work, so that
+  // it finds the share data in share. Unfortunately it does not look into
+  // share/mysqlql which would be nice to separate the mysql stuff
+  // from the other shared data. We can perhaps still change this later by
+  // building mysql with another prefix than /local/pgsql.
+  gchar* installation_directory = g_win32_get_package_installation_directory_of_module(0);
+  std::string test;
+
+  try
+  {
+    test = Glib::build_filename(installation_directory, Glib::build_filename("bin", real_program));
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+    return std::string();
+  }
+
+  g_free(installation_directory);
+
+  if(Glib::file_test(test, Glib::FILE_TEST_IS_EXECUTABLE))
+  {
+    if(quoted)
+      test = Glib::shell_quote(test);
+    return test;
+  }
+
+  // Look in PATH otherwise
+  std::string path = Glib::find_program_in_path(real_program);
+  if(quoted)
+    path = Glib::shell_quote(path);
+  return path;
+#else // G_OS_WIN32
+  // MYSQL_UTILS_PATH is defined in config.h, based on the configure.
+  try
+  {
+    std::string path = Glib::build_filename(MYSQL_UTILS_PATH, program + EXEEXT);
+    if(quoted)
+      path = Glib::shell_quote(path);
+    return path;
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+    return std::string();
+  }
+#endif // !G_OS_WIN32
+}
+
+
+Glib::ustring MySQL::port_as_string(unsigned int port_num)
+{
+  Glib::ustring result;
+  char* cresult = g_strdup_printf("%u", port_num);
+  if(cresult)
+    result = cresult;
+  g_free(cresult);
+
+  return result;
+}
+
+bool MySQL::save_backup(const SlotProgress& slot_progress, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& /* database_name */)
+{
+/* TODO:
+  if(m_network_shared && !running)
+  {
+    std::cerr << G_STRFUNC << ": The self-hosted database is not running." << std::endl;
+    return;
+  }
+*/
+
+  if(m_host.empty())
+  {
+    std::cerr << G_STRFUNC << ": m_host is empty." << std::endl;
+    return false;
+  }
+
+  if(m_port == 0)
+  {
+    std::cerr << G_STRFUNC << ": m_port is empty." << std::endl;
+    return false;
+  }
+
+  //TODO: Remember the existing username and password?
+  if(username.empty())
+  {
+    std::cerr << G_STRFUNC << ": username is empty." << std::endl;
+    return false;
+  }
+
+  if(password.empty())
+  {
+    std::cerr << G_STRFUNC << ": password is empty." << std::endl;
+    return false;
+  }
+
+  const std::string path_backup = get_self_hosting_backup_path(std::string(), true /* create parent directory if necessary */);
+  if(path_backup.empty())
+    return false;
+
+  // Make sure to use double quotes for the executable path, because the
+  // CreateProcess() API used on Windows does not support single quotes.
+  const std::string command_dump; //TODO_MySQL
+
+
+  //std::cout << "DEBUG: command_dump=" << command_dump << std::endl;
+
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_dump, slot_progress);
+
+  if(!result)
+  {
+    std::cerr << "Error while attempting to call pg_dump." << std::endl;
+  }
+
+  return result;
+}
+
+bool MySQL::convert_backup(const SlotProgress& slot_progress, const std::string& base_directory, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& /* database_name */)
+{
+/* TODO:
+  if(m_network_shared && !running)
+  {
+    std::cerr << G_STRFUNC << ": The self-hosted database is not running." << std::endl;
+    return;
+  }
+*/
+
+  if(m_host.empty())
+  {
+    std::cerr << G_STRFUNC << ": m_host is empty." << std::endl;
+    return false;
+  }
+
+  if(m_port == 0)
+  {
+    std::cerr << G_STRFUNC << ": m_port is empty." << std::endl;
+    return false;
+  }
+
+  //TODO: Remember the existing username and password?
+  if(username.empty())
+  {
+    std::cerr << G_STRFUNC << ": username is empty." << std::endl;
+    return false;
+  }
+
+  if(password.empty())
+  {
+    std::cerr << G_STRFUNC << ": password is empty." << std::endl;
+    return false;
+  }
+
+  //Make sure the path exists:
+  const std::string path_backup = get_self_hosting_backup_path(base_directory);
+  if(path_backup.empty() || !file_exists_filepath(path_backup))
+  {
+    std::cerr << G_STRFUNC << ": Backup file not found: " << path_backup << std::endl;
+    return false;
+  }
+
+  // Make sure to use double quotes for the executable path, because the
+  // CreateProcess() API used on Windows does not support single quotes.
+  const std::string command_restore; //TODO_MySQL
+
+  std::cout << "DEBUG: command_restore=" << command_restore << std::endl;
+
+  //TODO: Put the password in .pgpass
+
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_restore, slot_progress);
+
+  if(!result)
+  {
+    std::cerr << "Error while attempting to call pg_restore." << std::endl;
+  }
+
+  return result;
+}
+
+std::string MySQL::get_self_hosting_path(bool create, const std::string& child_directory)
+{
+  //Get the filepath of the directory that we should create:
+  const std::string dbdir_uri = m_database_directory_uri;
+  //std::cout << "debug: dbdir_uri=" << dbdir_uri << std::endl;
+
+  std::string dbdir;
+  try
+  {
+    dbdir = Glib::build_filename(
+      Glib::filename_from_uri(dbdir_uri), child_directory);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+  }
+
+  if(file_exists_filepath(dbdir))
+    return dbdir;
+  else if(!create)
+    return std::string();
+
+  //Create the directory:
+
+  //std::cout << "debug: dbdir=" << dbdir << std::endl;
+  g_assert(!dbdir.empty());
+
+  if(create_directory_filepath(dbdir))
+    return dbdir;
+  else
+    return std::string();
+}
+
+std::string MySQL::get_self_hosting_config_path(bool create)
+{
+  return get_self_hosting_path(create, "config");
+}
+
+std::string MySQL::get_self_hosting_data_path(bool create)
+{
+  return get_self_hosting_path(create, "data");
+}
+
+std::string MySQL::get_self_hosting_backup_path(const std::string& base_directory, bool create_parent_dir)
+{
+  //This is a file, not a directory, so we don't use get_self_hosting_path("backup");
+  std::string dbdir;
+  if(base_directory.empty())
+    dbdir = get_self_hosting_path(create_parent_dir);
+  else
+  {
+    dbdir = base_directory;
+  }
+
+  if(dbdir.empty())
+    return std::string();
+
+  try
+  {
+    return Glib::build_filename(dbdir, "backup");
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception from Glib::build_filename(): " << ex.what() << std::endl;
+    return std::string();
+  }
+}
+
+bool MySQL::create_directory_filepath(const std::string& filepath)
+{
+  if(filepath.empty())
+    return false;
+
+  const int mkdir_succeeded = g_mkdir_with_parents(filepath.c_str(), 0770);
+  if(mkdir_succeeded == -1)
+  {
+    std::cerr << G_STRFUNC << ": Error from g_mkdir_with_parents() while trying to create directory: " << filepath << std::endl;
+    perror("  perror(): Error from g_mkdir_with_parents()");
+
+    return false;
+  }
+
+  return true;
+}
+
+bool MySQL::file_exists_filepath(const std::string& filepath)
+{
+  if(filepath.empty())
+    return false;
+
+  const Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(filepath);
+  return file && file->query_exists();
+}
+
+bool MySQL::file_exists_uri(const std::string& uri) const
+{
+  if(uri.empty())
+    return false;
+
+  const Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(uri);
+  return file && file->query_exists();
+}
+
+
+
+bool MySQL::create_text_file(const std::string& file_uri, const std::string& contents, bool current_user_only)
+{
+  if(file_uri.empty())
+    return false;
+
+  Glib::RefPtr<Gio::File> file = Gio::File::create_for_uri(file_uri);
+  Glib::RefPtr<Gio::FileOutputStream> stream;
+
+  //Create the file if it does not already exist:
+#ifdef GLIBMM_EXCEPTIONS_ENABLED
+  try
+  {
+    if(file->query_exists())
+    {
+      if(current_user_only)
+      {
+        stream = file->replace(std::string() /* etag */, false /* make_backup */, Gio::FILE_CREATE_PRIVATE); //Instead of append_to().
+      }
+      else
+      {
+         stream = file->replace(); //Instead of append_to().
+      }
+    }
+    else
+    {
+      //By default files created are generally readable by everyone, but if we pass FILE_CREATE_PRIVATE in flags the file will be made readable only to the current user, to the level that is supported on the target filesystem.
+      if(current_user_only)
+      {
+      //TODO: Do we want to specify 0660 exactly? (means "this user and his group can read and write this non-executable file".)
+        stream = file->create_file(Gio::FILE_CREATE_PRIVATE);
+      }
+      else
+      {
+        stream = file->create_file();
+      }
+    }
+  }
+  catch(const Gio::Error& ex)
+  {
+#else
+  std::auto_ptr<Gio::Error> error;
+  stream.create(error);
+  if(error.get())
+  {
+    const Gio::Error& ex = *error.get();
+#endif
+    // If the operation was not successful, print the error and abort
+    std::cerr << "ConnectionPool::create_text_file(): exception while creating file." << std::endl
+      << "  file uri:" << file_uri << std::endl
+      << "  error:" << ex.what() << std::endl;
+    return false; // print_error(ex, output_uri_string);
+  }
+
+
+  if(!stream)
+    return false;
+
+
+  gssize bytes_written = 0;
+  const std::string::size_type contents_size = contents.size();
+#ifdef GLIBMM_EXCEPTIONS_ENABLED
+  try
+  {
+    //Write the data to the output uri
+    bytes_written = stream->write(contents.data(), contents_size);
+  }
+  catch(const Gio::Error& ex)
+  {
+#else
+  bytes_written = stream->write(contents.data(), contents_size, error);
+  if(error.get())
+  {
+    Gio::Error& ex = *error.get();
+#endif
+    // If the operation was not successful, print the error and abort
+    std::cerr << "ConnectionPool::create_text_file(): exception while writing to file." << std::endl
+      << "  file uri:" << file_uri << std::endl
+      << "  error:" << ex.what() << std::endl;
+    return false; //print_error(ex, output_uri_string);
+  }
+
+  if(bytes_written != (gssize)contents_size)
+  {
+    std::cerr << "ConnectionPool::create_text_file(): not all bytes written when writing to file." << std::endl
+      << "  file uri:" << file_uri << std::endl;
+    return false;
+  }
+
+  return true; //Success.
+}
+
+bool MySQL::supports_remote_access() const
+{
+  return true;
+}
+
+Gnome::Gda::SqlOperatorType MySQL::get_string_find_operator() const
+{
+  //TODO_MySQL:
+  // ILIKE is a MySQL extension for locale-dependent case-insensitive matches.
+  //See http://developer.mysqlql.org/pgdocs/mysql/functions-matching.html
+  return Gnome::Gda::SQL_OPERATOR_TYPE_ILIKE;
+}
+
+const char* MySQL::get_public_schema_name() const
+{
+  return ""; //TODO_MySQL: Find out what to use here, to speed up the metadata update.
+}
+
+} //namespace ConnectionPoolBackends
+
+} //namespace Glom
diff --git a/glom/libglom/connectionpool_backends/mysql.h b/glom/libglom/connectionpool_backends/mysql.h
new file mode 100644
index 0000000..218cccc
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql.h
@@ -0,0 +1,114 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2013 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GLOM_BACKEND_MYSQL_H
+#define GLOM_BACKEND_MYSQL_H
+
+#include <libgdamm/connection.h>
+#include <libglom/connectionpool_backends/backend.h>
+
+#include <libglom/libglom_config.h>
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+class MySQL : public Backend
+{
+public:
+  MySQL();
+
+  /** Check whether the libgda mysql provider is really available,
+   * so we can connect to mysql servers,
+   * in case the distro package has incorrect dependencies.
+   *
+   * @results True if everything is OK.
+   */
+  static bool check_mysql_gda_client_is_available();
+
+  /** Save a backup file, using the same directory layout as used by self-hosting.
+   */
+  virtual bool save_backup(const SlotProgress& slot_progress, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& database_name);
+
+  virtual bool convert_backup(const SlotProgress& slot_progress, const std::string& base_directory, const Glib::ustring& username, const Glib::ustring& password, const Glib::ustring& database_name);
+
+  /** Return the quoted path to the specified PostgreSQL utility.
+   */
+  static std::string get_path_to_mysql_executable(const std::string& program, bool quoted = true);
+
+
+
+private:
+  virtual bool supports_remote_access() const;
+  virtual Gnome::Gda::SqlOperatorType get_string_find_operator() const;
+  virtual const char* get_public_schema_name() const;
+
+  virtual bool change_columns(const Glib::RefPtr<Gnome::Gda::Connection>& connection, const Glib::ustring& table_name, const type_vec_const_fields& old_fields, const type_vec_const_fields& new_fields) throw();
+
+protected:
+  bool attempt_create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& host, const Glib::ustring& port, const Glib::ustring& username, const Glib::ustring& password);
+
+  /** Attempt to connect to the database with the specified criteria.
+   * @throws An ExceptionConnection if the correction failed.
+   */ 
+  Glib::RefPtr<Gnome::Gda::Connection> attempt_connect(const Glib::ustring& port, const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection);
+
+ std::string get_self_hosting_path(bool create = false, const std::string& child_directory = std::string());
+
+  /** Get the path to the config sub-directory, optionally creating it.
+   */
+  std::string get_self_hosting_config_path(bool create = false);
+
+  /** Get the path to the data sub-directory, optionally creating it.
+   */
+  std::string get_self_hosting_data_path(bool create = false);
+
+  /** Get the path to the backup file, regardless of whether it exists.
+   * @param base_directory Where to find the backup file, under a normal Glom directory structure.
+   * If @a base_directory is empty then it uses get_database_directory_uri().
+   */
+  std::string get_self_hosting_backup_path(const std::string& base_directory = std::string(), bool create_parent_dir = false);
+
+  bool create_directory_filepath(const std::string& filepath);
+  bool file_exists_filepath(const std::string& filepath);
+  bool file_exists_uri(const std::string& uri) const;
+
+  /**
+   * @param current_user_only If true then only the current user will be able to read or write the file.
+   */
+  static bool create_text_file(const std::string& file_uri, const std::string& contents, bool current_user_only = false);
+
+protected:
+  static Glib::ustring port_as_string(unsigned int port_num);
+
+  Glib::ustring m_host;
+  unsigned int m_port;
+
+private:
+  float m_mysql_server_version;
+};
+
+} //namespace ConnectionPoolBackends
+
+} //namespace Glom
+
+#endif //GLOM_BACKEND_MYSQL_H
diff --git a/glom/libglom/connectionpool_backends/mysql_central.cc b/glom/libglom/connectionpool_backends/mysql_central.cc
new file mode 100644
index 0000000..5bbbda6
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_central.cc
@@ -0,0 +1,170 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2004 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include <libglom/libglom_config.h>
+
+#include <libglom/connectionpool_backends/mysql_central.h>
+#include <glibmm/i18n.h>
+
+// Uncomment to see debug messages
+//#define GLOM_CONNECTION_DEBUG
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+MySQLCentralHosted::MySQLCentralHosted()
+: m_try_other_ports(true)
+{
+  //TODO_MySQL:
+  m_list_ports.push_back("5432"); //Ubuntu Breezy seems to default to this for MySQL 7.4, and this is probably the default for most mysql installations, including Fedora.
+
+  m_list_ports.push_back("5433"); //Ubuntu Dapper seems to default to this for MySQL 8.1, probably to avoid a clash with MySQL 7.4
+
+  m_list_ports.push_back("5434"); //Earlier versions of Ubuntu Feisty defaulted to this for MySQL 8.2.
+  m_list_ports.push_back("5435"); //In case Ubuntu increases the port number again in future.
+  m_list_ports.push_back("5436"); //In case Ubuntu increases the port number again in future.
+}
+
+void MySQLCentralHosted::set_host(const Glib::ustring& value)
+{
+  if(value != m_host)
+  {
+    m_host = value;
+
+    // Force us to try all ports again when connecting for the first time, then remember the working port again. Except when a specific port was set to be used.
+    if(m_try_other_ports)
+      m_port = 0;
+  }
+}
+
+void MySQLCentralHosted::set_port(unsigned int port)
+{
+  m_port = port;
+}
+
+void MySQLCentralHosted::set_try_other_ports(bool val)
+{
+  m_try_other_ports = val;
+}
+
+Glib::ustring MySQLCentralHosted::get_host() const
+{
+  return m_host;
+}
+
+unsigned int MySQLCentralHosted::get_port() const
+{
+  return m_port;
+}
+
+bool MySQLCentralHosted::get_try_other_ports() const
+{
+  return m_try_other_ports;
+}
+
+Glib::RefPtr<Gnome::Gda::Connection> MySQLCentralHosted::connect(const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection)
+{
+  Glib::RefPtr<Gnome::Gda::Connection> connection;
+
+  //Try each possible network port:
+  type_list_ports::const_iterator iter_port = m_list_ports.begin();
+
+  //Start with the remembered-as-working port:
+  Glib::ustring port = port_as_string(m_port);
+  if(m_port == 0)
+    port = *iter_port ++;
+
+  bool connection_possible = false;
+  try
+  {
+    connection = attempt_connect(port, database, username, password, fake_connection);
+    connection_possible = true;
+    m_port = atoi(port.c_str());
+  }
+  catch(const ExceptionConnection& ex)
+  {
+    // Remember port if only the database was missing
+    connection_possible = false;
+    if(ex.get_failure_type() == ExceptionConnection::FAILURE_NO_DATABASE)
+    {
+      connection_possible = true;
+      m_port = atoi(port.c_str());
+    }
+  }
+
+  // Try more ports if so desired, and we don't have a connection yet
+  if(m_try_other_ports && !connection)
+  {
+    while(!connection && iter_port != m_list_ports.end())
+    {
+      port = *iter_port;
+
+      try
+      {
+        connection = attempt_connect(port, database, username, password, fake_connection);
+        connection_possible = true;
+        m_port = atoi(port.c_str());
+      }
+      catch(const ExceptionConnection& ex)
+      {
+        //Don't set this, because we might have previously set it to true to 
+        //show that a connection was possible with a previously-tried port: connection_possible = false;
+
+        // Remember port if only the database was missing
+        if(ex.get_failure_type() == ExceptionConnection::FAILURE_NO_DATABASE)
+        {
+          connection_possible = true;
+          m_port = atoi(port.c_str());
+        }
+      }
+
+      // Skip if we already tried this port
+      if(iter_port != m_list_ports.end() && *iter_port == port)
+        ++ iter_port;
+    }
+  }
+
+  if(connection)
+  {
+    //Remember working port:
+    m_port = atoi(port.c_str());
+  }
+  else
+  {
+    if(connection_possible)
+      throw ExceptionConnection(ExceptionConnection::FAILURE_NO_DATABASE);
+    else
+      throw ExceptionConnection(ExceptionConnection::FAILURE_NO_SERVER);
+  }
+
+  return connection;
+}
+
+bool MySQLCentralHosted::create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& username, const Glib::ustring& password)
+{
+  return attempt_create_database(slot_progress, database_name, get_host(), port_as_string(m_port), username, password);
+}
+
+}
+
+}
diff --git a/glom/libglom/connectionpool_backends/mysql_central.h b/glom/libglom/connectionpool_backends/mysql_central.h
new file mode 100644
index 0000000..8bdbbfb
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_central.h
@@ -0,0 +1,66 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2004 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GLOM_BACKEND_MYSQL_CENTRAL_H
+#define GLOM_BACKEND_MYSQL_CENTRAL_H
+
+#include <libglom/connectionpool_backends/mysql.h>
+
+#include <libglom/libglom_config.h>
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+class MySQLCentralHosted : public MySQL
+{
+public:
+  MySQLCentralHosted();
+
+  /** 0 means any port
+   * Other ports will be tried if the specified port fails.
+   */
+  void set_host(const Glib::ustring& value);
+  void set_port(unsigned int port);
+  void set_try_other_ports(bool val);
+
+  Glib::ustring get_host() const;
+  unsigned int get_port() const;
+  bool get_try_other_ports() const;
+
+private:
+  virtual Glib::RefPtr<Gnome::Gda::Connection> connect(const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection = false);
+
+  virtual bool create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& username, const Glib::ustring& password);
+
+private:
+  typedef std::vector<Glib::ustring> type_list_ports;
+  type_list_ports m_list_ports;
+
+  bool m_try_other_ports;
+};
+
+} //namespace ConnectionPoolBackends
+
+} //namespace Glom
+
+#endif //GLOM_BACKEND_MYSQL_CENTRAL_H
diff --git a/glom/libglom/connectionpool_backends/mysql_self.cc b/glom/libglom/connectionpool_backends/mysql_self.cc
new file mode 100644
index 0000000..6ce6b64
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_self.cc
@@ -0,0 +1,735 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2013 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include <libglom/connectionpool_backends/mysql_self.h>
+#include <libglom/connectionpool.h>
+#include <libglom/utils.h>
+#include <libglom/db_utils.h>
+#include <libglom/spawn_with_feedback.h>
+#include <giomm/file.h>
+#include <glib/gstdio.h> // For g_remove
+
+#include <glibmm/convert.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/stringutils.h>
+#include <glibmm/timer.h>
+#include <glibmm/regex.h>
+#include <glibmm/main.h>
+#include <glibmm/shell.h>
+#include <glibmm/i18n.h>
+
+#include <libglom/gst-package.h>
+#include <sstream> //For stringstream
+#include <iostream>
+
+#ifdef G_OS_WIN32
+# include <windows.h>
+# include <winsock2.h>
+#else
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <errno.h>
+# include <netinet/in.h> //For sockaddr_in
+#endif
+
+#include <signal.h> //To catch segfaults
+
+// Uncomment to see debug messages
+//#define GLOM_CONNECTION_DEBUG
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+static const int PORT_MYSQL_SELF_HOSTED_START = 3306;
+static const int PORT_MYSQL_SELF_HOSTED_END = 3350;
+
+static const char FILENAME_DATA[] = "data";
+static const char FILENAME_BACKUP[] = "backup";
+
+static const char DEFAULT_DATABASE_NAME[] = "INFORMATION_SCHEMA";
+
+MySQLSelfHosted::MySQLSelfHosted()
+: m_network_shared(false),
+  m_temporary_password_active(false)
+{
+  m_host = "localhost";
+}
+
+bool MySQLSelfHosted::get_self_hosting_active() const
+{
+  return m_port != 0;
+}
+
+unsigned int MySQLSelfHosted::get_port() const
+{
+  return m_port;
+}
+
+/** Try to install mysql on the distro, though this will require a
+ * distro-specific patch to the implementation.
+ */
+bool MySQLSelfHosted::install_mysql(const SlotProgress& /* slot_progress */)
+{
+#if 0
+  // This  is example code for Ubuntu, and possibly Debian,
+  // using code from the gnome-system-tools Debian/Ubuntu patches.
+  // (But please, just fix the dependencies instead. MySQL is not optional.)
+  //
+  // You will also need to remove the "ifdef 0"s around the code in gst-package.[h|c],
+  // and define DISTRO_SPECIFIC_MYSQL_INSTALL_IMPLEMENTED above.
+
+  //Careful. Maybe you want a different version.
+  //Also, Glom will start its own instance of MySQL, on its own port, when it needs to,
+  //so there is no need to start a Glom service after installation at system startup,
+  //though it will not hurt Glom if you do that.
+  const gchar *packages[] = { "mysqlql-8.1", 0 };
+  const bool result = gst_packages_install(parent_window->gobj() /* parent window */, packages);
+  if(result)
+  {
+    std::cout << "Glom: gst_packages_install() reports success." << std::endl;
+    //Double-check, because gst_packages_install() incorrectly returns TRUE if it fails because
+    //a) synaptic is already running, or
+    //b) synaptic did not know about the package (no warning is shown in this case.)
+    //Maybe gst_packages_install() never returns FALSE.
+    return check_mysql_is_available_with_warning(); //This is recursive, but clicking Cancel will stop everything.
+  }
+  else
+  {
+    std::cout << "Glom: gst_packages_install() reports failure." << std::endl;
+    return false; //Failed to install mysql.
+  }
+#else
+  return false; //Failed to install mysql because no installation technique was implemented.
+#endif // #if 0
+}
+
+Backend::InitErrors MySQLSelfHosted::initialize(const SlotProgress& slot_progress, const Glib::ustring& initial_username, const Glib::ustring& password, bool network_shared)
+{
+  m_network_shared = network_shared;
+
+  if(m_database_directory_uri.empty())
+  {
+    std::cerr << G_STRFUNC << ": initialize: m_self_hosting_data_uri is empty." << std::endl;
+    return INITERROR_OTHER;
+  }
+
+  if(initial_username.empty())
+  {
+    std::cerr << "MySQLSelfHosted::initialize(). Username was empty while attempting to create self-hosting database" << std::endl;
+    return INITERROR_OTHER;
+  }
+
+  //Get the filepath of the directory that we should create:
+  const std::string dbdir_uri = m_database_directory_uri;
+  //std::cout << "debug: dbdir_uri=" << dbdir_uri << std::endl;
+
+  if(file_exists_uri(dbdir_uri))
+    return INITERROR_DIRECTORY_ALREADY_EXISTS;
+
+  const std::string dbdir = Glib::filename_from_uri(dbdir_uri);
+  //std::cout << "debug: dbdir=" << dbdir << std::endl;
+  g_assert(!dbdir.empty());
+
+  const bool dbdir_created = create_directory_filepath(dbdir);
+  if(!dbdir_created)
+  {
+    std::cerr << "Couldn't create directory: " << dbdir << std::endl;
+
+    return INITERROR_COULD_NOT_CREATE_DIRECTORY;
+  }
+
+  //Create these files: environment
+  set_network_shared(slot_progress, m_network_shared);
+
+  //Check that there is not an existing data directory:
+  const std::string dbdir_data = get_self_hosting_data_path(true /* create */);
+  if(dbdir_data.empty())
+  {
+    std::cerr << "Couldn't create the data directory: " << dbdir << std::endl;
+
+    return INITERROR_COULD_NOT_CREATE_DIRECTORY;
+  }
+
+  // initdb creates a new mysql database cluster:
+
+  // Make sure to use double quotes for the executable path, because the
+  // CreateProcess() API used on Windows does not support single quotes.
+  // We don't use mysql_secure_installation because it only takes the details via prompts.
+  // TODO: With MySQL 5.6, use the new --random-passwords option, because otherwise the root password will be blank,
+  // and, at least on Ubuntu, we will then not be able to connect with mysqladmin.
+  const std::string command_initdb = get_path_to_mysql_executable("mysql_install_db")
+    + " --no-defaults" //Otherwise Ubuntu specifies --user=mysql
+    + " --datadir=" + Glib::shell_quote(dbdir_data);
+    //TODO: + " --random-passwords";
+  //std::cout << "debug: command_initdb=" << command_initdb << std::endl;
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_initdb, slot_progress);
+  if(!result)
+  {
+    std::cerr << "Error while attempting to create self-hosting MySQL database." << std::endl;
+  }
+  else
+  {
+    //std::cout << "debug: command_initdb succeeded" << ", this=" << this << std::endl;
+  
+    //This is used during the first start:
+    m_initial_password_to_set = password;
+    m_initial_username_to_set = initial_username;
+
+    //TODO: With MySQL 5.6, use the new --random-passwords option (see above)
+    m_temporary_password = "";
+    m_temporary_password_active = true;
+    m_saved_username = "root";
+    m_saved_password = "";
+
+    //Startup (and shutdown) so we can set the initial username and password.
+    //TODO: This is inefficient, because the caller probably wants to start the server soon anyway,
+    //but that might be in a different instance of this backend,
+    //and we cannot take the risk of leaving the database with a default password.
+    if(startup(slot_progress, false) != STARTUPERROR_NONE)
+    {
+      std::cerr << "Error while attempting to create self-hosting MySQL database, while starting for the first time, to set the initial username and password." << std::endl;
+      return INITERROR_OTHER;
+    }
+    else
+    {
+      if(!cleanup(slot_progress))
+      {
+        std::cerr << "Error while attempting to create self-hosting MySQL database, while shutting down, after setting the initial username and password." << std::endl;
+        return INITERROR_OTHER;
+      }
+    }
+
+    //Get the temporary random password,
+    //which will be used when first starting the server.
+    /*
+    const std::string temporary_password_file = Glib::build_filename(
+      Glib::get_home_dir(), ".mysql.secret");
+    try
+    {
+      m_temporary_password = Glib::file_get_contents(temporary_password_file);
+      m_temporary_password_active = true;
+    }
+    catch(const Glib::Error& ex)
+    {
+      std::cerr << G_STRFUNC << "file_get_contents() failed: " << ex.what() << std::endl;
+    }
+
+    if(m_temporary_password.empty())
+    {
+       std::cerr << G_STRFUNC << " Unable to discover the initial MySQL password." << std::endl;
+       result = false;
+    }
+    */
+  }
+
+  return result ? INITERROR_NONE : INITERROR_COULD_NOT_START_SERVER;
+}
+
+Glib::ustring MySQLSelfHosted::get_mysqlql_utils_version(const SlotProgress& /* slot_progress */)
+{
+  return Glib::ustring(); //TODO
+}
+
+float MySQLSelfHosted::get_mysqlql_utils_version_as_number(const SlotProgress& /* slot_progress */)
+{
+  return 0; //TODO
+}
+
+static Glib::ustring build_query_change_username(const Glib::RefPtr<Gnome::Gda::Connection>& connection, const Glib::ustring& old_username, const Glib::ustring& new_username)
+{
+  if(old_username.empty())
+  {
+    std::cerr << G_STRFUNC << ": old_username is empty." << std::endl;
+    return Glib::ustring();
+  }
+
+  if(new_username.empty())
+  {
+    std::cerr << G_STRFUNC << ": new_username is empty." << std::endl;
+    return Glib::ustring();
+  }
+
+  //TODO: Try to avoid specifing @localhost.
+  //We do this to avoid this error:
+  //mysql> RENAME USER root TO glom_dev_user;
+  //ERROR 1396 (HY000): Operation RENAME USER failed for 'root'@'%'
+  //mysql> RENAME USER root localhost TO glom_dev_user;
+  //Query OK, 0 rows affected (0.00 sec)
+  const Glib::ustring user = connection->quote_sql_identifier(old_username) + "@localhost";
+
+  //Login will fail after restart if we don't specify @localhost here too:
+  const Glib::ustring new_user = connection->quote_sql_identifier(new_username) + "@localhost";
+
+  return "RENAME USER " + user + " TO " + new_user;
+}
+
+Backend::StartupErrors MySQLSelfHosted::startup(const SlotProgress& slot_progress, bool network_shared)
+{
+  m_network_shared = network_shared;
+
+  // Don't risk random crashes, although this really shouldn't be called
+  // twice of course.
+  //g_assert(!get_self_hosting_active());
+
+  if(get_self_hosting_active())
+  {
+    std::cerr << G_STRFUNC << ": Already started." << std::endl;
+    return STARTUPERROR_NONE; //Just do it once.
+  }
+
+  const std::string dbdir_uri = m_database_directory_uri;
+
+  if(!(file_exists_uri(dbdir_uri)))
+  {
+    //TODO: Use a return enum or exception so we can tell the user about this:
+    std::cerr << G_STRFUNC << ": The data directory could not be found: " << dbdir_uri << std::endl;
+    return STARTUPERROR_FAILED_UNKNOWN_REASON;
+  }
+
+  const std::string dbdir = Glib::filename_from_uri(dbdir_uri);
+  g_assert(!dbdir.empty());
+
+  const std::string dbdir_data = Glib::build_filename(dbdir, FILENAME_DATA);
+  const Glib::ustring dbdir_data_uri = Glib::filename_to_uri(dbdir_data);
+  if(!(file_exists_uri(dbdir_data_uri)))
+  {
+    const std::string dbdir_backup = Glib::build_filename(dbdir, FILENAME_BACKUP);
+    const Glib::ustring dbdir_backup_uri = Glib::filename_to_uri(dbdir_backup);
+    if(file_exists_uri(dbdir_backup_uri))
+    {
+      std::cerr << G_STRFUNC << ": There is no data, but there is backup data." << std::endl;
+      //Let the caller convert the backup to real data and then try again:
+      return STARTUPERROR_FAILED_NO_DATA_HAS_BACKUP_DATA;
+    }
+    else
+    {
+      std::cerr << G_STRFUNC << ": The data sub-directory could not be found." << dbdir_data_uri << std::endl;
+      return STARTUPERROR_FAILED_NO_DATA;
+    }
+  }
+
+  //Attempt to ensure that the config files are correct:
+  set_network_shared(slot_progress, m_network_shared);
+
+  const unsigned int available_port = discover_first_free_port(PORT_MYSQL_SELF_HOSTED_START, PORT_MYSQL_SELF_HOSTED_END);
+  //std::cout << "debug: " << G_STRFUNC << ":() : debug: Available port for self-hosting: " << available_port << std::endl;
+  if(available_port == 0)
+  {
+    //TODO: Use a return enum or exception so we can tell the user about this:
+    std::cerr << G_STRFUNC << ": No port was available between " << PORT_MYSQL_SELF_HOSTED_START << " and " << PORT_MYSQL_SELF_HOSTED_END << std::endl;
+    return STARTUPERROR_FAILED_UNKNOWN_REASON;
+  }
+
+  //TODO: Performance:
+  const std::string port_as_text = Glib::Ascii::dtostr(available_port);
+
+  // Make sure to use double quotes for the executable path, because the
+  // CreateProcess() API used on Windows does not support single quotes.
+  const std::string dbdir_pid = Glib::build_filename(dbdir, "pid");
+  const std::string dbdir_socket = Glib::build_filename(dbdir, "mysqld.sock");
+  const std::string command_mysql_start = get_path_to_mysql_executable("mysqld_safe")
+                                  + " --no-defaults"
+                                  + " --port=" + port_as_text
+                                  + " --datadir=" + Glib::shell_quote(dbdir_data)
+                                  + " --socket=" + Glib::shell_quote(dbdir_socket)
+                                  + " --pid-file=" + Glib::shell_quote(dbdir_pid);
+  //std::cout << G_STRFUNC << ": debug: command_mysql_start=" << command_mysql_start << std::endl;
+
+  m_port = available_port; //Needed by get_mysqladmin_command().
+  const std::string command_check_mysql_has_started = get_mysqladmin_command(m_saved_username, m_saved_password) //TODO: Get the temporary password in a callback.
+    + " ping";
+  const std::string second_command_success_text = "mysqld is alive"; //TODO: This is not a stable API. Also, watch out for localisation.
+  //std::cout << G_STRFUNC << ": debug: command_check_mysql_has_started=" << command_check_mysql_has_started << std::endl;
+
+  const bool result = Glom::Spawn::execute_command_line_and_wait_until_second_command_returns_success(command_mysql_start, command_check_mysql_has_started, slot_progress, second_command_success_text);
+  std::cout << G_STRFUNC << std::cout << "  DEBUG: started" << std::endl;
+
+  if(!result)
+  {
+    m_port = 0;
+
+    std::cerr << "Error while attempting to self-host a MySQL database." << std::endl;
+    return STARTUPERROR_FAILED_UNKNOWN_REASON;
+  }
+
+  m_port = available_port; //Remember it for later.
+
+  //If necessary, set the initial root password and rename the root user:
+  if(m_temporary_password_active)
+  {
+    //Set the root password:
+    const std::string command_initdb_set_initial_password = get_mysqladmin_command("root", m_temporary_password)
+      + " password " + Glib::shell_quote(m_initial_password_to_set);
+    //std::cout << "debug: command_initdb_set_initial_password=" << command_initdb_set_initial_password << std::endl;
+
+    const bool result = Glom::Spawn::execute_command_line_and_wait(command_initdb_set_initial_password, slot_progress);
+
+    if(!result)
+    {
+      std::cerr << "Error while attempting to start self-hosting MySQL database, when setting the initial password." << std::endl;
+      return STARTUPERROR_FAILED_UNKNOWN_REASON;
+    }
+
+    m_temporary_password_active = false;
+    m_temporary_password.clear();
+
+    //Rename the root user,
+    //so we can connnect as the expected username:
+    //We connect to the INFORMATION_SCHEMA database, because libgda needs us to specify some database.
+    const Glib::RefPtr<Gnome::Gda::Connection> gda_connection = connect(DEFAULT_DATABASE_NAME, "root", m_initial_password_to_set);
+    if(!gda_connection)
+    {
+      std::cerr << G_STRFUNC << "Error while attempting to start self-hosting MySQL database, when setting the initial username: connection failed." << std::endl;
+      return STARTUPERROR_FAILED_UNKNOWN_REASON;
+    }
+    m_saved_password = m_initial_password_to_set;
+
+    const std::string query = build_query_change_username(gda_connection, "root", m_initial_username_to_set);
+    //std::cout << G_STRFUNC << std::cout << "  DEBUG: rename user query=" << query << std::endl;
+
+    try
+    {
+      /* const bool test = */ gda_connection->statement_execute_non_select(query);
+      //This returns false even when the UPDATE succeeded,
+      //but throws an exception when it fail.
+      /*
+      if(!test)
+      {
+        std::cerr << G_STRFUNC << "Error while attempting to start self-hosting MySQL database, when setting the initial username: UPDATE failed." << std::endl;
+       return STARTUPERROR_FAILED_UNKNOWN_REASON;
+      }
+      */
+    }
+    catch(const Glib::Error& ex)
+    {
+      std::cerr << G_STRFUNC  << "Error while attempting to start self-hosting MySQL database, when setting the initial username: UPDATE failed: " << ex.what() << std::endl;
+      return STARTUPERROR_FAILED_UNKNOWN_REASON;
+    }
+  }
+
+  m_saved_username = m_initial_username_to_set;
+  m_initial_username_to_set.clear();
+
+  return STARTUPERROR_NONE;
+}
+
+//TODO: Avoid copy/paste with PostgresSelfHosted:
+void MySQLSelfHosted::show_active_connections()
+{
+/* TODO_MySQL
+  Glib::RefPtr<Gnome::Gda::SqlBuilder> builder =
+      Gnome::Gda::SqlBuilder::create(Gnome::Gda::SQL_STATEMENT_SELECT);
+  builder->select_add_field("*", "pg_stat_activity");
+  builder->select_add_target("pg_stat_activity");
+ 
+  Glib::RefPtr<Gnome::Gda::Connection> gda_connection = connect(m_saved_database_name, m_saved_username, m_saved_password);
+  if(!gda_connection)
+    std::cerr << G_STRFUNC << ": connection failed." << std::endl;
+  
+  Glib::RefPtr<Gnome::Gda::DataModel> datamodel = DbUtils::query_execute_select(builder);
+  if(!datamodel)
+    std::cerr << G_STRFUNC << ": pg_stat_activity SQL query failed." << std::endl;
+  
+  const int rows_count = datamodel->get_n_rows(); 
+  if(datamodel->get_n_rows() < 1)
+    std::cerr << G_STRFUNC << ": pg_stat_activity SQL query returned no rows." << std::endl;
+
+  std::cout << "Active connections according to a pg_stat_activity SQL query:" << std::endl;
+  const int cols_count = datamodel->get_n_columns();
+  for(int row = 0; row < rows_count; ++row)
+  {
+    for(int col = 0; col < cols_count; ++col)
+    {
+      if(col != 0)
+        std::cout << ", ";
+        
+      std::cout << datamodel->get_value_at(col, row).to_string();
+    }
+    
+    std::cout << std::endl;
+  }
+  
+  //Make sure that this connection does not stop a further attempt to stop the server.
+  gda_connection->close();
+*/
+}
+
+std::string MySQLSelfHosted::get_mysqladmin_command(const Glib::ustring& username, const Glib::ustring& password)
+{
+  if(username.empty())
+  {
+    std::cerr << G_STRFUNC << ": username is empty." << std::endl;
+  }
+
+  const std::string port_as_text = Glib::Ascii::dtostr(m_port);
+
+  std::string command = get_path_to_mysql_executable("mysqladmin")
+    + " --no-defaults"
+    + " --port=" + port_as_text
+    + " --protocol=tcp" //Otherwise we cannot connect as root. TODO: However, maybe we could use --skip-networking if network sharing is not enabled.
+    + " --user=" + Glib::shell_quote(username);
+
+  //--password='' is not always interpreted the same as specifying no --password.
+  if(!password.empty())
+    command += " --password=" + Glib::shell_quote(password);
+
+  return command;
+}
+
+bool MySQLSelfHosted::cleanup(const SlotProgress& slot_progress)
+{
+  // This seems to be called twice sometimes, so we don't assert here until
+  // this is fixed.
+  //g_assert(get_self_hosting_active());
+
+  if(!get_self_hosting_active())
+  {
+    //std::cout << G_STRFUNC << ": self-hosting is not active." << std::endl;
+    return true; //Don't try to stop it if we have not started it.
+  }
+
+  const std::string port_as_text = Glib::Ascii::dtostr(m_port);
+
+  // -D specifies the data directory.
+  // -c config_file= specifies the configuration file
+  // -k specifies a directory to use for the socket. This must be writable by us.
+  // We use "-m fast" instead of the default "-m smart" because that waits for clients to disconnect (and sometimes never succeeds).
+  // TODO: Warn about connected clients on other computers? Warn those other users?
+  // Make sure to use double quotes for the executable path, because the
+  // CreateProcess() API used on Windows does not support single quotes.
+  const std::string command_mysql_stop = get_mysqladmin_command(m_saved_username, m_saved_password)
+    + " shutdown";
+  //std::cout << "DEBUGcleanup before shutdown: command=" << command_mysql_stop << std::endl;
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_mysql_stop, slot_progress);
+
+  //Give it time to succeed, because mysqladmin shutdown does not wait when using protocol=tcp.
+  //TODO: Wait for a second command, such as myadmin ping, though its error message is less precise in this case.
+  Glib::usleep(5000 * 1000);
+
+  //std::cout << "DEBUGcleanup after shutdown: result=" << result << std::endl;
+
+  if(!result)
+  {
+    std::cerr << "Error while attempting to stop self-hosting of the MySQL database. Trying again."  << std::endl;
+    
+    //Show open connections for debugging:
+    try
+    {
+      show_active_connections();
+    }
+    catch(const Glib::Error& ex)
+    {
+      std::cerr << G_STRFUNC << ": exception while trying to show active MySQL connections: " << ex.what() << std::endl;
+    }
+    
+    //I've seen it fail when running under valgrind, and there are reports of failures in bug #420962.
+    //Maybe it will help to try again:
+    const bool result = Glom::Spawn::execute_command_line_and_wait(command_mysql_stop, slot_progress);
+    if(!result)
+    {
+      std::cerr << "Error while attempting (for a second time) to stop self-hosting of the database."  << std::endl;
+      return false;
+    }
+  }
+
+  m_port = 0;
+
+  return true;
+}
+
+
+
+bool MySQLSelfHosted::set_network_shared(const SlotProgress& /* slot_progress */, bool network_shared)
+{
+  //TODO: Use slot_progress, while doing async IO for create_text_file().
+
+  m_network_shared = network_shared;
+
+  return true;
+}
+
+static bool on_timeout_delay(const Glib::RefPtr<Glib::MainLoop>& mainloop)
+{
+  //Allow our mainloop.run() to return:
+  if(mainloop)
+    mainloop->quit();
+
+  return false;
+}
+
+
+Glib::RefPtr<Gnome::Gda::Connection> MySQLSelfHosted::connect(const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection)
+{
+  if(database.empty())
+  {
+    std::cerr << G_STRFUNC << ": The database name is empty. This is strange." << std::endl;
+    return Glib::RefPtr<Gnome::Gda::Connection>();
+  }
+
+  if(!get_self_hosting_active())
+  {
+    throw ExceptionConnection(ExceptionConnection::FAILURE_NO_BACKEND); //TODO: But there is a backend. It's just not ready.
+    return Glib::RefPtr<Gnome::Gda::Connection>();
+  }
+
+  Glib::RefPtr<Gnome::Gda::Connection> result;
+  bool keep_trying = true;
+  guint count_retries = 1;
+  const guint MAX_RETRIES_KNOWN_PASSWORD = 20;
+  const guint MAX_RETRIES_EVER = 20;
+  while(keep_trying)
+  {
+    try
+    {
+      result = attempt_connect(port_as_string(m_port), database, username, password, fake_connection);
+    }
+    catch(const ExceptionConnection& ex)
+    {
+      if(ex.get_failure_type() == ExceptionConnection::FAILURE_NO_SERVER)
+      {
+        //It must be using a default password, so any failure would not be due to a wrong password.
+        //However, pg_ctl sometimes reports success before it is really ready to let us connect,
+        //so in this case we can just keep trying until it works, with a very long timeout.
+        count_retries++;
+        const guint max_retries = m_network_shared ? MAX_RETRIES_EVER : MAX_RETRIES_KNOWN_PASSWORD;
+        if(count_retries > max_retries)
+        {
+          keep_trying = false;
+          continue;
+        }
+
+        std::cout << "debug: " << G_STRFUNC << ": Waiting and retrying the connection due to suspected too-early success of pg_ctl. retries=" << count_retries << ", max_retries=" << m_network_shared << std::endl;
+
+        //Wait:
+        Glib::RefPtr<Glib::MainLoop> mainloop = Glib::MainLoop::create(false);
+          sigc::connection connection_timeout = Glib::signal_timeout().connect(
+          sigc::bind(sigc::ptr_fun(&on_timeout_delay), sigc::ref(mainloop)),
+          1000 /* 1 second */);
+        mainloop->run();
+        connection_timeout.disconnect();
+
+        keep_trying = true;
+        continue;
+      }
+      else
+      {
+        throw ex;
+      }
+    }
+
+    keep_trying = false;
+  }
+
+  //Save the connection details _only_ for later debug use:
+  
+  m_saved_database_name = database;
+  m_saved_username = username;
+  m_saved_password = password;
+  return result;
+}
+
+bool MySQLSelfHosted::create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& username, const Glib::ustring& password)
+{
+  return attempt_create_database(slot_progress, database_name, "localhost", port_as_string(m_port), username, password);
+}
+
+unsigned int MySQLSelfHosted::discover_first_free_port(unsigned int start_port, unsigned int end_port)
+{
+  //Open a socket so we can try to bind it to a port:
+  const int fd = socket(AF_INET, SOCK_STREAM, 0);
+  if(fd == -1)
+  {
+#ifdef G_OS_WIN32
+    std::cerr << "Create socket: " << WSAGetLastError() << std::endl;
+#else
+    perror("Create socket");
+#endif //G_OS_WIN32
+    return 0;
+  }
+
+  //This code was originally suggested by Lennart Poettering.
+
+  struct ::sockaddr_in sa;
+  memset(&sa, 0, sizeof(sa));
+  sa.sin_family = AF_INET;
+
+  guint16 port_to_try = start_port;
+  while (port_to_try <= end_port)
+  {
+    sa.sin_port = htons(port_to_try);
+
+    const int result = bind(fd, (sockaddr*)&sa, sizeof(sa));
+    bool available = false;
+    if(result == 0)
+       available = true;
+    else if(result < 0)
+    {
+      #ifdef G_OS_WIN32
+      available = (WSAGetLastError() != WSAEADDRINUSE);
+      #endif // G_OS_WIN32
+
+      //Some BSDs don't have this.
+      //But watch out - if you don't include errno.h then this won't be
+      //defined on Linux either, but you really do need to check for it.
+      #ifdef EADDRINUSE
+      available = (errno != EADDRINUSE);
+      #endif
+
+      #ifdef EPORTINUSE //Linux doesn't have this.
+      available = (errno != EPORTINUSE);
+      #endif
+    }
+    else
+    {
+      //std::cout << "debug: " << G_STRFUNC << ": port in use: " << port_to_try << std::endl;
+    }
+
+    if(available)
+    {
+      #ifdef G_OS_WIN32
+      closesocket(fd);
+      #else
+      close(fd);
+      #endif //G_OS_WIN32
+
+      //std::cout << "debug: " << G_STRFUNC << ": Found: returning " << port_to_try << std::endl;
+      return port_to_try;
+    }
+
+    ++port_to_try;
+  }
+
+#ifdef G_OS_WIN32
+  closesocket(fd);
+#else
+  close(fd);
+#endif //G_OS_WIN32
+
+  std::cerr << G_STRFUNC << ": No port was available." << std::endl;
+  return 0;
+}
+
+} // namespace ConnectionPoolBackends
+
+} // namespcae Glom
diff --git a/glom/libglom/connectionpool_backends/mysql_self.h b/glom/libglom/connectionpool_backends/mysql_self.h
new file mode 100644
index 0000000..828bd0f
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_self.h
@@ -0,0 +1,100 @@
+/* Glom
+ *
+ * Copyright (C) 2001-2013 Murray Cumming
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GLOM_BACKEND_MYSQL_SELF_H
+#define GLOM_BACKEND_MYSQL_SELF_H
+
+#include <libglom/libglom_config.h>
+
+#include <libglom/connectionpool_backends/mysql.h>
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+class MySQLSelfHosted : public MySQL
+{
+public:
+  MySQLSelfHosted();
+
+  /** Return whether the self-hosted server is currently running.
+   *
+   * @result True if it is running, and false otherwise.
+   */
+  bool get_self_hosting_active() const;
+
+  /** Returns the port number the local mysql server is running on.
+   *
+   * @result The port number of the self-hosted server, or 0 if it is not
+   * running.
+   */
+  unsigned int get_port() const;
+
+  /** Try to install mysql on the distro, though this will require a
+   * distro-specific patch to the implementation.
+   */
+  static bool install_mysql(const SlotProgress& slot_progress);
+
+private:
+  std::string get_mysqladmin_command(const Glib::ustring& username, const Glib::ustring& password);
+
+  virtual InitErrors initialize(const SlotProgress& slot_progress, const Glib::ustring& initial_username, const Glib::ustring& password, bool network_shared = false);
+
+  virtual StartupErrors startup(const SlotProgress& slot_progress, bool network_shared = false);
+  virtual bool cleanup(const SlotProgress& slot_progress);
+  virtual bool set_network_shared(const SlotProgress& slot_progress, bool network_shared = true);
+
+  virtual Glib::RefPtr<Gnome::Gda::Connection> connect(const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection = false);
+
+  virtual bool create_database(const SlotProgress& slot_progress, const Glib::ustring& database_name, const Glib::ustring& username, const Glib::ustring& password);
+
+private:
+  /** Examine ports one by one, starting at @a starting_port, in increasing
+   * order, and return the first one that is available.
+   */
+  static unsigned int discover_first_free_port(unsigned int start_port, unsigned int end_port);
+
+  /** Run the command-line with the --version option to discover what version
+   * of MySQL is installed, so we can use the appropriate configuration
+   * options when self-hosting.
+   */
+  Glib::ustring get_mysqlql_utils_version(const SlotProgress& slot_progress);
+
+  float get_mysqlql_utils_version_as_number(const SlotProgress& slot_progress);
+  
+  void show_active_connections();
+
+  bool m_network_shared;
+  
+  //These are remembered in order to use them to issue the shutdown command via mysqladmin:
+  Glib::ustring m_saved_database_name, m_saved_username, m_saved_password;
+
+  bool m_temporary_password_active; //Whether the password is an initial temporary one.
+  Glib::ustring m_initial_password_to_set, m_initial_username_to_set;
+  Glib::ustring m_temporary_password;
+};
+
+} // namespace ConnectionPoolBackends
+
+} //namespace Glom
+
+#endif //GLOM_BACKEND_MYSQL_SELF_H
diff --git a/glom/libglom/connectionpool_backends/postgres.cc b/glom/libglom/connectionpool_backends/postgres.cc
index 3fb50aa..d306c59 100644
--- a/glom/libglom/connectionpool_backends/postgres.cc
+++ b/glom/libglom/connectionpool_backends/postgres.cc
@@ -65,6 +65,7 @@ Postgres::Postgres()
 {
 }
 
+//TODO: We need to specify TCP for the connection: https://bugzilla.gnome.org/show_bug.cgi?id=691069
 Glib::RefPtr<Gnome::Gda::Connection> Postgres::attempt_connect(const Glib::ustring& port, const Glib::ustring& database, const Glib::ustring& username, const Glib::ustring& password, bool fake_connection)
 {
   //We must specify _some_ database even when we just want to create a database.
diff --git a/glom/libglom/db_utils.cc b/glom/libglom/db_utils.cc
index dcdc2b8..c9b68a7 100644
--- a/glom/libglom/db_utils.cc
+++ b/glom/libglom/db_utils.cc
@@ -1245,7 +1245,13 @@ bool create_table(const sharedptr<const TableInfo>& table_info, const Document::
     info->set_g_type( Field::get_gda_type_for_glom_type(field->get_glom_type()) );
     field->set_field_info(info); //TODO_Performance
 
-    Glib::ustring sql_field_description = escape_sql_id(field->get_name()) + " " + field->get_sql_type();
+    Glib::ustring field_type = field->get_sql_type();
+    if(field_type == "varchar")
+      field_type = "varchar(255)"; //For MySQL.
+    else if(field_type == "VARBINARY")
+      field_type = "blob"; //For MySQL.
+
+    Glib::ustring sql_field_description = escape_sql_id(field->get_name()) + " " + field_type;
 
     if(field->get_primary_key())
       sql_field_description += " NOT NULL  PRIMARY KEY";
diff --git a/glom/libglom/db_utils.h b/glom/libglom/db_utils.h
index 986cc35..408296b 100644
--- a/glom/libglom/db_utils.h
+++ b/glom/libglom/db_utils.h
@@ -174,6 +174,7 @@ bool rename_table(const Glib::ustring& table_name, const Glib::ustring& new_tabl
 bool drop_table(const Glib::ustring& table_name);
 
 /** Escape, and quote, SQL identifiers such as table names.
+ * This requires a current connection.
  */
 Glib::ustring escape_sql_id(const Glib::ustring& id);
 
diff --git a/glom/libglom/document/document.cc b/glom/libglom/document/document.cc
index 2718d26..9b394b4 100644
--- a/glom/libglom/document/document.cc
+++ b/glom/libglom/document/document.cc
@@ -58,6 +58,8 @@ static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE[] = "hosting_mode";
 static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_CENTRAL[] = "postgres_central";
 static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_SELF[] = "postgres_self";
 static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_SQLITE[] = "sqlite";
+static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_CENTRAL[] = "mysql_central";
+static const char GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_SELF[] = "mysql_self";
 static const char GLOM_ATTRIBUTE_CONNECTION_NETWORK_SHARED[] = "network_shared";
 static const char GLOM_ATTRIBUTE_CONNECTION_SERVER[] = "server";
 static const char GLOM_ATTRIBUTE_CONNECTION_PORT[] = "port";
@@ -341,8 +343,11 @@ bool Document::get_network_shared() const
 
   //Enforce constraints:
   const HostingMode hosting_mode = get_hosting_mode();
-  if(hosting_mode == HOSTING_MODE_POSTGRES_CENTRAL)
+  if( (hosting_mode == HOSTING_MODE_POSTGRES_CENTRAL) ||
+    (hosting_mode == HOSTING_MODE_MYSQL_CENTRAL) )
+  {
     shared = true; //Central hosting means that it must be shared on the network.
+  }
   else if(hosting_mode == HOSTING_MODE_SQLITE)
     shared = false; //sqlite does not allow network sharing.
 
@@ -378,6 +383,12 @@ std::string Document::get_connection_self_hosted_directory_uri() const
       case HOSTING_MODE_SQLITE:
         datadir = parent;
         break;
+      case HOSTING_MODE_MYSQL_SELF:
+        datadir = parent->get_child("glom_mysql_data");
+        break;
+      case HOSTING_MODE_MYSQL_CENTRAL:
+        datadir = parent;
+        break;
       default:
         g_assert_not_reached();
         break;
@@ -2547,6 +2558,10 @@ bool Document::load_after(int& failure_code)
             mode = HOSTING_MODE_POSTGRES_SELF;
           else if(attr_mode == GLOM_ATTRIBUTE_CONNECTION_HOSTING_SQLITE)
             mode = HOSTING_MODE_SQLITE;
+          else if(attr_mode == GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_CENTRAL)
+            mode = HOSTING_MODE_MYSQL_CENTRAL;
+          else if(attr_mode == GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_SELF)
+            mode = HOSTING_MODE_MYSQL_SELF;
           else
 	  {
             std::cerr << G_STRFUNC << ": Hosting mode " << attr_mode << " is not supported" << std::endl;
@@ -3570,13 +3585,24 @@ bool Document::save_before()
     switch(m_hosting_mode)
     {
     case HOSTING_MODE_POSTGRES_CENTRAL:
-      XmlUtils::set_node_attribute_value(nodeConnection, GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_CENTRAL);
+      XmlUtils::set_node_attribute_value(nodeConnection, 
+        GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_CENTRAL);
       break;
     case HOSTING_MODE_POSTGRES_SELF:
-      XmlUtils::set_node_attribute_value(nodeConnection, GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_SELF);
+      XmlUtils::set_node_attribute_value(nodeConnection,
+        GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_POSTGRES_SELF);
       break;
     case HOSTING_MODE_SQLITE:
-      XmlUtils::set_node_attribute_value(nodeConnection, GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_SQLITE);
+      XmlUtils::set_node_attribute_value(nodeConnection,
+        GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_SQLITE);
+      break;
+    case HOSTING_MODE_MYSQL_CENTRAL:
+      XmlUtils::set_node_attribute_value(nodeConnection, 
+        GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_CENTRAL);
+      break;
+    case HOSTING_MODE_MYSQL_SELF:
+      XmlUtils::set_node_attribute_value(nodeConnection,
+        GLOM_ATTRIBUTE_CONNECTION_HOSTING_MODE, GLOM_ATTRIBUTE_CONNECTION_HOSTING_MYSQL_SELF);
       break;
     default:
       g_assert_not_reached();
diff --git a/glom/libglom/document/document.h b/glom/libglom/document/document.h
index 58d5dfd..fd12fd6 100644
--- a/glom/libglom/document/document.h
+++ b/glom/libglom/document/document.h
@@ -106,9 +106,11 @@ public:
   /// How the database is hosted.
   enum HostingMode
   {
-    HOSTING_MODE_POSTGRES_CENTRAL, /*!< The database is hosted on an external postgresql server. */
-    HOSTING_MODE_POSTGRES_SELF, /*!< A new postgres database process is spawned that hosts the data. */
+    HOSTING_MODE_POSTGRES_CENTRAL, /*!< The database is hosted on an external PostgreSQL server. */
+    HOSTING_MODE_POSTGRES_SELF, /*!< A new PostgreSQL database process is spawned that hosts the data. */
     HOSTING_MODE_SQLITE, /*!< A sqlite database file is used. */
+    HOSTING_MODE_MYSQL_CENTRAL, /*!< The database is hosted on an external MySQL server. */
+    HOSTING_MODE_MYSQL_SELF,  /*!< A new MySQL database process is spawned that hosts the data. */
     HOSTING_MODE_DEFAULT = HOSTING_MODE_POSTGRES_SELF /*!- Arbitrary default. */
   };
 
diff --git a/glom/libglom/filelist.am b/glom/libglom/filelist.am
index 9ca0169..d4feee5 100644
--- a/glom/libglom/filelist.am
+++ b/glom/libglom/filelist.am
@@ -180,6 +180,10 @@ libglom_sources =							\
 	glom/libglom/python_embed/py_glom_ui_callbacks.h		\
 	glom/libglom/python_embed/pygdavalue_conversions.cc		\
 	glom/libglom/python_embed/pygdavalue_conversions.h		\
+	glom/libglom/connectionpool_backends/mysql.cc			\
+	glom/libglom/connectionpool_backends/mysql.h			\
+	glom/libglom/connectionpool_backends/mysql_central.cc \
+	glom/libglom/connectionpool_backends/mysql_central.h \
 	glom/libglom/connectionpool_backends/sqlite.cc			\
 	glom/libglom/connectionpool_backends/sqlite.h			\
 	glom/libglom/connectionpool_backends/postgres.cc		\
@@ -190,5 +194,7 @@ libglom_sources =							\
 if !GLOM_ENABLE_CLIENT_ONLY
 libglom_sources +=						\
 	glom/libglom/connectionpool_backends/postgres_self.cc	\
-	glom/libglom/connectionpool_backends/postgres_self.h
+	glom/libglom/connectionpool_backends/postgres_self.h \
+	glom/libglom/connectionpool_backends/mysql_self.cc \
+	glom/libglom/connectionpool_backends/mysql_self.h
 endif
diff --git a/glom/main.cc b/glom/main.cc
index 18c40b2..6013c8c 100644
--- a/glom/main.cc
+++ b/glom/main.cc
@@ -36,7 +36,7 @@
 #include <glibmm/convert.h>
 #include <glibmm/miscutils.h>
 
-// For postgres availability checks:
+// For PostgreSQL availability checks:
 #ifdef GLOM_ENABLE_POSTGRESQL
 #include <libglom/connectionpool_backends/postgres.h>
 #ifndef GLOM_ENABLE_CLIENT_ONLY
@@ -44,6 +44,14 @@
 #endif //GLOM_ENABLE_CLIENT_ONLY
 #endif //GLOM_ENABLE_POSTGRESQL
 
+// For MySQL availability checks:
+#ifdef GLOM_ENABLE_MYSQL
+#include <libglom/connectionpool_backends/mysql.h>
+#ifndef GLOM_ENABLE_CLIENT_ONLY
+#include <libglom/connectionpool_backends/mysql_self.h>
+#endif //GLOM_ENABLE_CLIENT_ONLY
+#endif //GLOM_ENABLE_MYSQL
+
 // For sanity checks:
 #include <glom/python_embed/glom_python.h>
 
@@ -220,7 +228,7 @@ bool check_user_is_not_root_with_warning()
 // Message to packagers:
 // If your Glom package does not depend on PostgreSQL, for some reason,
 // then your distro-specific patch should uncomment this #define.
-// and implement ConnectionPool::install_posgres().
+// and implement ConnectionPool::install_postgres().
 // But please, just make your Glom package depend on PostgreSQL instead,
 // because this is silly.
 //
@@ -273,6 +281,57 @@ bool check_postgres_is_available_with_warning()
 
 #endif //GLOM_ENABLE_POSTGRESQL
 
+#ifdef GLOM_ENABLE_MYSQL
+#ifndef GLOM_ENABLE_CLIENT_ONLY
+
+/** Check whether MySQL is really available for self-hosting,
+ * in case the distro package has incorrect dependencies.
+ *
+ * @results True if everything is OK.
+ */
+bool check_mysql_is_available_with_warning()
+{
+  const std::string binpath = Glom::ConnectionPoolBackends::MySQLSelfHosted::get_path_to_mysql_executable("mysql", false /* not quoted */);
+
+  // TODO: At least on Windows we should probably also check for initdb and
+  // pg_ctl. Perhaps it would also be a good idea to access these files as
+  // long as glom runs so they cannot be (re)moved.
+  if(!binpath.empty())
+  {
+    const Glib::ustring uri_binpath = Glib::filename_to_uri(binpath);
+    if(Utils::file_exists(uri_binpath))
+      return true;
+  }
+
+  #ifdef DISTRO_SPECIFIC_MYSQL_INSTALL_IMPLEMENTED
+
+  //Show message to the user about the broken installation:
+  //This is a packaging bug, but it would probably annoy packagers to mention that in the dialog:
+  //Unlike for PostgreSQL, this warning is only shown if MySQL was specified in the build.
+  Gtk::MessageDialog dialog(Utils::bold_message(_("Incomplete Glom Installation")), true /* use_markup */, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_NONE, true /* modal */);
+  dialog.set_secondary_text(_("Your installation of Glom is not complete, because MySQL is not available on your system. MySQL is needed for self-hosting of some Glom databases.\n\nYou may now install MySQL to complete the Glom installation."));
+  dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
+  dialog.add_button(_("Install MySQL"), Gtk::RESPONSE_OK);
+  const int response = dialog.run();
+  if(response != Gtk::RESPONSE_OK)
+    return false; //Failure. Glom should now quit.
+  else
+    return install_mysql(&dialog);
+
+  #else  //DISTRO_SPECIFIC_MYSQL_INSTALL_IMPLEMENTED
+
+  //Show message to the user about the broken installation:
+  Gtk::MessageDialog dialog(Utils::bold_message(_("Incomplete Glom Installation")), true /* use_markup */, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true /* modal */);
+  dialog.set_secondary_text(_("Your installation of Glom is not complete, because MySQL is not available on your system. MySQL is needed for self-hosting of some Glom databases.\n\nPlease report this bug to your vendor, or your system administrator so it can be corrected."));
+  dialog.run();
+  return false;
+
+  #endif //DISTRO_SPECIFIC_MYSQL_INSTALL_IMPLEMENTED
+}
+#endif //GLOM_ENABLE_CLIENT_ONLY
+
+#endif //GLOM_ENABLE_MYSQL
+
 bool check_pyglom_is_available_with_warning()
 {
   if(glom_python_module_is_available())
diff --git a/glom/mode_design/users/dialog_users_list.cc b/glom/mode_design/users/dialog_users_list.cc
index 1eee143..8dada10 100644
--- a/glom/mode_design/users/dialog_users_list.cc
+++ b/glom/mode_design/users/dialog_users_list.cc
@@ -332,6 +332,8 @@ void Dialog_UsersList::on_button_user_edit()
 
       if(!user.empty() && !password.empty())
       {
+        //TODO: Can this change the username too?
+        //Note: If using MySQL, we need MySQL 5.6.7 for ALTER USER:
         const Glib::ustring strQuery = "ALTER USER " + DbUtils::escape_sql_id(user) + " PASSWORD '" + password + "'" ; //TODO: Escape the password.
         const bool test = DbUtils::query_execute_string(strQuery);
         if(!test)
diff --git a/tests/test_selfhosting_new_empty.cc b/tests/test_selfhosting_new_empty.cc
index 7536da9..4e727d4 100644
--- a/tests/test_selfhosting_new_empty.cc
+++ b/tests/test_selfhosting_new_empty.cc
@@ -59,6 +59,13 @@ int main()
 
   const int result = test_all_hosting_modes(sigc::ptr_fun(&test));
 
+  if(!test(Glom::Document::HOSTING_MODE_MYSQL_SELF))
+  {
+    std::cerr << "Failed with MySQL" << std::endl;
+    test_selfhosting_cleanup();
+    return EXIT_FAILURE;
+  }
+
   Glom::libglom_deinit();
 
   return result;
diff --git a/tests/test_selfhosting_utils.cc b/tests/test_selfhosting_utils.cc
index db0e9da..37c465a 100644
--- a/tests/test_selfhosting_utils.cc
+++ b/tests/test_selfhosting_utils.cc
@@ -173,6 +173,7 @@ bool test_selfhost(Glom::Document& document, const Glib::ustring& user, const Gl
 bool test_create_and_selfhost_new_empty(Glom::Document& document, Glom::Document::HostingMode hosting_mode, const std::string& subdirectory_path)
 {
   if( (hosting_mode != Glom::Document::HOSTING_MODE_POSTGRES_SELF) &&
+    (hosting_mode != Glom::Document::HOSTING_MODE_MYSQL_SELF) &&
     (hosting_mode != Glom::Document::HOSTING_MODE_SQLITE) )
   {
     std::cerr << G_STRFUNC << ": This test function does not support the specified hosting_mode: " << hosting_mode << std::endl;
@@ -323,6 +324,7 @@ bool test_create_and_selfhost_from_test_example(const std::string& example_filen
 bool test_create_and_selfhost_from_uri(const Glib::ustring& example_file_uri, Glom::Document& document, Glom::Document::HostingMode hosting_mode, const std::string& subdirectory_path)
 {
   if( (hosting_mode != Glom::Document::HOSTING_MODE_POSTGRES_SELF) &&
+    (hosting_mode != Glom::Document::HOSTING_MODE_MYSQL_SELF) &&
     (hosting_mode != Glom::Document::HOSTING_MODE_SQLITE) )
   {
     std::cerr << G_STRFUNC << ": This test function does not support the specified hosting_mode: " << hosting_mode << std::endl;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]