
/**
 * \file fetch.c
 *
 * \brief Low level SQLite database functions.
 *
 * This program is free software under the GNU General Public License
 * (>=v2). Read the file COPYING that comes with GRASS for details.
 *
 * \author Radim Blazek
 * \author Support for multiple connections by Markus Metz
 *
 * \date 2005-2011
 */

#include <stdlib.h>
#include <string.h>
#include <grass/dbmi.h>
#include <grass/glocale.h>
#include "globals.h"
#include "proto.h"


/**
 * \fn int db__driver_fetch (dbCursor *cn, int position, int *more)
 *
 * \brief Low level SQLite database table record fetch.
 *
 * NOTE: <b>position</b> is one of:
 * DB_NEXT, DB_FIRST, DB_CURRENT, DB_PREVIOUS, DB_LAST.
 *
 * \param[in] cn open database cursor
 * \param[in] position database position. See NOTE.
 * \param[in,out] more 1 = more data; 0 = no more data
 * \return int
 */

int db__driver_fetch(dbCursor * cn, int position, int *more)
{
    cursor *c;
    dbToken token;
    dbTable *table;
    int i, ret;
    int ns;

    /* get cursor token */
    token = db_get_cursor_token(cn);

    /* get the cursor by its token */
    if (!(c = (cursor *) db_find_token(token))) {
	append_error("Cursor not found");
	report_error();
	return DB_FAILED;
    }

    G_debug(3, "fetch row = %d", c->row);

    /* fetch on position */
    switch (position) {
    case DB_NEXT:
    case DB_FIRST:

	if (position == DB_FIRST)
	    c->row = -1;

	ret = sqlite3_step(c->statement);

	if (ret != SQLITE_ROW) {
	    /* get real result code */
	    ret = sqlite3_reset(c->statement);
	    if (ret != SQLITE_OK) {
		append_error("Cannot fetch:\n");
		append_error((char *)sqlite3_errmsg(sqlite));
		report_error();
		return DB_FAILED;
	    }
	    *more = 0;
	    return DB_OK;
	}
	c->row++;
	break;

    case DB_CURRENT:
	break;

    case DB_PREVIOUS:
	append_error("DB_PREVIOUS is not supported");
	report_error();
	return DB_FAILED;
	break;

    case DB_LAST:
	append_error("DB_LAST is not supported");
	report_error();
	return DB_FAILED;
	break;
    };

    *more = 1;

    /* get the data out of the descriptor into the table */
    table = db_get_cursor_table(cn);

    for (i = 0; i < c->nkcols; i++) {
	int col, litetype, sqltype;
	dbColumn *column;
	dbValue *value;
	const char *text;
	dbDateTime *dt;

	col = c->kcols[i];	/* known cols */

	column = db_get_table_column(table, i);
	sqltype = db_get_column_sqltype(column);
	/*      fails for dates: 
	   litetype  = db_get_column_host_type(column); 
	 */
	litetype = sqlite3_column_type(c->statement, col);
	text = (const char *)sqlite3_column_text(c->statement, col);

	value = db_get_column_value(column);
	db_zero_string(&value->s);

	/* Is null? */
	if (sqlite3_column_type(c->statement, col) == SQLITE_NULL) {
	    value->isNull = 1;
	    continue;
	}
	else {
	    value->isNull = 0;
	}

	G_debug(3, "col %d, litetype %d, sqltype %d: val = '%s'",
		col, litetype, sqltype, text);

	/* http://www.sqlite.org/capi3ref.html#sqlite3_column_type
	   SQLITE_INTEGER  1
	   SQLITE_FLOAT    2
	   SQLITE_TEXT     3
	   SQLITE_BLOB     4
	   SQLITE_NULL     5

	   lib/db/dbmi_base/sqltype.c defines:
	   DB_SQL_TYPE_*
	 */

	/* Note: we have set DATESTYLE TO ISO in db_driver_open_select_cursor() so datetime
	 *       format should be ISO */

	switch (sqltype) {
	case DB_SQL_TYPE_INTEGER:
	case DB_SQL_TYPE_SMALLINT:
	case DB_SQL_TYPE_SERIAL:
	    value->i = sqlite3_column_int(c->statement, col);
	    break;

	case DB_SQL_TYPE_REAL:
	case DB_SQL_TYPE_DOUBLE_PRECISION:
	    value->d = sqlite3_column_double(c->statement, col);
	    break;

	case DB_SQL_TYPE_DATE:
	    dt = &value->t;
	    dt->hour = 0;
	    dt->minute = 0;
	    dt->seconds = 0.0;
	    G_debug(3, "sqlite fetched date: %s", text);
	    ns = sscanf(text, "%4d-%2d-%2d", &dt->year, &dt->month, &dt->day);
	    if (ns != 3) {
		append_error("Cannot scan date:");
		append_error(text);
		report_error();
		return DB_FAILED;
	    }
	    break;

	case DB_SQL_TYPE_TIME:
	    dt = &value->t;
	    dt->year = 0;
	    dt->month = 0;
	    dt->day = 0;
	    G_debug(3, "sqlite fetched date: %s", text);
	    ns = sscanf(text, "%2d:%2d:%lf",
			&dt->hour, &dt->minute, &dt->seconds);
	    if (ns != 3) {
		append_error("Cannot scan time:");
		append_error(text);
		report_error();
		return DB_FAILED;
	    }
	    break;

	case DB_SQL_TYPE_TIMESTAMP:
	    dt = &value->t;
	    G_debug(3, "sqlite fetched timestamp: %s", text);
	    ns = sscanf(text, "%4d-%2d-%2d %2d:%2d:%lf",
			&dt->year, &dt->month, &dt->day,
			&dt->hour, &dt->minute, &dt->seconds);
	    if (ns != 6) {
		append_error("Cannot scan timestamp:");
		append_error(text);
		report_error();
		return DB_FAILED;
	    }
	    break;

	case DB_SQL_TYPE_INTERVAL:
	    dt = &value->t;
	    dt->year = 0;
	    dt->month = 0;
	    dt->day = 0;
	    dt->hour = 0;
	    dt->minute = 0;
	    G_debug(3, "sqlite fetched interval: %s", text);
	    G_warning
		("SQLite driver: parsing of interval values not implemented; assuming seconds");
	    ns = sscanf(text, "%lf", &dt->seconds);
	    if (ns != 1) {
		append_error("Cannot scan interval:");
		append_error(text);
		report_error();
		return DB_FAILED;
	    }
	    break;

	case DB_SQL_TYPE_DECIMAL:
	case DB_SQL_TYPE_NUMERIC:
	case DB_SQL_TYPE_CHARACTER:
	case DB_SQL_TYPE_TEXT:
	    db_set_string(&value->s, text);
	    break;
	}
    }

    G_debug(3, "Row fetched");

    return DB_OK;
}


