The R Project SVN R-packages

Rev

Rev 3684 | Rev 4798 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

#include <stdio.h>
#include <string.h>

#define __SQLITE_WORKSPACE__
#include "sqlite_dataframe.h"

/* global variables */
sqlite3 *g_workspace = NULL;
char *g_sql_buf[NBUFS];
int g_sql_buf_sz[NBUFS];

/****************************************************************************
 * UTILITY FUNCTIONS
 ****************************************************************************/

/* test if a file is a sqlite database file */
sqlite3* _is_sqlitedb(char *filename) {
    sqlite3 *db;
    int res;
    res = sqlite3_open(filename, &db);
    if (res != SQLITE_OK) { goto is_sqlitedb_FAIL; }

    sqlite3_stmt *stmt; char *sql = "select * from sqlite_master";
    res = sqlite3_prepare(db, sql, -1, &stmt, 0);
    if (stmt != NULL) sqlite3_finalize(stmt);
    /*char **result_set;
    char nrow, ncol;
    res = sqlite3_get_table(db, "select * from sqlite_master limit 0", 
            &result_set, &nrow, &ncol, NULL);
    sqlite3_free_table(result_set);*/
    if (res != SQLITE_OK) goto is_sqlitedb_FAIL;

    return db;

is_sqlitedb_FAIL:
    sqlite3_close(db);
    return NULL;
}

/* test if a file is a SQLiteDF workspace */
sqlite3* _is_workspace(char *filename) {
    sqlite3* db = _is_sqlitedb(filename); 

    if (db != NULL) {
        sqlite3_stmt *stmt;
        char *sql = "select * from workspace";
        int res = sqlite3_prepare(db, sql, -1, &stmt, 0), ncols;
        if ((res != SQLITE_OK) || /* no workspace table */
              ((ncols = sqlite3_column_count(stmt)) != WORKSPACE_COLUMNS) ||
              /* below also checks the ordering of the columns */
              (strcmp(sqlite3_column_name(stmt, 0), "rel_filename") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 0), "text") != 0) ||
              (strcmp(sqlite3_column_name(stmt, 1), "full_filename") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 1), "text") != 0) ||
              (strcmp(sqlite3_column_name(stmt, 2), "internal_name") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 2), "text") != 0) ||
              (strcmp(sqlite3_column_name(stmt, 3), "loaded") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 3), "bit") != 0) ||
              (strcmp(sqlite3_column_name(stmt, 4), "uses") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 4), "int") != 0) ||
              (strcmp(sqlite3_column_name(stmt, 5), "used") != 0) ||
              (strcmp(sqlite3_column_decltype(stmt, 5), "bit") != 0)) {
            sqlite3_finalize(stmt); sqlite3_close(db); db = NULL;
        } else {
            sqlite3_finalize(stmt);
        }
    }

    return db;
}

/* test if a file is a sqlite.data.frame. returns the internal name of the
 * sdf if file is an sdf or NULL otherwise */
char * _is_sdf2(char *filename) {
    sqlite3* db = _is_sqlitedb(filename); 
    char *ret = (db == NULL) ? NULL : filename;

    if (ret) {
        sqlite3_stmt *stmt;
        char *sql = "select * from sdf_attributes where attr='name'";
        int res, ncols;
        res = sqlite3_prepare(db, sql, -1, &stmt, NULL);
        ret = (((res == SQLITE_OK) && /* no attribute table */
               ((ncols = sqlite3_column_count(stmt)) == 2) &&
               (strcmp(sqlite3_column_name(stmt, 0), "attr") == 0) &&
               (strcmp(sqlite3_column_decltype(stmt, 0), "text") == 0) &&
               (strcmp(sqlite3_column_name(stmt, 1), "value") == 0) &&
               (strcmp(sqlite3_column_decltype(stmt, 1), "text") == 0))) ? ret : NULL ;
        
        if (ret == NULL) goto _is_sdf_cleanup;

        /* get internal name. we are assuming here that the 1st row of the attributes
         * table always contains the column name */
        res = sqlite3_step(stmt);
        ret = (res == SQLITE_ROW) ? ret : NULL;
        if (ret == NULL) goto _is_sdf_cleanup;

        /* copy to buf2, because when we finalize stmt, we won't be sure
         * if sqlite3_column_text()'s ret value will still be there */
        strcpy(g_sql_buf[2], (char *)sqlite3_column_text(stmt, 1));
        ret = g_sql_buf[2];
        sqlite3_finalize(stmt);
        
        sql = "select * from sdf_data";
        res = sqlite3_prepare(db, sql, -1, &stmt, NULL);
        ret = (res == SQLITE_OK) ? ret : NULL;  /* if not, missing data table */

_is_sdf_cleanup:
        sqlite3_finalize(stmt);
        sqlite3_close(db);
    }

    return ret;
}

