Category talk:Wren-sql

From Rosetta Code

Relational database management system (RDBMS)

Wren has no built in RDBMS support and, as far as I know, there is no third party support available either.

I have therefore written a Wren wrapper for the popular C library, SQLite, to fill this gap. As a small embedded SQL database engine, SQLite seems particularly well suited to the needs of the Wren programmer and should be much simpler to use than a wrapper for one of the heavyweight RDBMS's would have been.

Even so, SQLite is a full-featured SQL implementation and has more that 225 API function many of which are specialized and/or rarely used. To keep the wrapper to a reasonable size, I have therefore just wrapped the more commonly used functions (around 67 in total). In particular, as the Wren VM is effectively single threaded, I have excluded those APIs which are concerned with multi-threading and have also had to exclude some APIs, which might have been useful, but would have been difficult or impractical to wrap including virtual tables, application-defined SQL functions and functions requiring a C callback.

Hopefully, what remains will be sufficient to cover at least 95% of likely needs.

On the other hand, I decided to support the sqlite3_get_table function which, although deprecated nowadays, is still useful for getting a quick idea of a query's results.

Memory is managed automatically by Wren which calls an object's finalizer just before it is about to be garbage collected. The finalizer in turn calls the appropriate SQLite 'close' or 'finalize function to ensure that memory allocated to the object is fully released. So there is no need for the user to call this function directly and, to avoid 'double close' scenarios, it is not exposed by Wren at all.

Source code (Wren)

/* Module "sql.wren" */

/*
   The Sql class contains various SQLite constants
   plus some static methods which don't belong to the other classes.
*/
class Sql {
    // Datatype constants.
    static Integer        {  1 }
    static Float          {  2 }
    static Text           {  3 }
    static Blob           {  4 }
    static Null           {  5 }

    // File open flags.
    static openReadOnly   {  1 }
    static openReadWrite  {  2 }
    static openCreate     {  4 }
    static openUri        { 40 }
    static openMemory     { 80 }

    // Result codes.
    static ok             {  0 }
    static error          {  1 }
    static internal       {  2 }
    static perm           {  3 }
    static abort          {  4 }
    static busy           {  5 }
    static locked         {  6 }
    static noMem          {  7 }
    static readonly       {  8 }
    static interrupt      {  9 }
    static ioerr          { 10 }
    static corrupt        { 11 }
    static notfound       { 12 }
    static fill           { 13 }
    static cantopen       { 14 }
    static protocol       { 15 }
    static empty          { 16 }
    static schema         { 17 }
    static toobig         { 18 }
    static constraint     { 19 }
    static mismatch       { 20 }
    static misuse         { 21 }
    static nolfs          { 22 }
    static auth           { 23 }
    static format         { 24 }
    static range          { 25 }
    static notadb         { 26 }
    static notice         { 27 }
    static warning        { 28 }
    static row            { 100 }
    static done           { 101 }

    // Miscellaneous methods.
    foreign static errStr(code)
    foreign static keywordCheck(word)
    foreign static randomness(n)
    foreign static sleep(ms)
    foreign static version
}

/* Instances of the Connection class are double pointers to the sqlite3 struct in C. */
foreign class Connection {
    construct open(fileName) {
        if (errCode != Sql.ok) Fiber.abort(errMsg)
    }

    construct open(fileName, flags) {
        if (errCode != Sql.ok) Fiber.abort(errMsg)
    }

    prepare(sql) { Statement.prepare(this, sql) }

    foreign exec(sql)
    foreign table(sql) // first two list elements are numbers of rows and columns followed by data.

    // Prints the results of calling the 'table' method with each column being left justified (or truncated)
    // to the corresponding width in the 'widths' array. If the latter is incomplete, it is increased to
    // the correct size by adding default width(s) of 10. Any excess widths are ignored.
    printTable(sql, widths) {
        var fit = Fn.new { |s, l|
            var c = s.count
            if (c < l) return s + " " * (l - c)
            if (c > l) return s[0...l]
            return s
        }
        var res = table(sql)
        var nRows = res[0]
        var nCols = res[1]
        if (widths.count < nCols) widths = widths + [10] * (nCols - widths.count)
        var colHeadings = res[2...2+nCols]
        var header = (0...nCols).map { |i| fit.call(colHeadings[i], widths[i]) }.join(" ")
        System.print(header)
        System.print("-" * header.count)
        res = res[2+nCols..-1]
        for (r in 0...nRows) {
            var fields = res[r*nRows...r*nRows + nCols]
            var row = (0...nCols).map { |i| fit.call(fields[i], widths[i]) }.join(" ")
            System.print(row)
        }
    }

    // Convenience version of the above method which always uses default widths of 10.
    printTable(sql) { printTable(sql, []) }

    foreign tableColumnMetadata(dbName, tableName, columnName)

    foreign lastInsertRowId
    foreign lastInsertRowId=(rowId)

    blobOpen(db, table, column, row, flags) {
        return Blob.open(this, db, table, column, row, flags)
    }

    foreign errCode
    foreign errMsg
    foreign systemErrNo

    foreign changes
    foreign totalChanges
    foreign autoCommit
    foreign dbFileName(dbName)
    foreign dbReadOnly(dbName)

    foreign interrupt()
}

