
/*
 * Copyright (C) 2003, 2004 Laurent Sansonetti <lrz@gnome.org>
 *
 * This file is part of Ruby/Libgda.
 *
 * Ruby/Libgda is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * Ruby/Libgda 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with Ruby/Libgda; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

#include "rbgda.h"

/*
 * Class: Gda::Connection
 *
 * The Gda::Connection class offers access to all operations involving an
 * opened connection to a database. Gda::Connection objects are obtained via
 * the Gda::Client class.
 *
 * Once obtained, applications can use GdaConnection to execute commands, run
 * transactions, and get information about all objects stored in the
 * underlying database.
 */
VALUE cGdaConnection;

/*
 * Method: close
 *
 * Closes the connection to the underlying data source. After calling this
 * method, you should not use anymore the Gda::Connection object, since it may
 * have been destroyed.
 *
 * Returns: nil.
 */
VALUE rb_gda_connection_close(self)
    VALUE self;
{
    gda_connection_close(RGDA_CONNECTION(self));
    return Qnil;    /* object should be destroyed */
}

/*
 * Method: opened?
 *
 * Checks whether a connection is open or not.
 *
 * Returns: true if the connection is open, false if it is not.
 */
static VALUE rb_gda_connection_is_opened(self)
    VALUE self;
{
    return CBOOL2RVAL(gda_connection_is_open(RGDA_CONNECTION(self))); 
}

/*
 * Method: dsn
 *
 * Returns: the data source name the connection object is connected to.
 */
static VALUE rb_gda_connection_get_dsn(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_dsn(RGDA_CONNECTION(self)));
}

/*
 * Method: username
 *
 * Returns: the user name used to open this connection.
 */
static VALUE rb_gda_connection_get_username(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_username(RGDA_CONNECTION(self)));
}

/*
 * Method: password
 *
 * Returns: the pass word used to open this connection.
 */
static VALUE rb_gda_connection_get_password(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_password(RGDA_CONNECTION(self)));
}

/*
 * Method: cnc_string
 *
 * Gets the connection string used to open this connection.
 *
 * The connection string is the string sent over to the underlying database
 * provider, which describes the parameters to be used to open a connection on
 * the underlying data source.
 *
 * Returns: the connection string used when opening this connection.
 */
static VALUE rb_gda_connection_get_cnc_string(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_cnc_string(RGDA_CONNECTION(self)));
}

/*
 * Method: server_version
 *
 * Returns: the version string of the underlying database server.
 */
static VALUE rb_gda_connection_get_server_version(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_server_version(RGDA_CONNECTION(self)));
}

/*
 * Method: options
 *
 * Returns: the Gda::Connection::Options used to open this connection.
 */
static VALUE rb_gda_connection_get_options(self)
    VALUE self;
{
    return GFLAGS2RVAL(gda_connection_get_options(RGDA_CONNECTION(self)),
                       GDA_TYPE_CONNECTION_OPTIONS);
}

/*
 * Method: provider
 *
 * Returns: the provider ID used to open this connection.
 */
static VALUE rb_gda_connection_get_provider(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_provider(RGDA_CONNECTION(self)));
}

/*
 * Method: client
 *
 * Gets the Gda::Client object associated with a connection. This is always the
 * client that created the connection, as returned by
 * Gda::Client#open_connection.
 *
 * Returns: the Gda::Client to which the connection belongs to.
 */
static VALUE rb_gda_connection_get_client(self)
    VALUE self;
{
    GdaClient *client = gda_connection_get_client(RGDA_CONNECTION(self));
    return client != NULL
        ? RGDA_CLIENT_NEW(client)
        : Qnil;
}

/*
 * Method: set_client(client)
 * client: a Gda::Client object.
 *
 * Associates a Gda::Client with this connection. This method is not intended
 * to be called by applications.
 *
 * Returns: self.
 */
static VALUE rb_gda_connection_set_client(self, client)
    VALUE self, client;
{
    gda_connection_set_client(RGDA_CONNECTION(self),
                              RGDA_CLIENT(client));
    return self;
}