/* remove an sdf from the workspace */
void _delete_sdf2(const char *iname) {
    sprintf(g_sql_buf[2], "delete from workspace where internal_name='%s';", iname);
    _sqlite_exec(g_sql_buf[2]);
}

/* add a sdf to the workspace */
int _add_sdf1(char *filename, char *internal_name) {
    sprintf(g_sql_buf[1], "insert into workspace(rel_filename, full_filename, "
            "internal_name, loaded, uses, used) values('%s', '%s', '%s', 0, 0, 0)",
            filename, _get_full_pathname2(filename), internal_name);

    return _sqlite_exec(g_sql_buf[1]);
}


static char* _get_sdf_detail2(char *iname, int what) {
    sqlite3_stmt *stmt;
    char * ret; int res;

    sprintf(g_sql_buf[2], "select full_filename from workspace where "
            "internal_name='%s'", iname);
    sqlite3_prepare(g_workspace, g_sql_buf[2], -1, &stmt, NULL);
    res = sqlite3_step(stmt);
    sqlite3_finalize(stmt);

    if (res == SQLITE_DONE) {
        ret = NULL;
    } else {
        ret = g_sql_buf[2];
        switch (what) {
            case SDF_DETAIL_EXISTS: 
                break; /* doesn't matter */
            case SDF_DETAIL_FULLFILENAME:
                strcpy(g_sql_buf[2], (char *)sqlite3_column_text(stmt, 0));
                break;
        }
    }

    return ret;
}

/* returns TRUE if sdf exists in the workspace */
int _sdf_exists2(char *iname) {
    return _get_sdf_detail2(iname, SDF_DETAIL_EXISTS) != NULL;
}

/****************************************************************************
 * WORKSPACE FUNCTIONS
 ****************************************************************************/

SEXP sdf_init_workspace() {
    int file_idx = 0, i;
    char *basename = "workspace", *filename;
    SEXP ret;

    /* initialize sql_buf */
    for (i = 0; i < NBUFS; i++) {
        if (g_sql_buf[i] == NULL) {
            g_sql_buf_sz[i] = 1024;
            g_sql_buf[i] = Calloc(g_sql_buf_sz[i], char);
        }
    }

    /* create symbols used for object attributes */
    SDF_RowNamesSymbol = install("sdf.row.names");
    SDF_VectorTypeSymbol = install("sdf.vector.type");
    SDF_DimSymbol = install("sdf.dim");
    SDF_DimNamesSymbol = install("sdf.dimnames");

    /*
     * check for workspace.db, workspace1.db, ..., workspace9999.db if they
     * are valid workspace file. if one is found, use that as the workspace.
     */
    filename = R_alloc(28, sizeof(char)); /* .SQLiteDF/workspace10000.db\0 */
    sprintf(filename, ".SQLiteDF/%s.db", basename);
    while(_file_exists(filename) && file_idx < 10000) {
        if ((g_workspace = _is_workspace(filename)) != NULL) break;
        warning("%s is not a SQLiteDF workspace\n", filename);
        sprintf(filename, ".SQLiteDF/%s%d.db", basename, ++file_idx);
    }

    if ((g_workspace == NULL) && (file_idx < 10000)) {
        /* no workspace found but there are still "available" file name */
        /* if (file_idx) warn("workspace will be stored at #{filename}") */
        sqlite3_open(filename, &g_workspace);
        _sqlite_exec("create table workspace(rel_filename text, full_filename text,"
               "internal_name text unique, loaded bit, uses int, used bit)");
        ret = ScalarLogical(TRUE);
    } else if (g_workspace != NULL) {
        /* a valid workspace has been found, load each of the tables */
        int res, nrows, ncols; 
        char **result_set, *fname, *iname;
        
        /* since only 30 can be loaded, sort sdf's by # of uses the last time */
        res = sqlite3_get_table(g_workspace, "select rel_filename, internal_name"
                " from workspace order by uses desc", 
                &result_set, &nrows, &ncols, NULL);

        /* reset fields uses and loaded to 0 and false */
        res = _sqlite_exec("update workspace set uses=0, loaded=0, used=0");
        _sqlite_error(res);
        
        if (res == SQLITE_OK && nrows >= 1 && ncols == 2) {
            int maxrows;  /* actual # of sdfs to be attached */
            maxrows = (nrows > 30) ? 30 : nrows;
            for (i = 1; i <= maxrows && i < nrows; i++) {
                /* we will use rel_filename in opening the file, so that
                 * if the user is "sensible", files will be dir agnostic */
                fname = result_set[i*ncols]; iname = result_set[i*ncols+1];

                if (!USE_SDF1(iname, TRUE, FALSE)) maxrows++;

                /* update full_filename */
                sprintf(g_sql_buf[0], "update workspace set full_filename='%s' "
                        "where iname='%s'", _get_full_pathname2(fname), iname);
                _sqlite_exec(g_sql_buf[0]);
            }
        }
        sqlite3_free_table(result_set);

        /* notify if a previous workspace is reloaded */
        Rprintf("[Previous SQLiteDF workspace restored (%s)]\n", filename);

        ret = ScalarLogical(TRUE);
    } else { /* can't find nor create workspace */
        ret = ScalarLogical(FALSE);
    }

    /* register sqlite math functions */
    __register_vector_math();
    return ret;
}
        