/* Instances of the Statement class are double pointers to the sqlite3_stmt struct in C. */
foreign class Statement {
    construct prepare(conn, sql) {
        if (conn.errCode != Sql.ok) Fiber.abort(conn.errMsg)
    }

    foreign bindBlob(index, value)
    foreign bindDouble(index, value)
    foreign bindInt(index, value)
    foreign bindInt64(index, value)
    foreign bindNull(index)
    foreign bindText(index, value)
    foreign bindZeroBlob(index, size)
    foreign bindParameterCount
    foreign bindParameterIndex(name)
    foreign bindParameterName(index)
    foreign clearBindings()

    bindBool(index, value) { bindInt(index, value ? 1 : 0) }

    foreign columnBlob(index)
    foreign columnDouble(index)
    foreign columnInt(index)
    foreign columnInt64(index)
    foreign columnText(index)
    foreign columnCount
    foreign columnBytes(index)
    foreign columnType(index)
    foreign columnDeclType(index)
    foreign columnName(index)
    foreign columnDatabaseName(index)
    foreign columnTableName(index)
    foreign columnOriginName(index)
    foreign dataCount

    columnBool(index) { columnInt(index) != 0 }

    foreign sql
    foreign expandedSql

    foreign reset()
    foreign step()
    foreign busy
    foreign isExplain
    foreign readOnly
}

/* Instances of the Blob class are double pointers to the sqlite3_blob struct in C. */
foreign class Blob {
    construct open(conn, db, table, column, row, flags) {
        if (conn.errCode != Sql.ok) Fiber.abort(conn.errMsg)
    }

    foreign bytes
    foreign read(n, offset)
    foreign write(data, n, offset)
    foreign reopen(rowId)
}

/* Instances of the Backup class are double pointers to the sqlite3_backup struct in C. */
foreign class Backup {
    construct init(dest, destName, source, sourceName) {
        if (conn.errCode != Sql.ok) Fiber.abort(conn.errMsg)
    }

    foreign step(nPage)
    step { step(-1) }

    foreign remaining
    foreign pageCount
}

Source code (C)

/* gcc -O3 -DSQLITE_ENABLE_COLUMN_METADATA -DSQLITE_THREADSAFE=0 wren-sql.c -o wren-sql -lsqlite3 -lwren -lm */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include "wren.h"

/* Sql functions */

void Sql_errStr(WrenVM* vm) {
    int code = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_errstr(code);
    wrenSetSlotString(vm, 0, res);
}

void Sql_keywordCheck(WrenVM* vm) {
    const char *word = wrenGetSlotString(vm, 1);
    int res = sqlite3_keyword_check(word, strlen(word));
    wrenSetSlotBool(vm, 0, (bool)res);
}

void Sql_randomness(WrenVM* vm) {
    int n = (int)wrenGetSlotDouble(vm, 1);
    void *buffer = calloc(n+1, sizeof(char));
    sqlite3_randomness(n, buffer);
    wrenSetSlotString(vm, 0, (const char*)buffer);
    free(buffer);
}