static GdaParameterList *parse_params(argc, argv, cmd)
    int argc;
    VALUE *argv, *cmd;
{
    VALUE list;
    rb_scan_args(argc, argv, "11", cmd, &list);
    return NIL_P(list) 
        ? NULL 
        : RGDA_PARAMETER_LIST(list);
}

/*
 * Method: execute_single_command(cmd, params=nil)
 * cmd: a Gda::Command object.
 * params: parameters, as a Gda::ParameterList object.
 *
 * Executes a single command on the given connection.
 *
 * This method lets you retrieve a simple data model from the underlying
 * difference, instead of having to retrieve a list of them, as is the case
 * with Gda::Connection#execute_command.
 *
 * Returns: a Gda::DataModel containing the data returned by the data source,
 * or nil on error.
 */
static VALUE rb_gda_connection_execute_single_command(argc, argv, self)
    int argc;
    VALUE *argv, self;
{
    GdaParameterList *plist;
    GdaDataModel *mod;
    VALUE cmd;
    GError *error;

    error = NULL;
    plist = parse_params(argc, argv, &cmd);
    mod = gda_connection_execute_single_command(RGDA_CONNECTION(self),
                                                RGDA_COMMAND(cmd),
                                                plist
#if defined(GDA_AT_LEAST_1_3)
                                                , &error
#endif
                                                );

    if (mod == NULL) {
        if (error != NULL)
            RAISE_GERROR(error);
        return Qnil;
    }
    
    return RGDA_DATAMODEL_NEW(mod);
}

/*
 * Method: execute_command(cmd, params=nil)
 * cmd: a Gda::Command object.
 * params: parameters, as a Gda::ParameterList object.
 *
 * Executes a command on the underlying data source.
 *
 * This method provides the way to send several commands at once to the data
 * source being accessed by the Gda::Connection. The Gda::Command specified
 * can contain a list of commands in its "text" property (usually a set of SQL
 * commands separated by ';').
 *
 * Returns: an Array of Gda::DataModel objects, as returned by the underlying
 * provider.
 */
static VALUE rb_gda_connection_execute_command(argc, argv, self)
    int argc;
    VALUE *argv, self;
{
    GdaParameterList *plist;
    VALUE cmd, arr;
    GList *list;
    GError *error;

    error = NULL;
    arr = rb_ary_new();
    plist = parse_params(argc, argv, &cmd);
    list = gda_connection_execute_command(RGDA_CONNECTION(self),
                                          RGDA_COMMAND(cmd),
                                          plist
#if defined(GDA_AT_LEAST_1_3)
                                          , &error
#endif
                                          );
    if (list == NULL) {
        if (error != NULL)
            RAISE_GERROR(error);
    }
    else { 
        rb_ary_push(arr, RGDA_DATAMODEL_NEW(list->data));
    }
    
    return arr;        
}

/*
 * Method: execute_non_query(cmd, params=nil)
 * cmd: a Gda::Command object.
 * params: parameters, as a Gda::ParameterList object.
 *
 * Executes a single command on the underlying database, and gets the number
 * of rows affected.
 *
 * Returns: the number of affected rows by the executed command, or -1 on
 * error.
 */
static VALUE rb_gda_connection_execute_non_query(argc, argv, self)
    int argc;
    VALUE *argv, self;
{
    GdaParameterList *plist;
    VALUE cmd;
    GError *error;
    int ret;

    error = NULL;
    plist = parse_params(argc, argv, &cmd);
    ret = gda_connection_execute_non_query(RGDA_CONNECTION(self),
                                           RGDA_COMMAND(cmd),
                                           plist
#if defined(GDA_AT_LEAST_1_3)
                                           , &error
#endif
                                           );

    if (ret == -1 && error != NULL)
        RAISE_GERROR(error);

    return INT2FIX(ret);
}

#if !defined(GDA_AT_LEAST_1_3)
/*
 * Method: errors
 *
 * Retrieves a list of the last errors ocurred in the connection.
 *
 * Returns: an Array of Gda::Error objects.
 */