int USE_SDF1(const char *iname, int exists, int protect) {
    sqlite3_stmt *stmt;
    int loaded, res;
    char *iname_final = (char *)iname;

    /* determine if iname is loaded */
    sprintf(g_sql_buf[2], "select loaded, rel_filename from workspace where internal_name='%s'", iname);
    sqlite3_prepare(g_workspace, g_sql_buf[2], -1, &stmt, NULL);
    res = sqlite3_step(stmt);
    if (exists && res != SQLITE_ROW) { 
        sqlite3_finalize(stmt); 
        error("No SDF with name '%s' found in the workspace.\n", iname);
    }
    loaded = (res == SQLITE_ROW) ? sqlite3_column_int(stmt, 0) : 0;
    strcpy(g_sql_buf[1], (char*)sqlite3_column_text(stmt, 1)); /* ref filename */
    sqlite3_finalize(stmt);

    if (!loaded) {
        char *fname = g_sql_buf[1];

        /* test first if we will be loading valid sdf */
        if (exists && !_file_exists(fname)) {
            _delete_sdf2(iname);
            warning("SDF %s does not exist.\n", iname);
            return 0;
        }

        if (exists && _is_sdf2(fname) == NULL) {
            _delete_sdf2(iname);
            warning("%s is not a valid SDF.\n", fname);
            return 0;
        }

        /* g_sql_buf[2] contains the internal name as stored in the sdf
         * attribute. sync workspace record to that if needed */
        if (exists && strcmp(iname, g_sql_buf[2]) != 0) {
            if (!_is_r_sym(g_sql_buf[2])) {
                warning("name \"%s\" stored in SDF is not valid. Ignoring that name...", g_sql_buf[2]);
                goto __out_of_syncname;
            }
            iname_final = R_alloc(strlen(g_sql_buf[2]) + 1, sizeof(g_sql_buf[2]));
            strcpy(iname_final, g_sql_buf[2]);

            sprintf(g_sql_buf[2], "update workspace set internal_name='%s' where "
                        "internal_name='%s'", iname_final, iname);
            _sqlite_error(_sqlite_exec(g_sql_buf[2]));

        }
__out_of_syncname:

        /* unload sdf's if we run to the MAX_ATTACHED limit */
        _prepare_attach2();

        /* set loaded to true */
        sprintf(g_sql_buf[2], "update workspace set loaded=1 where internal_name='%s'", iname_final);
        res = _sqlite_exec(g_sql_buf[2]);
        _sqlite_error(res);

        /* load, using relative path */
        sprintf(g_sql_buf[2], "attach '%s' as [%s]", fname, iname_final);
        res = _sqlite_exec(g_sql_buf[2]);
        if (_sqlite_error(res)) return 0;
    }

    /* upgrade its uses count, and protect if necessary */
    if (protect) protect = 1; 
    sprintf(g_sql_buf[2], "update workspace set uses=uses+1, used=%d" 
            " where internal_name='%s'", protect, iname_final);
    _sqlite_exec(g_sql_buf[2]);
    return 1;
}

