[glom] Initial MySQL support
- From: Murray Cumming <murrayc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glom] Initial MySQL support
- Date: Fri, 11 Jan 2013 15:34:38 +0000 (UTC)
commit 1491beae4b9375f2507a88c6657804fcb6ba5868
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 559ab11..7a3dae7 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 b777faf..376f434 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]