static VALUE rb_gda_connection_get_errors(self)
    VALUE self;
{
    const GList* list;
    VALUE arr;

    arr = rb_ary_new();
    for (list = gda_connection_get_errors(RGDA_CONNECTION(self));
         list != NULL;
         list = g_list_next(list))
    {
        rb_ary_push(arr, RGDA_ERROR_NEW(list->data));
    }
    return arr;
}

/*
 * Method: add_errors(*errors)
 * errors: one or several Gda::Error objects.
 * 
 * Adds one or more errors to the connection. This method is usually called by
 * providers, to inform clients of errors that happened during some operation.
 *
 * As soon as this method is called, the connection object (and the associated
 * Gda::Client object) emits the "error" signal, to which clients can connect
 * to be informed of errors.
 *
 * Returns: self.
 */
static VALUE rb_gda_connection_add_errors(self, errors)
    VALUE self, errors;
{
    GList *list = NULL;
    int i;

    for (i = 0; i < RARRAY(errors)->len; i++) {
        list = g_list_append(list, RGDA_ERROR(rb_ary_entry(errors, i)));
    }
    gda_connection_add_error_list(RGDA_CONNECTION(self), list);
    /* the list is automatically freed */
    return self;
}
#endif

/*
 * Method: begin_transaction(xaction)
 * xaction: a Gda::Transaction object.
 *
 * Starts a transaction on the data source, identified by the xaction
 * parameter.
 *
 * Before starting a transaction, you can check whether the underlying provider
 * does support transactions or not by using the Gda::Connection#supports?
 * method.
 *
 * Returns: true if the transaction was started successfully, false otherwise.
 */
static VALUE rb_gda_connection_begin_transaction(self, tr)
    VALUE self, tr;
{
    gda_connection_begin_transaction(RGDA_CONNECTION(self),
                                     RGDA_TRANSACTION(tr));
    return self;
}

/*
 * Method: commit_transaction(xaction)
 * xaction: a Gda::Transaction object.
 *
 * Commits the given transaction to the backend database. You need to do
 * Gda::Connection#begin_transaction first.
 *
 * Returns: true if the transaction was finished successfully, false otherwise.
 */
static VALUE rb_gda_connection_commit_transaction(self, tr)
    VALUE self, tr;
{
    gda_connection_commit_transaction(RGDA_CONNECTION(self),
                                      RGDA_TRANSACTION(tr));
    return self;
}

/*
 * Method: rollback_transaction(xaction)
 * xaction: a Gda::Transaction object.
 *
 * Rollbacks the given transaction. This means that all changes made to the
 * underlying data source since the last call to 
 * Gda:Connection#begin_transaction or Gda::Connection#commit_transaction
 * will be discarded.
 *
 * Returns: true if the operation was successful, false otherwise.
 */
static VALUE rb_gda_connection_rollback_transaction(self, tr)
    VALUE self, tr;
{
    gda_connection_rollback_transaction(RGDA_CONNECTION(self),
                                        RGDA_TRANSACTION(tr));
    return self;
}

/*
 * Method: database
 *
 * Returns: the name of the currently active database in the connection.
 */
static VALUE rb_gda_connection_get_database(self)
    VALUE self;
{
    return CSTR2RVAL(gda_connection_get_database(RGDA_CONNECTION(self)));
}