int UNUSE_SDF2(const char *iname) {
    sprintf(g_sql_buf[2], "update workspace set used=0 where internal_name='%s'", iname);
    _sqlite_exec(g_sql_buf[2]);
    return 1;
}

        
    
SEXP sdf_finalize_workspace() {
    SEXP ret;
    int i;
    ret = ScalarLogical(sqlite3_close(g_workspace) == SQLITE_OK);
    for (i = 0; i < NBUFS; i++) Free(g_sql_buf[i]);
    return ret;
} 


SEXP sdf_list_sdfs(SEXP pattern) {
    SEXP ret;
    char **result;
    int nrow, ncol, res, i;

    if (TYPEOF(pattern) != STRSXP) {
        res = sqlite3_get_table(g_workspace, "select internal_name from workspace "
                "order by loaded desc, uses desc", &result, &nrow, &ncol, NULL);
    } else {
        /* since internal_names must be a valid r symbol, 
           did not check for "'" */
        sprintf(g_sql_buf[0], "select internal_name from workspace where "
                "internal_name like '%s%%'", CHAR(STRING_ELT(pattern, 0)));
        res = sqlite3_get_table(g_workspace, g_sql_buf[0], &result, &nrow,
                &ncol, NULL);
    }

    if (_sqlite_error(res)) return R_NilValue;
    PROTECT(ret = NEW_CHARACTER(nrow));
    
    for (i = 0; i < nrow; i++) SET_STRING_ELT(ret, i, mkChar(result[i+1]));

    sqlite3_free_table(result);
    UNPROTECT(1);
    return ret;
}

SEXP sdf_get_sdf(SEXP name) {    
    char *iname;
    SEXP ret;

    if (TYPEOF(name) != STRSXP) {
        error("Argument must be a string containing the SDF name.");
    }
    iname = CHAR(STRING_ELT(name, 0));

    if (!USE_SDF1(iname, TRUE, FALSE)) return R_NilValue;

    ret = _create_sdf_sexp(iname);
    return ret;
}

SEXP sdf_attach_sdf(SEXP filename, SEXP internal_name) {
    /* when studying this, please be mindful of the global buffers used.
     * you have been warned */
    char *fname=NULL, *iname, *iname_orig=NULL;
    int fnamelen=0, res;
    sqlite3_stmt *stmt;

    if (IS_CHARACTER(filename)) {
        fname = CHAR_ELT(filename, 0);
        fnamelen = strlen(fname);
    } else {
        error("filename argument must be a string.");
    }

    if (strcmp(fname+(fnamelen-3),".db") != 0) {
        error("will not attach because extension is not .db");
    }

    /* check if it is a valid sdf file */
    if (_is_sdf2(fname) == NULL) {
        error("%s is not a valid SDF.", fname);
    } else {
        /* _is_sdf2 puts the orig iname in buf2. transfer data to buf0 since
         * functions called below will use buf2 */
        strcpy(g_sql_buf[0], g_sql_buf[2]);
        iname_orig = g_sql_buf[0];
    }

    /* check if file to be attached exists in the workspace already */
    _get_full_pathname2(fname);
    res = sqlite3_prepare(g_workspace, "select internal_name from workspace where full_filename=?",
            -1, &stmt, NULL);
    sqlite3_bind_text(stmt, 1, g_sql_buf[2], -1, SQLITE_STATIC);
    res = sqlite3_step(stmt);
    if (res == SQLITE_ROW) {
        warning("this sdf is already attached as '%s'\n",
                sqlite3_column_text(stmt, 0));
        sqlite3_finalize(stmt);
        return R_NilValue;
    } else sqlite3_finalize(stmt);


    /* internal_name checking and processing. */
    if (IS_CHARACTER(internal_name)) {
        /* if name is specified, rename the sdf. original internal name is the
         * one stored at sdf_attribute */
        iname = CHAR_ELT(internal_name, 0);
        if (!_is_r_sym(iname)) {
            error("%s is not a valid R symbol.", iname);
        }
    } else {
        /* if no name is specified, use original internal name */
        iname = (char *)iname_orig;  /* g_sql_buf[0]! */
    }

    /* check if internal name is already used in the workspace */
    res = sqlite3_prepare(g_workspace, "select full_filename from workspace "
           " where internal_name=?", -1, &stmt, NULL);
    sqlite3_bind_text(stmt, 1, iname, -1, SQLITE_STATIC);
    res = sqlite3_step(stmt);
    if (res == SQLITE_ROW) {
        strcpy(g_sql_buf[0], (char *)sqlite3_column_text(stmt,1));
        sqlite3_finalize(stmt);
        error("the sdf internal name '%s' is already used by file %s.",
                iname, g_sql_buf[0]);
    } 
    sqlite3_finalize(stmt);
    
    /* add it to workspace */
    res = _add_sdf1(fname, iname);
    if (_sqlite_error(res)) return R_NilValue;

    /* attach using USE_SDF1, detach other SDF if necessary */
    USE_SDF1(iname, TRUE, FALSE);

    /* if internal name found in newly-attached-SDF is the same as the
     * name wanted by the user, do nothing. otherwise, update sdf_attribute
     * on attached-SDF. this is like attachSdf then renameSdf */
    if (iname != iname_orig && strcmp(iname, iname_orig) != 0) {
        sprintf(g_sql_buf[1], "update [%s].sdf_attributes set value=? where attr='name'",
                iname);
        res = sqlite3_prepare(g_workspace, g_sql_buf[0], -1, &stmt, NULL);
        if (_sqlite_error(res)) { 
            sqlite3_finalize(stmt); 
            sprintf(g_sql_buf[1], "detach [%s]", iname);
            _sqlite_exec(g_sql_buf[1]);
            return R_NilValue;
        }
        res = sqlite3_bind_text(stmt, 1, iname, -1, SQLITE_STATIC);
        res = sqlite3_step(stmt);
        sqlite3_finalize(stmt);
    } 

    return _create_sdf_sexp(iname);
}