void Sql_sleep(WrenVM* vm) {
    int n = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_sleep(n);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Sql_version(WrenVM* vm) {
    const char *res = sqlite3_libversion();
    wrenSetSlotString(vm, 0, res);
}

/* Connection functions */

void Conn_allocate(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(sqlite3*));
    const char *filename = wrenGetSlotString(vm, 1);
    int slots = wrenGetSlotCount(vm);
    if (slots == 2) {
        sqlite3_open(filename, ppDb);
    } else {
        int flags = (int)wrenGetSlotDouble(vm, 2);
        sqlite3_open_v2(filename, ppDb, flags, NULL);
    }
}

void Conn_finalize(void* data) {
    int res = sqlite3_close_v2(*(sqlite3**)data);
    if (res != SQLITE_OK) fprintf(stderr, "Error code %d when closing the database connection.\n", res);
}

void Conn_exec(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *sql = wrenGetSlotString(vm, 1);
    char *errMsg = 0;
    int res = sqlite3_exec(*ppDb, sql, NULL, NULL, &errMsg);
    if (res != SQLITE_OK) {
        fprintf(stderr, "SQLite error: %s\n", errMsg);
        sqlite3_free(errMsg);
    }
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Conn_table(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *zSql = wrenGetSlotString(vm, 1);
    char **azResult = 0;
    char *errMsg = 0;
    int i, nRow = 0, nCol = 0;
    int res = sqlite3_get_table(*ppDb, zSql, &azResult, &nRow, &nCol, &errMsg);
    wrenSetSlotNewList(vm, 0);
    if (res == SQLITE_OK) {
        wrenEnsureSlots(vm, 2);
        wrenSetSlotDouble(vm, 1, nRow);
        wrenInsertInList(vm, 0, 0, 1);
        wrenSetSlotDouble(vm, 1, nCol);
        wrenInsertInList(vm, 0, 1, 1);
        for (i = 0; i < (nRow + 1) * nCol; ++i) {
            wrenSetSlotString(vm, 1, azResult[i]);
            wrenInsertInList(vm, 0, i+2, 1);
        }
    } else {
        fprintf(stderr, "SQLite error: %s\n", errMsg);
        sqlite3_free(errMsg);
    }
    sqlite3_free_table(azResult);
}

void Conn_tableColumnMetadata(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *zDbName = wrenGetSlotString(vm, 1);
    if (strcmp(zDbName, "") == 0) zDbName = NULL;
    const char *zTableName = wrenGetSlotString(vm, 2);
    const char *zColumnName = wrenGetSlotString(vm, 3);
    if (strcmp(zColumnName, "") == 0) zColumnName = NULL;
    char const *zDataType = 0;
    char const *zCollSeq = 0;
    int i, notNull = 0, primaryKey = 0, autoinc = 0;
    int res = sqlite3_table_column_metadata(*ppDb, zDbName, zTableName, zColumnName, &zDataType, &zCollSeq, &notNull, &primaryKey, &autoinc);
    wrenSetSlotNewList(vm, 0);
    if (res == SQLITE_OK) {
        wrenEnsureSlots(vm, 6);
        wrenSetSlotString(vm, 1, zDataType);
        wrenSetSlotString(vm, 2, zCollSeq);
        wrenSetSlotBool(vm, 3, (bool)notNull);
        wrenSetSlotBool(vm, 4, (bool)primaryKey);
        wrenSetSlotBool(vm, 5, (bool)autoinc);
        for (i = 0; i < 5; ++i) wrenInsertInList(vm, 0, i, i+1);
    } else {
        fprintf(stderr, "Error code %d when calling sqlite3_table_column_metadata.\n", res);
    }
}

void Conn_lastInsertRowId(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    sqlite3_int64 rowid = sqlite3_last_insert_rowid(*ppDb);
    wrenSetSlotDouble(vm, 0, (double)rowid);
}

void Conn_setLastInsertRowId(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    sqlite3_int64 rowid = (sqlite3_int64)wrenGetSlotDouble(vm, 1);
    sqlite3_set_last_insert_rowid(*ppDb, rowid);
}

void Conn_errCode(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_errcode(*ppDb);
    wrenSetSlotDouble(vm, 0, (double)res);
} 

void Conn_errMsg(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *res = sqlite3_errmsg(*ppDb);
    wrenSetSlotString(vm, 0, res);
}

void Conn_systemErrNo(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_system_errno(*ppDb);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Conn_changes(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_changes(*ppDb);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Conn_totalChanges(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_total_changes(*ppDb);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Conn_autoCommit(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_get_autocommit(*ppDb);
    wrenSetSlotBool(vm, 0, (bool)res);
}

void Conn_dbFileName(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *zDbName = wrenGetSlotString(vm, 1);
    const char *filename = sqlite3_db_filename(*ppDb, zDbName);
    if (filename == NULL) filename = "";
    wrenSetSlotString(vm, 0, filename);
}

void Conn_dbReadOnly(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    const char *zDbName = wrenGetSlotString(vm, 1);
    int res = sqlite3_db_readonly(*ppDb, zDbName);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Conn_interrupt(WrenVM* vm) {
    sqlite3 **ppDb = (sqlite3**)wrenGetSlotForeign(vm, 0);
    sqlite3_interrupt(*ppDb);
}

/* Statement functions */

void Stmt_allocate(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(sqlite3_stmt*));
    sqlite3 **pDb = (sqlite3**)wrenGetSlotForeign(vm, 1);
    const char *zSql = wrenGetSlotString(vm, 2);
    int res = sqlite3_prepare_v2(*pDb, zSql, -1, ppStmt, NULL);
}

void Stmt_finalize(void* data) {
    int res = sqlite3_finalize(*(sqlite3_stmt**)data);
    if (res != SQLITE_OK) fprintf(stderr, "Error code %d when finalizing a statment.\n", res);
}

void Stmt_bindBlob(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *value = wrenGetSlotString(vm, 2);
    size_t size = strlen(value);
    int res = sqlite3_bind_blob(*ppStmt, index, (const void*)value, size, SQLITE_STATIC);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindDouble(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    double value = wrenGetSlotDouble(vm, 2);
    int res = sqlite3_bind_double(*ppStmt, index, value);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindInt(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int value = (int)wrenGetSlotDouble(vm, 2);
    int res = sqlite3_bind_int(*ppStmt, index, value);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindInt64(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    sqlite3_int64 value = (sqlite3_int64)wrenGetSlotDouble(vm, 2);
    int res = sqlite3_bind_int64(*ppStmt, index, value);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindNull(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_bind_null(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindText(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *value = wrenGetSlotString(vm, 2);
    int res = sqlite3_bind_text(*ppStmt, index, value, -1, SQLITE_STATIC);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindZeroBlob(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int size = (int)wrenGetSlotDouble(vm, 2);
    int res = sqlite3_bind_zeroblob(*ppStmt, index, size);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindParameterCount(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_bind_parameter_count(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);  
}

void Stmt_bindParameterIndex(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    const char *name = wrenGetSlotString(vm, 1);
    int res = sqlite3_bind_parameter_index(*ppStmt, name);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_bindParameterName(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_bind_parameter_name(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_clearBindings(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_clear_bindings(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnBlob(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const void *res = sqlite3_column_blob(*ppStmt, index);
    wrenEnsureSlots(vm, 2);
    wrenGetVariable(vm, "./sql", "Blob", 1);
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenSetSlotNewForeign(vm, 0, 1, sizeof(sqlite3_blob*));
    *ppBlob = (sqlite3_blob*)res;
}

void Stmt_columnDouble(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    double res = sqlite3_column_double(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, res);
}

void Stmt_columnInt(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_column_int(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnInt64(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    sqlite3_int64 res = sqlite3_column_int64(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnText(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const unsigned char *res = sqlite3_column_text(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, (const char*)res);
}

void Stmt_columnCount(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_column_count(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnBytes(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_column_bytes(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnType(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_column_type(*ppStmt, index);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_columnDeclType(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_column_decltype(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_columnName(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_column_name(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_columnDatabaseName(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_column_database_name(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_columnTableName(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_column_table_name(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_columnOriginName(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int index = (int)wrenGetSlotDouble(vm, 1);
    const char *res = sqlite3_column_origin_name(*ppStmt, index);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, res);
}

void Stmt_dataCount(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_data_count(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_sql(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    const char *res = sqlite3_sql(*ppStmt);
    wrenSetSlotString(vm, 0, res);
}

void Stmt_expandedSql(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    char *res = sqlite3_expanded_sql(*ppStmt);
    if (res == NULL) res = "";
    wrenSetSlotString(vm, 0, (const char *)res);
    if (strcmp(res, "") != 0) sqlite3_free(res);
}

void Stmt_reset(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_reset(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_step(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_step(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_busy(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_stmt_busy(*ppStmt);
    wrenSetSlotBool(vm, 0, (bool)res);
}

void Stmt_isExplain(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_stmt_isexplain(*ppStmt);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Stmt_readOnly(WrenVM* vm) {
    sqlite3_stmt **ppStmt = (sqlite3_stmt**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_stmt_readonly(*ppStmt);
    wrenSetSlotBool(vm, 0, (bool)res);
}

/* Blob functions */

void Blob_allocate(WrenVM* vm) {
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(sqlite3_blob*));
    sqlite3 **pDb = (sqlite3**)wrenGetSlotForeign(vm, 1);
    const char *zDb = wrenGetSlotString(vm, 2);
    const char *zTable = wrenGetSlotString(vm, 3);
    const char *zColumn = wrenGetSlotString(vm, 4);
    sqlite3_int64 iRow = (sqlite3_int64)wrenGetSlotDouble(vm, 5);
    bool readWrite = wrenGetSlotBool(vm, 6);
    int flags = readWrite ? 1 : 0;
    int res = sqlite3_blob_open(*pDb, zDb, zTable, zColumn, iRow, flags, ppBlob);
}

void Blob_finalize(void* data) {
    int res = sqlite3_blob_close(*(sqlite3_blob**)data);
    if (res != SQLITE_OK) fprintf(stderr, "Error code %d when closing a BLOB handle.\n", res);
}

void Blob_read(WrenVM* vm) {
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenGetSlotForeign(vm, 0);
    int n = (int)wrenGetSlotDouble(vm, 1);
    int iOffset = (int)wrenGetSlotDouble(vm, 2);
    void *buffer = calloc(n+1, sizeof(char));
    int res = sqlite3_blob_read(*ppBlob, buffer, n, iOffset);
    if (res == SQLITE_OK) {
        wrenSetSlotString(vm, 0, (const char*)buffer);
    } else {
        wrenSetSlotString(vm, 0, "");
    }
    free(buffer);
}

void Blob_write(WrenVM* vm) {
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenGetSlotForeign(vm, 0); 
    const void *buffer = (const void *)wrenGetSlotString(vm, 1);
    int n = (int)wrenGetSlotDouble(vm, 2);
    int iOffset = (int)wrenGetSlotDouble(vm, 3);
    int res = sqlite3_blob_write(*ppBlob, buffer, n, iOffset);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Blob_reopen(WrenVM* vm) {
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenGetSlotForeign(vm, 0); 
    sqlite3_int64 rowid = (sqlite3_int64)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_blob_reopen(*ppBlob, rowid);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Blob_bytes(WrenVM* vm) {
    sqlite3_blob **ppBlob = (sqlite3_blob**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_blob_bytes(*ppBlob);
    wrenSetSlotDouble(vm, 0, (double)res);
}

/* Backup functions */

void Backup_allocate(WrenVM* vm) {
    sqlite3_backup **backup = (sqlite3_backup**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(sqlite3_backup*));
    sqlite3 **ppDest = (sqlite3**)wrenGetSlotForeign(vm, 1);
    const char *zDestName = wrenGetSlotString(vm, 2);
    sqlite3 **ppSource = (sqlite3**)wrenGetSlotForeign(vm, 3);
    const char *zSourceName = wrenGetSlotString(vm, 4);
    *backup = sqlite3_backup_init(*ppDest, zDestName, *ppSource, zSourceName);
}

void Backup_finalize(void* data) {
    int res = sqlite3_backup_finish(*(sqlite3_backup**)data);
    if (res != SQLITE_OK) fprintf(stderr, "Error code %d when finishing a back-up.\n", res);
}

void Backup_step(WrenVM* vm) {
    sqlite3_backup **backup = (sqlite3_backup**)wrenGetSlotForeign(vm, 0);
    int nPage = (int)wrenGetSlotDouble(vm, 1);
    int res = sqlite3_backup_step(*backup, nPage);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Backup_remaining(WrenVM* vm) {
    sqlite3_backup **backup = (sqlite3_backup**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_backup_remaining(*backup);
    wrenSetSlotDouble(vm, 0, (double)res);
}

void Backup_pageCount(WrenVM* vm) {
    sqlite3_backup **backup = (sqlite3_backup**)wrenGetSlotForeign(vm, 0);
    int res = sqlite3_backup_pagecount(*backup);
    wrenSetSlotDouble(vm, 0, (double)res);
}

WrenForeignClassMethods bindForeignClass(WrenVM* vm, const char* module, const char* className) {
    WrenForeignClassMethods methods;
    methods.allocate = NULL;
    methods.finalize = NULL;
    if (strcmp(module, "./sql") == 0) {
        if (strcmp(className, "Connection") == 0) {
            methods.allocate = Conn_allocate;
            methods.finalize = Conn_finalize;
        } else if (strcmp(className, "Statement") == 0) {
            methods.allocate = Stmt_allocate;
            methods.finalize = Stmt_finalize;
        } else if (strcmp(className, "Blob") == 0) {
            methods.allocate = Blob_allocate;
            methods.finalize = Blob_finalize;
        } else if (strcmp(className, "Backup") == 0) {
            methods.allocate = Backup_allocate;
            methods.finalize = Backup_finalize;
        }
    }
    return methods;
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "./sql") == 0) {
        if (strcmp(className, "Sql") == 0) {
            if(isStatic && strcmp(signature, "errStr(_)") == 0) return Sql_errStr;
            if(isStatic && strcmp(signature, "keywordCheck(_)") == 0) return Sql_keywordCheck;
            if(isStatic && strcmp(signature, "randomness(_)") == 0) return Sql_randomness;
            if(isStatic && strcmp(signature, "sleep(_)") == 0) return Sql_sleep;
            if(isStatic && strcmp(signature, "version") == 0) return Sql_version;
        } else if (strcmp(className, "Connection") == 0) {
            if(!isStatic && strcmp(signature, "exec(_)") == 0) return Conn_exec;
            if(!isStatic && strcmp(signature, "table(_)") == 0) return Conn_table;
            if(!isStatic && strcmp(signature, "tableColumnMetadata(_,_,_)") == 0) return Conn_tableColumnMetadata;
            if(!isStatic && strcmp(signature, "lastInsertRowId") == 0) return Conn_lastInsertRowId;
            if(!isStatic && strcmp(signature, "lastInsertRowId=(_)") ==  0) return Conn_setLastInsertRowId;
            if(!isStatic && strcmp(signature, "errCode") == 0) return Conn_errCode;
            if(!isStatic && strcmp(signature, "errMsg") == 0) return Conn_errMsg;
            if(!isStatic && strcmp(signature, "systemErrNo") == 0) return Conn_systemErrNo;
            if(!isStatic && strcmp(signature, "changes") == 0) return Conn_changes;
            if(!isStatic && strcmp(signature, "totalChanges") == 0) return Conn_totalChanges;
            if(!isStatic && strcmp(signature, "autoCommit") == 0) return Conn_autoCommit;
            if(!isStatic && strcmp(signature, "dbFileName(_)") == 0) return Conn_dbFileName;
            if(!isStatic && strcmp(signature, "dbReadOnly(_)") == 0) return Conn_dbReadOnly;
            if(!isStatic && strcmp(signature, "interrupt()") == 0) return Conn_interrupt;
        } else if (strcmp(className, "Statement") == 0) {
            if(!isStatic && strcmp(signature, "bindBlob(_,_)") == 0) return Stmt_bindBlob;
            if(!isStatic && strcmp(signature, "bindDouble(_,_)") == 0) return Stmt_bindDouble;
            if(!isStatic && strcmp(signature, "bindInt(_,_)") == 0) return Stmt_bindInt;
            if(!isStatic && strcmp(signature, "bindInt64(_,_)") == 0) return Stmt_bindInt64;
            if(!isStatic && strcmp(signature, "bindNull(_)") == 0) return Stmt_bindNull;
            if(!isStatic && strcmp(signature, "bindText(_,_)") == 0) return Stmt_bindText;
            if(!isStatic && strcmp(signature, "bindZeroBlob(_,_)") == 0) return Stmt_bindZeroBlob;
            if(!isStatic && strcmp(signature, "bindParameterCount") == 0) return Stmt_bindParameterCount;
            if(!isStatic && strcmp(signature, "bindParameterIndex(_)") == 0) return Stmt_bindParameterIndex;
            if(!isStatic && strcmp(signature, "bindParameterName(_)") == 0) return Stmt_bindParameterName;
            if(!isStatic && strcmp(signature, "clearBindings()") == 0) return Stmt_clearBindings;
            if(!isStatic && strcmp(signature, "columnBlob(_)") == 0) return Stmt_columnBlob;
            if(!isStatic && strcmp(signature, "columnDouble(_)") == 0) return Stmt_columnDouble;
            if(!isStatic && strcmp(signature, "columnInt(_)") == 0) return Stmt_columnInt;
            if(!isStatic && strcmp(signature, "columnInt64(_)") == 0) return Stmt_columnInt64;
            if(!isStatic && strcmp(signature, "columnText(_)") == 0) return Stmt_columnText;
            if(!isStatic && strcmp(signature, "columnCount") == 0) return Stmt_columnCount;
            if(!isStatic && strcmp(signature, "columnBytes(_)") == 0) return Stmt_columnBytes;
            if(!isStatic && strcmp(signature, "columnType(_)") == 0) return Stmt_columnType;
            if(!isStatic && strcmp(signature, "columnDeclType(_)") == 0) return Stmt_columnDeclType;
            if(!isStatic && strcmp(signature, "columnName(_)") == 0) return Stmt_columnName;
            if(!isStatic && strcmp(signature, "columnDatabaseName(_)") == 0) return Stmt_columnDatabaseName;
            if(!isStatic && strcmp(signature, "columnTableName(_)") == 0) return Stmt_columnTableName;
            if(!isStatic && strcmp(signature, "columnOriginName(_)") == 0) return Stmt_columnOriginName;
            if(!isStatic && strcmp(signature, "dataCount") == 0) return Stmt_dataCount;
            if(!isStatic && strcmp(signature, "sql") == 0) return Stmt_sql;
            if(!isStatic && strcmp(signature, "expandedSql") == 0) return Stmt_expandedSql;
            if(!isStatic && strcmp(signature, "reset()") == 0) return Stmt_reset;
            if(!isStatic && strcmp(signature, "step()") == 0) return Stmt_step;
            if(!isStatic && strcmp(signature, "busy") == 0) return Stmt_busy;
            if(!isStatic && strcmp(signature, "isExplain") == 0) return Stmt_isExplain;
            if(!isStatic && strcmp(signature, "readOnly") == 0) return Stmt_readOnly;
        } else if (strcmp(className, "Blob") == 0) {
            if(!isStatic && strcmp(signature, "bytes") == 0) return Blob_bytes;
            if(!isStatic && strcmp(signature, "read(_,_)") == 0) return Blob_read;
            if(!isStatic && strcmp(signature, "write(_,_,_)") == 0) return Blob_write;
            if(!isStatic && strcmp(signature, "reopen(_)") == 0) return Blob_reopen;
        } else if (strcmp(className, "Backup") == 0) {
            if(!isStatic && strcmp(signature, "step(_)") == 0) return Backup_step;
            if(!isStatic && strcmp(signature, "remaining") == 0) return Backup_remaining;
            if(!isStatic && strcmp(signature, "pageCount") == 0) return Backup_pageCount;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    size_t ret = fread(script, 1, fsize, f);
    if (ret != fsize) printf("Error reading %s\n", fileName);
    fclose(f);
    script[fsize] = 0;
    return script;
}

static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
    if( result.source) free((void*)result.source);
}

WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
    WrenLoadModuleResult result = {0};
    if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
        result.onComplete = loadModuleComplete;
        char fullName[strlen(name) + 6];
        strcpy(fullName, name);
        strcat(fullName, ".wren");
        result.source = readFile(fullName);
    }
    return result;
}

int main(int argc, char **argv) {
    if (argc != 2) {
        printf("Please pass the name of the Wren file to be executed.\n");
        return 1;
    }
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignClassFn = &bindForeignClass;
    config.bindForeignMethodFn = &bindForeignMethod;
    config.loadModuleFn = &loadModule;
    WrenVM* vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = argv[1];
    char *script = readFile(fileName);
    if (sqlite3_initialize() != SQLITE_OK) {
        printf("Error initializing SQLite!\n");
        return 1;
    }
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    wrenFreeVM(vm);
    free(script);
    sqlite3_shutdown();
    return 0;
}