/*
 * Method: change_database(name)
 * name: name of the database to switch to.
 *
 * Changes the current database for the given connection. This operation is
 * not available in all providers.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_change_database(self, db)
    VALUE self, db;
{
    return CBOOL2RVAL(gda_connection_change_database(RGDA_CONNECTION(self),
                                                     RVAL2CSTR(db)));
}

#if !defined(GDA_AT_LEAST_1_3)
/*
 * Method: create_database(name)
 * name: database name.
 *
 * Creates a new database on the connection.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_create_database(self, db)
    VALUE self, db;
{
    return CBOOL2RVAL(gda_connection_create_database(RGDA_CONNECTION(self),
                                                     RVAL2CSTR(db)));
}

/*
 * Method: drop_database(name)
 * name: database name.
 *
 * Drops a database from the connection.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_drop_database(self, db)
    VALUE self, db;
{
    return CBOOL2RVAL(gda_connection_drop_database(RGDA_CONNECTION(self),
                                                   RVAL2CSTR(db)));
}
#endif

#if defined(GDA_AT_LEAST_1_1)
/*
 * Method: reset!
 *
 * Resets the connection.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_reset(self)
    VALUE self;
{
    return CBOOL2RVAL(gda_connection_reset(RGDA_CONNECTION(self)));
}

/*
 * Method: clear_errors
 *
 * This method lets you clear the list of Gda::Error's of the
 * current connection. This is useful to reuse a Gda::Connection
 * because next uses of Gda::Connection#errors will return an empty
 * list.
 * 
 * Returns: self.
 */
static VALUE rb_gda_connection_clear_errors(self)
    VALUE self;
{
    gda_connection_clear_error_list(RGDA_CONNECTION(self));
    return self;
}

/*
 * Method: create_table(name, attributes)
 * name: the name of the table to create.
 * attributes: an array of Gda::FieldAttributes objects.
 *
 * Creates a table on the current connection from the given set of fields.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_create_table(self, name, attributes)
    VALUE self, name, attributes;
{
    GdaFieldAttributes **c_attributes;
    int i;
    gboolean ok;
    
    c_attributes = g_new(GdaFieldAttributes *, RARRAY(attributes)->len);
    for (i = 0; i < RARRAY(attributes)->len; i++)
        c_attributes[i] = RGDA_FIELD_ATTRIBUTES(rb_ary_entry(attributes, i));
    ok = gda_connection_create_table(RGDA_CONNECTION(self), RVAL2CSTR(name), 
                                     (const GdaFieldAttributes **)c_attributes);
    free(c_attributes);

    return CBOOL2RVAL(attributes);
}

/*
 * Method: drop_table(name)
 * name: the name of the table to drop.
 *
 * Drops a table from the database.
 *
 * Returns: true if successful, false otherwise.
 */
static VALUE rb_gda_connection_drop_table(self, name)
    VALUE self, name;
{
    return CBOOL2RVAL(gda_connection_drop_table(RGDA_CONNECTION(self), RVAL2CSTR(name)));
}
#endif /* GDA_AT_LEAST_1_1 */

/*
 * Method: supports?(feature)
 * feature: feature to ask for (see Gda::Connection::Feature).
 *
 * Asks the underlying provider for if a specific feature is supported.
 *
 * Returns: true if the provider supports it, false if not.
 */
static VALUE rb_gda_connection_supports(self, feature)
    VALUE self, feature;
{
    return CBOOL2RVAL(gda_connection_supports(RGDA_CONNECTION(self),
                                              RVAL2GENUM(feature, GDA_TYPE_CONNECTION_FEATURE)));
}

/*
 * Method: get_schema(schema, params=nil)
 * schema: database schema to get (see Gda::Connection::Schema).
 * params: an optional Gda::ParameterList object.
 *
 * Asks the underlying data source for a list of database objects.
 *
 * This is the method that lets applications ask the different providers
 * about all their database objects (tables, views, procedures, etc).
 * The set of database objects that are retrieved are given by the 2 parameters
 * of this method: 'schema' (which specifies the specific schema required) and
 * 'params' (which is a list of Gda::Parameter objects that can be used to give
 * more detail about the objects to be returned).
 *
 * The list of parameters is specific to each schema type.
 *
 * Returns: a Gda::DataModel containing the data required if successful, 
 * nil otherwise.
 */
static VALUE rb_gda_connection_get_schema(argc, argv, self)
    int argc;
    VALUE *argv, self;
{
    VALUE schema, params;
    GdaDataModel *model;

    rb_scan_args(argc, argv, "11", &schema, &params);

    model = gda_connection_get_schema(RGDA_CONNECTION(self),
                                      RVAL2GENUM(schema, GDA_TYPE_CONNECTION_SCHEMA),
				      NIL_P(params) ? NULL : RGDA_PARAMETER_LIST(params));
    return model != NULL
        ? RGDA_DATAMODEL_NEW(model)
	: Qnil;
}