/* not necessary anymore, since stuffs will eventually be detached
 * if we keep on adding new sdfs */
SEXP sdf_detach_sdf(SEXP internal_name) {
    char *iname;
    int res;

    if (!IS_CHARACTER(internal_name)) {
        error("iname argument is not a string.");
    }

    iname = CHAR_ELT(internal_name, 0);
    sprintf(g_sql_buf[0], "detach [%s]", iname);

    res = _sqlite_exec(g_sql_buf[0]);
    res = !_sqlite_error(res);

    if (res) _delete_sdf2(iname);
    
    return ScalarLogical(res);
}

SEXP sdf_rename_sdf(SEXP sdf, SEXP name) {
    char *iname, *path, *newname;
    int res, ret_tmp;

    iname = SDF_INAME(sdf);
    newname = CHAR_ELT(name, 0);
    
    if (!USE_SDF1(iname, TRUE, FALSE)) return R_NilValue;

    /* check if valid r name */
    if (!_is_r_sym(newname)) {
        error("%s is not a valid R symbol.", iname);
    }

    /* check if sdf already exists */
    if (_sdf_exists2(newname)) { /* name is already taken */
        error("Error: the name \"%s\" is already taken.", newname);
    }

    /* get path of the sdf file, because we're going to detach it */
    path = _get_sdf_detail2(iname, SDF_DETAIL_FULLFILENAME);
    if (path == NULL) {
        error("no sdf named \"%s\" exists.", iname);
    }

    /* change name in sdf_attribute */
    sprintf(g_sql_buf[0], "update [%s].sdf_attributes set value='%s' "
            "where attr='name'", iname, newname);
    res = _sqlite_exec(g_sql_buf[0]);
    /* if (_sqlite_error(res)) return R_NilValue; */

    /* detach and remove sdf from workspace */
    sprintf(g_sql_buf[0], "detach '%s'", iname);
    res = _sqlite_exec(g_sql_buf[0]);
    ret_tmp = !_sqlite_error(res);

    /* remove from ws, attach and add again to ws using new name */
    if (ret_tmp) {
        _delete_sdf2(iname);
        sprintf(g_sql_buf[0], "attach '%s' as '%s'", path, newname);
        res = _sqlite_exec(g_sql_buf[0]);
        ret_tmp = !_sqlite_error(res);
        
        /* TODO: make path relative! */
        _add_sdf1(iname, path);
    }

    return ScalarLogical(ret_tmp);
}