Bigfoot71 / raymob

Simple raylib implementation for Android
Other
85 stars 19 forks source link

SQlite3 embedded with Raylib projects #4

Closed AkrilaMayNotBeAvailable closed 1 year ago

AkrilaMayNotBeAvailable commented 1 year ago

I use SQlite for (mostly) string data storing, that I use to load other stuff with Raylib. It works with Linux and Windows build normally but when I tried compiling here I found the following problem:

Do you have any idea about why it isn't finding the database file? Is SQlite maybe not supported on android build?

Other than that, everything seems to work normally! Thanks for developing this tool.

Bigfoot71 commented 1 year ago

I'm not very familiar with SQLite, but if it's a file reading issue, I can certainly help you.

The first thing that comes to my mind is that your library might not be using Android's AssetManager. Is it possible for you to send me a reproducible example that I can try on my side?

If my intuition is good, I should be able to offer you a solution fairly quickly.

AkrilaMayNotBeAvailable commented 1 year ago

Hello there! Sorry for taking so long. You can check those files, I made them as a example, but my usual framework is pretty much exactly like them.

https://github.com/AkrilaMayNotBeAvailable/Example_Files/tree/main/SQlite%20embedded%20with%20Raylib

On Raymob project my setup is:

Raymob_root/app/src/main/
  > assets/Database/file.db
  > cpp/*.c and *.h files
Bigfoot71 commented 1 year ago

Thank you for the example. After some research, it seems that it might be possible to load an SQLite database directly from memory. So, I tried to load the file using the LoadFileData() function from raylib, which uses its AssetManager instance. The file is loaded successfully, but then I receive this error:

com.raylib.raymob     I  FILEIO: [Database/example.db] File loaded successfully
com.raylib.raymob     E  Error executing SQL script: near "SQLite": syntax error

Here's my code, if you are more familiar with sqlite3 than I am, you might see what's wrong:

int ExecuteQuery(const char *fp, const char *query, char *str){
    sqlite3 *db;
    sqlite3_stmt *stmt;
    int rc;

    // Load the database content from the asset using LoadFileData
    unsigned int byteReaded = 0;
    unsigned char *dbContent = LoadFileData(fp, &byteReaded);
    if (!dbContent) {
        TraceLog(LOG_ERROR, "Error loading asset: %s", fp);
        return -1;
    }

    // SQLite function to open an in-memory database
    rc = sqlite3_open(":memory:", &db);
    if (rc != SQLITE_OK) {
        TraceLog(LOG_ERROR, "Error opening in-memory database: %s", sqlite3_errmsg(db));
        UnloadFileData(dbContent); // Free allocated memory
        return rc; // SQLite Error code
    }

    // Execute the SQL script from the asset (?)
    rc = sqlite3_exec(db, (const char *)dbContent, 0, 0, 0);
    UnloadFileData(dbContent); // Free allocated memory

    if (rc != SQLITE_OK) {
        TraceLog(LOG_ERROR, "Error executing SQL script: %s", sqlite3_errmsg(db));
        sqlite3_close(db); // Close in-memory database if error
        return rc; // SQLite Error code
    }

    // Prepare the query
    rc = sqlite3_prepare_v2(db, query, -1, &stmt, 0);
    if (rc != SQLITE_OK) {
        TraceLog(LOG_ERROR, "Error preparing query: %s", sqlite3_errmsg(db));
        sqlite3_close(db); // Close in-memory database if error
        return rc; // SQLite Error code
    }

    while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
        const unsigned char *phrase = sqlite3_column_text(stmt, 0); // Gets string from column 0
        // While phrase != 0
        if (phrase) {
            snprintf(str, STRING_SIZE, "%s", phrase);
        }
    }

    // Something went wrong
    if (rc != SQLITE_DONE) {
        TraceLog(LOG_ERROR, "Error while executing query: %s", sqlite3_errmsg(db));
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);

    return rc; // SQLite error code
}

As additional information, the attempt to open the file using fopen() in your version of FileChecking() fails for an unknown reason, as it should normally correspond to the fopen() overload in raylib for Android.

So, I then wanted to replace your FileChecking() function with raylib's FileExists() function, but I discovered that it directly uses access without any "overload" to check in the assets. So, it will likely be an addition to make in raylib.

By the way, it's normal that you didn't get any error logs because printf and fprintf won't work. You should use the Android log library, and the TraceLog function in raylib will do the job for you.

AkrilaMayNotBeAvailable commented 1 year ago

Hello again, thanks for helping me out with this, so about your code, I did some research as I don't know that much about SQLite in-depth functions, but I can point this:


This is creating a empty in-memory database, not loading the one by LoadFileData()

rc = sqlite3_open(":memory:", &db);


This is actually a wrapper function for the whole code ( sqlite3_prepare_v2(), sqlite3_step(), sqlite3_finalize() ) under it. It should receive a query data as 2nd parameter, so it probably fails here as what is being passed as parameter isn't a query.

rc = sqlite3_exec(db, (const char *)dbContent, 0, 0, 0);

You can check the exact same process with the query by commenting this function, it will return:

Error preparing query: no such table: example


But anyway those errors come from it not actually being loaded by SQLite.

So it is possible to load it through Raylib LoadFileData function, but then it should be accessible by sqlite3_open() to load data somehow, but this function takes a file path (with a file) to do a bunch of stuff...

I made some unsuccessful tests with sqlite3_open_v2() in which there is some additional parameters for VFS and flags for stuff but I just couldn't make it work. Do you have any idea? I'm really not used to android file system, sorry about that.

Also, thanks for the TraceLog tip.

Bigfoot71 commented 1 year ago

Unfortunately, I don't have many ideas as this seems more related to SQLite, and I'm not very familiar with it. I have two other potential solutions that should work but may not be the best.

The first solution

It involves loading the file from assets and then writing it to GetAndroidApp()->activity->internalDataPath, which is a secure directory reserved for your application. The path to this directory will then be accessible without the AssetManager.

I've tried it, and it works (code written hastily but functional):

// Copy database from assets to internal data
void CopyFileToInternalData(const char *src, const char *dst) {
    unsigned int bytesRead = 0;
    unsigned char *data = LoadFileData(src, &bytesRead);

    if (data != NULL) {

        // Open the destination file in binary write mode
        FILE *fileDst = fopen(dst, "wb");

        if (fileDst != NULL) {

            // Write data to the destination file
            size_t bytesWritten = fwrite(data, 1, bytesRead, fileDst);

            if (bytesWritten == bytesRead) {
                TraceLog(LOG_INFO, "Successful copy to %s\n", dst);
            } else {
                TraceLog(LOG_ERROR, "Copy failed to %s\n", dst);
            }

            // Close the destination file
            fclose(fileDst);

        } else {
            TraceLog(LOG_ERROR, "Unable to open destination file %s\n", dst);
        }

        // Free the memory allocated for data
        free(data);

    } else {
        TraceLog(LOG_ERROR, "Error reading source file %s\n", src);
    }
}

// Copy and load from internal data path
ERRORCODE_e DataIntegrity(char *string){
    const char *database = "Database/example.db";

    const char *destination = TextFormat(
            "%s/%s", GetAndroidApp()->activity->internalDataPath, "example.db");

    CopyFileToInternalData(database, destination);

    const char *query = "SELECT str FROM example";
    if(ExecuteQuery(destination, query, string)){
        return DATABASE_NOT_CONNECTED;
    }

    return ERROR_NONE;
}

Result: Screenshot_20230923_133652

com.raylib.raymob     I  FILEIO: [Database/example.db] File loaded successfully
com.raylib.raymob     I  Successful copy to /data/user/0/com.raylib.raymob/files/example.db

## Then the file is loaded by sqlite successfully ##

The second solution

It would be to use SQLiteAssetHelper, which is a Java library. In this case, you would write the methods you need on the Java side in the NativeLoader class and call these Java methods from C/C++ using raymob's functions:

RLAPI JNIEnv *AttachCurrentThread(void);
RLAPI void DetachCurrentThread(void);
RLAPI const jobject GetNativeLoaderInstance(void);

You can see the already existing gists for raymob which shows how to call Java methods from C/C++: https://gist.github.com/Bigfoot71/2c89864354a9254ce555f2eca1961590

Here is a link that might help you write the methods on the Java side: https://stackoverflow.com/a/49768006

You can integrate it via Gradle easily if you use Android Studio or see their repo: https://github.com/jgilfelt/android-sqlite-asset-helper

Perhaps there's a similar alternative to SQLiteAssetHelper in C/C++, but I'm not aware of it.

AkrilaMayNotBeAvailable commented 1 year ago

Thank you for the solutions! The first one worked nicely for me, even if it isn't the best solution, it's okay. For the second one it may take a while for me to try it though... but anyway, that actually solves my problem. Feel free to close this issue :)