void Init_gda_connection(void) {
    VALUE c = G_DEF_CLASS(GDA_TYPE_CONNECTION, "Connection", mGda);

    rb_define_method(c, "close",   rb_gda_connection_close,     0);
    rb_define_method(c, "opened?", rb_gda_connection_is_opened, 0);
  
    rb_define_method(c, "dsn",            rb_gda_connection_get_dsn,            0);
    rb_define_method(c, "username",       rb_gda_connection_get_username,       0);
    rb_define_method(c, "password",       rb_gda_connection_get_password,       0);
    rb_define_method(c, "cnc_string",     rb_gda_connection_get_cnc_string,     0);
    rb_define_method(c, "server_version", rb_gda_connection_get_server_version, 0);
    rb_define_method(c, "options",        rb_gda_connection_get_options,        0);
    rb_define_method(c, "provider",       rb_gda_connection_get_provider,       0);

    rb_define_method(c, "client",     rb_gda_connection_get_client, 0);
    rb_define_method(c, "set_client", rb_gda_connection_set_client, 1);

    rb_define_method(c, "execute_command",        rb_gda_connection_execute_command,        -1);
    rb_define_method(c, "execute_single_command", rb_gda_connection_execute_single_command, -1);
    rb_define_method(c, "execute_non_query",      rb_gda_connection_execute_non_query,      -1);
    rb_define_alias(c, "execute", "execute_single_command");

    rb_define_method(c, "begin_transaction",    rb_gda_connection_begin_transaction,    1);
    rb_define_method(c, "commit_transaction",   rb_gda_connection_commit_transaction,   1);
    rb_define_method(c, "rollback_transaction", rb_gda_connection_rollback_transaction, 1);

#if !defined(GDA_AT_LEAST_1_3)
    rb_define_method(c, "errors",     rb_gda_connection_get_errors,  0);
    rb_define_method(c, "add_errors", rb_gda_connection_add_errors, -2);
#endif
    
    rb_define_method(c, "database",        rb_gda_connection_get_database,    0);
    rb_define_method(c, "change_database", rb_gda_connection_change_database, 0);
#if !defined(GDA_AT_LEAST_1_3)
    rb_define_method(c, "create_database", rb_gda_connection_create_database, 0);
    rb_define_method(c, "drop_database",   rb_gda_connection_drop_database,   0);
#endif
    
#if defined(GDA_AT_LEAST_1_1)
    rb_define_method(c, "reset!",  rb_gda_connection_reset,     0);
    rb_define_method(c, "clear_errors", rb_gda_connection_clear_errors, 0);
    rb_define_method(c, "create_table", rb_gda_connection_create_table, 2);
    rb_define_method(c, "drop_table", rb_gda_connection_drop_table, 1);
#endif
    
    rb_define_method(c, "supports?", rb_gda_connection_supports, 1);

    rb_define_method(c, "get_schema", rb_gda_connection_get_schema, -1);

    G_DEF_CLASS(GDA_TYPE_CONNECTION_OPTIONS, "Options", c);
    G_DEF_CONSTANTS(c, GDA_TYPE_CONNECTION_OPTIONS, "GDA_CONNECTION_");
    G_DEF_CLASS(GDA_TYPE_CONNECTION_FEATURE, "Feature", c);
    G_DEF_CONSTANTS(c, GDA_TYPE_CONNECTION_FEATURE, "GDA_CONNECTION_");
    G_DEF_CLASS(GDA_TYPE_CONNECTION_SCHEMA, "Schema", c);
    G_DEF_CONSTANTS(c, GDA_TYPE_CONNECTION_SCHEMA,  "GDA_CONNECTION_");
    G_DEF_SETTERS(c);

    cGdaConnection = c;
}