/**
 * \fn int db__driver_get_num_rows (dbCursor *cn)
 *
 * \brief Gets number of rows in SQLite database table.
 *
 * \param[in] cn open database cursor
 * \return int number of rows in table
 */

int db__driver_get_num_rows(dbCursor * cn)
{
    cursor *c;
    dbToken token;
    int row, ret;

    /* get cursor token */
    token = db_get_cursor_token(cn);

    /* get the cursor by its token */
    if (!(c = (cursor *) db_find_token(token))) {
	append_error("Cursor not found");
	report_error();
	return DB_FAILED;
    }

    if (c->nrows > -1) {
	return (c->nrows);
    }

    sqlite3_reset(c->statement);

    c->nrows = 0;
    while ((ret = sqlite3_step(c->statement)) == SQLITE_ROW) {
	c->nrows++;
    }

    /* get real result code */
    ret = sqlite3_reset(c->statement);

    if (ret != SQLITE_OK) {
	append_error("Cannot get number of rows\n");
	append_error((char *)sqlite3_errmsg(sqlite));
	report_error();
	return DB_FAILED;
    }

    /* Reset cursor position */
    row = -1;
    if (c->row > -1) {
	while (sqlite3_step(c->statement) == SQLITE_ROW) {
	    if (row == c->row)
		break;

	    row++;
	}
    }

    return (c->nrows);
}
