#include #include #include #include #include #include "config.h" #include "ag-db.h" #define SCHEMA_VERSION 1 static AgDb *singleton = NULL; typedef struct _AgDbPrivate { gchar *dsn; GdaConnection *conn; } AgDbPrivate; G_DEFINE_TYPE_WITH_PRIVATE(AgDb, ag_db, G_TYPE_OBJECT); static void ag_db_non_select(AgDb *db, const gchar *sql) { GdaStatement *sth; gint nrows; const gchar *remain; GdaSqlParser *parser; AgDbPrivate *priv = ag_db_get_instance_private(db); GError *err = NULL; parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); g_assert(GDA_IS_SQL_PARSER(parser)); if ((sth = gda_sql_parser_parse_string( parser, sql, &remain, &err )) == NULL) { g_error( "SQL error: %s", (err && err->message) ? err->message : "no reason" ); } if ((nrows = gda_connection_statement_execute_non_select( priv->conn, sth, NULL, NULL, &err )) == -1) { g_error( "SQL error: %s", (err && err->message) ? err->message : "no details" ); } g_object_unref(sth); } static gboolean ag_db_table_exists(AgDb *db, const gchar *table) { GdaSqlParser *parser; GdaStatement *sth; GdaDataModel *result; GdaSet *params; GdaHolder *holder; const gchar *remain; gboolean ret; GValue table_value = G_VALUE_INIT; AgDbPrivate *priv = ag_db_get_instance_private(db); GError *err = NULL; parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); if ((sth = gda_sql_parser_parse_string( parser, "SELECT type \n" \ " FROM sqlite_master \n" \ " WHERE type = 'table' \n" \ " AND name = ##name::string", &remain, &err )) == NULL) { g_error( "SQL error: %s", (err && err->message) ? err->message : "no reason" ); } if (gda_statement_get_parameters(sth, ¶ms, &err) == FALSE) { g_error( "Params error: %s", (err && err->message) ? err->message : "no reason" ); } holder = gda_set_get_holder(params, "name"); g_value_init(&table_value, G_TYPE_STRING); g_value_set_string(&table_value, table); gda_holder_set_value(holder, &table_value, &err); g_value_unset(&table_value); result = gda_connection_statement_execute_select( priv->conn, sth, params, &err ); if (gda_data_model_get_n_rows(result) > 0) { ret = TRUE; } else { ret = FALSE; } g_object_unref(result); g_object_unref(sth); return ret; } /** * ag_db_select: * @db: the database object to work on * @err: a #GError or NULL * @sql: the query to execute * @...: a NULL terminated list of key-value pairs of the query parameters * * Returns: (transfer full): the #GdaDataModel as the result of the query */ static GdaDataModel * ag_db_select(AgDb *db, GError **err, const gchar *sql, ...) { GdaSqlParser *parser; const gchar *remain; GdaSet *params; GdaStatement *sth; GdaDataModel *ret; gchar *error = NULL; AgDbPrivate *priv = ag_db_get_instance_private(db); parser = g_object_get_data(G_OBJECT(priv->conn), "parser"); if ((sth = gda_sql_parser_parse_string( parser, sql, &remain, err )) == NULL) { g_error( "SQL error: %s", (*err && (*err)->message) ? (*err)->message : "no reason" ); } if (!gda_statement_get_parameters(sth, ¶ms, err)) { g_error( "SQL error: %s", (*err && (*err)->message) ? (*err)->message : "no reason" ); } if (params) { va_list ap; va_start(ap, sql); while (TRUE) { gchar *key; GdaHolder *holder; GType type; GValue value = G_VALUE_INIT; if ((key = va_arg(ap, gchar *)) == NULL) { break; } if ((holder = gda_set_get_holder( params, (const gchar *)key )) == NULL) { g_error("Error: holder %s is not defined in query.", key); } type = gda_holder_get_g_type(holder); g_value_init(&value, type); G_VALUE_COLLECT_INIT(&value, type, ap, 0, &error); if (error) { g_error("SQL GValue error: %s", error); } if (!gda_holder_set_value(holder, (const GValue *)&value, err)) { g_error( "SQL GdaHolder error: %s", (*err && (*err)->message) ? (*err)->message : "no reason" ); } } } ret = gda_connection_statement_execute_select(priv->conn, sth, params, err); g_object_unref(sth); return ret; } static void ag_db_check_version_table(AgDb *db) { if (ag_db_table_exists(db, "version")) { GdaDataModel *result; gint ret; g_debug( "Version table exists. " \ "Checking if db_version is %d and app_version is %s", SCHEMA_VERSION, PACKAGE_VERSION ); result = ag_db_select( db, NULL, "SELECT db_version, app_version FROM version", NULL ); ret = gda_data_model_get_n_rows(result); if (ret < 0) { g_error("No number of rows?"); } else if (ret > 1) { g_error("Error in database. Version table has more than one rows!"); } else if (ret == 0) { // TODO: Check schema against current one maybe? If it’s fine, we // may just add a row here. g_error("Error in database. Version table has no rows!"); } else { // Version table has one row const GValue *value; GdaDataModelIter *iter = gda_data_model_create_iter(result); gint version; gda_data_model_iter_move_next(iter); value = gda_data_model_iter_get_value_at(iter, 0); if (!G_VALUE_HOLDS_INT(value)) { g_error( "Database is invalid. " \ "version.db_version should be an integer." ); } version = g_value_get_int(value); if (version < SCHEMA_VERSION) { // TODO g_error("Update required!"); } else if (version > SCHEMA_VERSION) { const GValue *app_version_value; const gchar *app_version; app_version_value = gda_data_model_iter_get_value_at( iter, 1 ); app_version = g_value_get_string( app_version_value ); g_error( "The version of your database is from the future. " \ "It seems it was created by Astrognome v%s.", app_version ); } else { g_object_unref(result); } } } else { GValue db_version = G_VALUE_INIT, app_version = G_VALUE_INIT; AgDbPrivate *priv = ag_db_get_instance_private(db); g_value_init(&db_version, G_TYPE_INT); g_value_init(&app_version, G_TYPE_STRING); g_value_set_int(&db_version, SCHEMA_VERSION); g_value_set_static_string(&app_version, PACKAGE_VERSION); ag_db_non_select( db, "CREATE TABLE version (" \ "id INTEGER PRIMARY KEY, " \ "db_version INTEGER UNIQUE NOT NULL, " \ "app_version TEXT UNIQUE NOT NULL" \ ")" ); gda_connection_insert_row_into_table( priv->conn, "version", NULL, "db_version", &db_version, "app_version", &app_version, NULL ); } } static void ag_db_check_chart_table(AgDb *db) { ag_db_non_select( db, "CREATE TABLE IF NOT EXISTS chart (" \ "id INTEGER PRIMARY KEY, " \ "name TEXT NOT NULL, " \ "country_name TEXT, " \ "city_name TEXT, " \ "longitude DOUBLE NOT NULL, " \ "latitude DOUBLE NOT NULL, " \ "altitude DOUBLE, " \ "year INTEGER NOT NULL, " \ "month UNSIGNED INTEGER NOT NULL, " \ "day UNSIGNED INTEGER NOT NULL, " \ "hour UNSIGNED INTEGER NOT NULL, " \ "minute UNSIGNED INTEGER NOT NULL, " \ "second UNSIGNED INTEGER NOT NULL, " \ "timezone DOUBLE NOT NULL, " \ "house_system TEXT NOT NULL, " \ "note TEXT" \ ")" ); } static gint ag_db_verify(AgDb *db) { ag_db_check_version_table(db); ag_db_check_chart_table(db); return 0; } static void ag_db_init(AgDb *db) { GdaSqlParser *parser; GFile *user_data_dir = g_file_new_for_path(g_get_user_data_dir()), *ag_data_dir = g_file_get_child(user_data_dir, "astrognome"); AgDbPrivate *priv = ag_db_get_instance_private(db); gchar *path = g_file_get_path(ag_data_dir); GError *err = NULL; gda_init(); if (!g_file_query_exists(ag_data_dir, NULL)) { gchar *path = g_file_get_path(ag_data_dir); if (g_mkdir_with_parents(path, 0700) != 0) { g_error( "Data directory %s does not exist and can not be created.", path ); } } priv->dsn = g_strdup_printf("SQLite://DB_DIR=%s;DB_NAME=charts", path); g_free(path); g_object_unref(user_data_dir); g_object_unref(ag_data_dir); priv->conn = gda_connection_open_from_string( NULL, priv->dsn, NULL, GDA_CONNECTION_OPTIONS_NONE, &err ); if (priv->conn == NULL) { g_error( "Unable to initialize database: %s", (err && err->message) ? err->message : "no reason" ); } if ((parser = gda_connection_create_parser(priv->conn)) == NULL) { parser = gda_sql_parser_new(); } g_object_set_data_full( G_OBJECT(priv->conn), "parser", parser, g_object_unref ); ag_db_verify(db); } static void ag_db_dispose(GObject *gobject) { AgDbPrivate *priv = ag_db_get_instance_private(AG_DB(gobject)); g_object_unref(priv->conn); g_free(priv->dsn); G_OBJECT_CLASS(ag_db_parent_class)->dispose(gobject); } static void ag_db_finalize(GObject *gobject) { singleton = NULL; G_OBJECT_CLASS(ag_db_parent_class)->finalize(gobject); } static void ag_db_class_init(AgDbClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = ag_db_dispose; gobject_class->finalize = ag_db_finalize; } AgDb * ag_db_get(void) { if (!singleton) { singleton = AG_DB(g_object_new(AG_TYPE_DB, NULL)); } else { g_object_ref(singleton); } g_assert(singleton); return singleton; }