[glom/mysql] Initial structure of MySQL support



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

    Initial structure of MySQL support

 configure.ac                                       |   37 +-
 glom/libglom/connectionpool_backends/mysql.cc      |  962 ++++++++++++++++++++
 glom/libglom/connectionpool_backends/mysql.h       |  121 +++
 glom/libglom/connectionpool_backends/mysql_self.cc |  724 +++++++++++++++
 glom/libglom/connectionpool_backends/mysql_self.h  |   95 ++
 glom/libglom/filelist.am                           |    6 +-
 glom/main.cc                                       |   63 ++-
 7 files changed, 2002 insertions(+), 6 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index e75b566..ca1ed24 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)])],
@@ -248,6 +268,17 @@ 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])
+AC_DEFINE_UNQUOTED([MYSQL_UTILS_PATH], ["$MYSQL_UTILS_PATH"],
+                   [Define to the location of the PostgreSQL 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_backends/mysql.cc b/glom/libglom/connectionpool_backends/mysql.cc
new file mode 100644
index 0000000..f738711
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql.cc
@@ -0,0 +1,962 @@
+/* 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
+    return "USERNAME=" + Glom::DbUtils::gda_cnc_string_encode(username) + 
+      ";PASSWORD=" + Glom::DbUtils::gda_cnc_string_encode(password);
+}
+
+} //anonymous namespace
+
+namespace Glom
+{
+
+namespace ConnectionPoolBackends
+{
+
+MySQL::MySQL()
+: m_port(0),
+  m_mysql_server_version(0.0f)
+{
+}
+
+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)
+{
+  //We must specify _some_ database even when we just want to create a database.
+  //This _might_ be different on some systems. I hope not. murrayc
+  const Glib::ustring default_database = "template1";
+  //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;
+}
+
+//Because ~/.pgpass is not an absolute path.
+static std::string get_absolute_pgpass_filepath()
+{
+  return Glib::build_filename(
+    Glib::get_home_dir(), ".pgpass");
+}
+
+bool MySQL::save_password_to_pgpass(const Glib::ustring username, const Glib::ustring& password, std::string& filepath_previous, std::string& filepath_original)
+{
+  //Initialize output variables:
+  filepath_previous.clear();
+  filepath_original.clear();
+
+  const std::string filepath_pgpass = get_absolute_pgpass_filepath();
+  filepath_original = filepath_pgpass;
+
+  //Move any existing file out of the way:
+  if(file_exists_filepath(filepath_pgpass))
+  {
+    //std::cout << "DEBUG: File exists: " << filepath_pgpass << std::endl;
+    filepath_previous = filepath_pgpass + ".glombackup";
+    if(g_rename(filepath_pgpass.c_str(), filepath_previous.c_str()) != 0)
+    {
+      std::cerr << G_STRFUNC << "Could not rename file: from=" << filepath_pgpass << ", to=" << filepath_previous << std::endl;
+      return false;
+    }
+  }
+
+  //See http://www.mysqlql.org/docs/8.4/static/libpq-pgpass.html
+  //TODO: Escape \ and : characters.
+  const Glib::ustring contents =
+    m_host + ":" + port_as_string(m_port) + ":*:" + username + ":" + password;
+
+  std::string uri;
+  try
+  {
+    uri = Glib::filename_to_uri(filepath_pgpass);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << G_STRFUNC << ": exception from Glib::filename_from_uri(): " << ex.what() << std::endl;
+    g_rename(filepath_previous.c_str(), filepath_pgpass.c_str());
+    return false;
+  }
+
+  const bool result = create_text_file(uri, contents, true /* current user only */);
+  if(!result)
+  {
+    std::cerr << G_STRFUNC << ": create_text_file() failed." << std::endl;
+    g_rename(filepath_previous.c_str(), filepath_pgpass.c_str());
+    return false;
+  }
+
+  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;
+  }
+
+  // Save the password to ~/.pgpass, because this is the only way to use
+  // pg_dump without it asking for the password:
+  std::string pgpass_backup, pgpass_original;
+  const bool pgpass_created = save_password_to_pgpass(username, password, pgpass_backup, pgpass_original);
+  if(!pgpass_created)
+  {
+    std::cerr << G_STRFUNC << ": save_password_to_pgpass() failed." << 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 = get_path_to_mysql_executable("pg_dump") +
+    " --format=c " + // The default (plain) format cannot be used with pg_restore.
+    " --create --file=" + Glib::shell_quote(path_backup) +
+    " --host=" + Glib::shell_quote(m_host) +
+    " --port=" + port_as_string(m_port) +
+    " --username=" + Glib::shell_quote(username) +
+    " " + database_name; //TODO: Quote database_name?
+
+
+  //std::cout << "DEBUG: command_dump=" << command_dump << std::endl;
+
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_dump, slot_progress);
+
+  //Move the previously-existing .pgpass file back:
+  //TODO: Really, we should just edit the file instead of completely replacing it,
+  //      because another application might try to edit it in the meantime.
+  if(!pgpass_backup.empty())
+  {
+    g_rename(pgpass_backup.c_str(), pgpass_original.c_str());
+  }
+
+  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;
+  }
+
+  // Save the password to ~/.pgpass, because this is the only way to use
+  // pg_dump without it asking for the password:
+  std::string pgpass_backup, pgpass_original;
+  const bool pgpass_created = save_password_to_pgpass(username, password, pgpass_backup, pgpass_original);
+  if(!pgpass_created)
+  {
+    std::cerr << G_STRFUNC << ": save_password_to_pgpass() failed." << 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 = get_path_to_mysql_executable("pg_restore") +
+    " -d " + database_name + //TODO: Quote database name?
+    " --host=" + Glib::shell_quote(m_host) +
+    " --port=" + port_as_string(m_port) +
+    " --username=" + Glib::shell_quote(username) +
+    " " + path_backup;
+
+  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);
+
+  //Move the previously-existing .pgpass file back:
+  //TODO: Really, we should just edit the file instead of completely replacing it,
+  //      because another application might try to edit it in the meantime.
+  if(!pgpass_backup.empty())
+  {
+    g_rename(pgpass_backup.c_str(), pgpass_original.c_str());
+  }
+
+  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 "public";
+}
+
+} //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..d711a38
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql.h
@@ -0,0 +1,121 @@
+/* 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);
+
+  /**
+   * @param filepath_previous The path to which the previous .pgpass, if any was moved.
+   * @param filepath_original The path to which filepath_previous should be moved back after the caller has finished.
+   * @param result whether it succeeded.
+   */
+  bool save_password_to_pgpass(const Glib::ustring username, const Glib::ustring& password, std::string& filepath_previous, std::string& filepath_original);
+
+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_self.cc b/glom/libglom/connectionpool_backends/mysql_self.cc
new file mode 100644
index 0000000..aa64130
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_self.cc
@@ -0,0 +1,724 @@
+/* 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/miscutils.h>
+#include <glibmm/stringutils.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
+{
+
+
+#define DEFAULT_CONFIG_PG_HBA_LOCAL \
+"# TYPE  DATABASE    USER        CIDR-ADDRESS          METHOD\n" \
+"\n" \
+"# local is for Unix domain socket connections only\n" \
+"# trust allows connection from the current PC without a password:\n" \
+"local   all         all                               trust\n" \
+"local   all         all                               md5\n" \
+"\n" \
+"# TCP connections from the same computer, with a password:\n" \
+"host    all         all         127.0.0.1    255.255.255.255    md5\n" \
+"# IPv6 local connections:\n" \
+"host    all         all         ::1/128               md5\n"
+
+#define DEFAULT_CONFIG_PG_HBA_REMOTE_EXTRA \
+"\n" \
+"# IPv4 local connections:\n" \
+"host    all         all         0.0.0.0/0          md5\n" \
+"# IPv6 local connections:\n" \
+"host    all         all         ::1/128               md5\n"
+
+#define DEFAULT_CONFIG_PG_HBA_REMOTE \
+DEFAULT_CONFIG_PG_HBA_LOCAL \
+DEFAULT_CONFIG_PG_HBA_REMOTE_EXTRA
+
+static const int PORT_MYSQL_SELF_HOSTED_START = 5433;
+static const int PORT_MYSQL_SELF_HOSTED_END = 5500;
+
+static const char FILENAME_DATA[] = "data";
+static const char FILENAME_BACKUP[] = "backup";
+
+MySQLSelfHosted::MySQLSelfHosted()
+: m_network_shared(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 the config directory:
+  const std::string dbdir_config = get_self_hosting_config_path(true /* create */);
+  if(dbdir_config.empty())
+  {
+    std::cerr << "Couldn't create the config directory: " << dbdir << std::endl;
+
+    return INITERROR_COULD_NOT_CREATE_DIRECTORY;
+  }
+
+  //Create these files: environment, pg_hba.conf, start.conf
+  set_network_shared(slot_progress, m_network_shared); //Creates pg_hba.conf
+
+  //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:
+
+  //Get file:// URI for the tmp/ directory:
+  const std::string temp_pwfile = Utils::get_temp_file_path("glom_initdb_pwfile");
+  const Glib::ustring temp_pwfile_uri = Glib::filename_to_uri(temp_pwfile);
+  const bool pwfile_creation_succeeded = create_text_file(temp_pwfile_uri, password);
+  g_assert(pwfile_creation_succeeded);
+
+  // 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_initdb = get_path_to_mysql_executable("initdb") + " -D " + Glib::shell_quote(dbdir_data) +
+                                        " -U " + initial_username + " --pwfile=" + Glib::shell_quote(temp_pwfile);
+
+  //Note that --pwfile takes the password from the first line of a file. It's an alternative to supplying it when prompted on stdin.
+  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 database." << std::endl;
+  }
+
+  const int temp_pwfile_removed = g_remove(temp_pwfile.c_str()); //Of course, we don't want this to stay around. It would be a security risk.
+  g_assert(temp_pwfile_removed == 0);
+
+  return result ? INITERROR_NONE : INITERROR_COULD_NOT_START_SERVER;
+}
+
+Glib::ustring MySQLSelfHosted::get_mysqlql_utils_version(const SlotProgress& slot_progress)
+{
+  Glib::ustring result;
+
+  const std::string command = get_path_to_mysql_executable("pg_ctl") + " --version";
+
+  //The first command does not return, but the second command can check whether it succeeded:
+  std::string output;
+  const bool spawn_result = Glom::Spawn::execute_command_line_and_wait(command, slot_progress, output);
+  if(!spawn_result)
+  {
+    std::cerr << "Error while attempting to discover the pg_ctl version." << std::endl;
+    return result;
+  }
+
+  //Use a regex to get the version number:
+  Glib::RefPtr<Glib::Regex> regex;
+
+  //We want the characters at the end:
+  const gchar VERSION_REGEX[] = "pg_ctl \\(MySQL\\) (.*)";
+
+  try
+  {
+    regex = Glib::Regex::create(VERSION_REGEX);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << "Glom: Glib::Regex::create() failed: " << ex.what() << std::endl;
+    return result;
+  }
+
+  if(!regex)
+    return result;
+
+  typedef std::vector<Glib::ustring> type_vec_strings;
+  const type_vec_strings vec = regex->split(output, Glib::REGEX_MATCH_NOTEMPTY);
+  //std::cout << "DEBUG: output == " << output << std::endl;
+  //std::cout << "DEBUG: vec.size() == " << vec.size() << std::endl;
+
+  // We get, for instance, "\n" and 8.4.1" and "\n".
+  for(type_vec_strings::const_iterator iter = vec.begin();
+       iter != vec.end();
+       ++iter)
+  {
+    const Glib::ustring str = *iter;
+    if(!str.empty())
+      return str; //Found.
+  }
+
+  return result;
+}
+
+float MySQLSelfHosted::get_mysqlql_utils_version_as_number(const SlotProgress& slot_progress)
+{
+  float result = 0;
+
+  const Glib::ustring version_str = get_mysqlql_utils_version(slot_progress);
+
+  Glib::RefPtr<Glib::Regex> regex;
+
+  //We want the characters at the end:
+  const gchar VERSION_REGEX[] = "^(\\d*)\\.(\\d*)";
+
+  try
+  {
+    regex = Glib::Regex::create(VERSION_REGEX);
+  }
+  catch(const Glib::Error& ex)
+  {
+    std::cerr << "Glom: Glib::Regex::create() failed: " << ex.what() << std::endl;
+    return result;
+  }
+
+  if(!regex)
+    return result;
+
+  typedef std::vector<Glib::ustring> type_vec_strings;
+  const type_vec_strings vec = regex->split(version_str, Glib::REGEX_MATCH_NOTEMPTY);
+  //std::cout << "DEBUG: str == " << version_str << std::endl;
+  //std::cout << "DEBUG: vec.size() == " << vec.size() << std::endl;
+
+  //We need to loop over the numbers because we get some "" items that we want to ignore:
+  guint count = 0; //We want 2 numbers.
+  for(type_vec_strings::const_iterator iter = vec.begin();
+       iter != vec.end();
+       ++iter)
+  {
+    //std::cout << "regex item: START" << *iter << "END" << std::endl;
+
+    const Glib::ustring str = *iter;
+    if(str.empty())
+      continue;
+
+    const float num = atoi(str.c_str());
+    if(count == 0)
+      result = num;
+    else if(count == 1)
+    {
+      result += (0.1 * num);
+      break;
+    }
+
+    ++count;
+  }
+
+  return result;
+}
+
+
+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 << "ConnectionPool::create_self_hosting(): 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); //Creates pg_hba.conf
+
+  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);
+
+  // -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.
+  // 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_config = Glib::build_filename(dbdir, "config");
+  const std::string dbdir_hba = Glib::build_filename(dbdir_config, "pg_hba.conf");
+  const std::string dbdir_pid = Glib::build_filename(dbdir, "pid");
+  const std::string listen_address = (m_network_shared ? "*" : "localhost");
+  const std::string command_mysql_start = get_path_to_mysql_executable("mysql") + " -D " + Glib::shell_quote(dbdir_data)
+                                  + " -p " + port_as_text
+                                  + " -h " + listen_address
+                                  + " -c hba_file=" + Glib::shell_quote(dbdir_hba)
+                                  + " -k " + Glib::shell_quote(dbdir)
+                                  + " --external_pid_file=" + Glib::shell_quote(dbdir_pid);
+  //std::cout << G_STRFUNC << ": debug: " << command_mysql_start << std::endl;
+
+  // 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_check_mysql_has_started = get_path_to_mysql_executable("pg_ctl") + " status -D " + Glib::shell_quote(dbdir_data);
+
+  //For mysql 8.1, this is "postmaster is running".
+  //For mysql 8.2, this is "server is running".
+  //This is a big hack that we should avoid. murrayc.
+  //
+  //pg_ctl actually seems to return a 0 result code for "is running" and a 1 for not running, at least with MySQL 8.2,
+  //so maybe we can avoid this in future.
+  //Please do test it with your mysql version, using "echo $?" to see the result code of the last command.
+  const std::string second_command_success_text = "is running"; //TODO: This is not a stable API. Also, watch out for localisation.
+
+  //The first command does not return, but the second command can check whether it succeeded:
+  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);
+  if(!result)
+  {
+    std::cerr << "Error while attempting to self-host a database." << std::endl;
+    return STARTUPERROR_FAILED_UNKNOWN_REASON;
+  }
+
+  m_port = available_port; //Remember it for later.
+
+  return STARTUPERROR_NONE;
+}
+
+void MySQLSelfHosted::show_active_connections()
+{
+  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();
+}
+
+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())
+    return true; //Don't try to stop it if we have not started it.
+
+  const std::string dbdir_uri = m_database_directory_uri;
+  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);
+
+
+  // TODO: Detect other instances on the same computer, and use a different port number,
+  // or refuse to continue, showing an error dialog.
+
+  // -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_path_to_mysql_executable("pg_ctl") + " -D " + Glib::shell_quote(dbdir_data) + " stop -m fast";
+  const bool result = Glom::Spawn::execute_command_line_and_wait(command_mysql_stop, slot_progress);
+  if(!result)
+  {
+    std::cerr << "Error while attempting to stop self-hosting of the 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 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;
+
+  const std::string dbdir_uri = m_database_directory_uri;
+  const std::string dbdir = Glib::filename_from_uri(dbdir_uri);
+
+  const std::string dbdir_uri_config = dbdir_uri + "/config";
+  const char* default_conf_contents = 0;
+
+  // Choose the configuration contents based on 
+  // whether we want to be network-shared:
+  //const float mysqlql_version = get_mysqlql_utils_version_as_number(slot_progress);
+  //std::cout << "DEBUG: mysqlql_version=" << mysqlql_version << std::endl;
+
+  default_conf_contents = m_network_shared ? DEFAULT_CONFIG_PG_HBA_REMOTE : DEFAULT_CONFIG_PG_HBA_LOCAL;
+
+  //std::cout << "DEBUG: default_conf_contents=" << default_conf_contents << std::endl;
+
+  const bool hba_conf_creation_succeeded = create_text_file(dbdir_uri_config + "/pg_hba.conf", default_conf_contents);
+  g_assert(hba_conf_creation_succeeded);
+  if(!hba_conf_creation_succeeded)
+    return false;
+
+  return hba_conf_creation_succeeded;
+}
+
+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(!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 = 0;
+  const guint MAX_RETRIES_KNOWN_PASSWORD = 30; /* seconds */
+  const guint MAX_RETRIES_EVER = 60; /* seconds */
+  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..538a9b2
--- /dev/null
+++ b/glom/libglom/connectionpool_backends/mysql_self.h
@@ -0,0 +1,95 @@
+/* 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:
+  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 only remembered in order to use them to provide debug
+  //information when the MySQL shutdown fails:
+  Glib::ustring m_saved_database_name, m_saved_username, m_saved_password;
+};
+
+} // namespace ConnectionPoolBackends
+
+} //namespace Glom
+
+#endif //GLOM_BACKEND_MYSQL_SELF_H
diff --git a/glom/libglom/filelist.am b/glom/libglom/filelist.am
index 9ca0169..7488026 100644
--- a/glom/libglom/filelist.am
+++ b/glom/libglom/filelist.am
@@ -180,6 +180,8 @@ 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/sqlite.cc			\
 	glom/libglom/connectionpool_backends/sqlite.h			\
 	glom/libglom/connectionpool_backends/postgres.cc		\
@@ -190,5 +192,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())



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