summaryrefslogtreecommitdiff
path: root/platform/android/src/sqlite3.cpp
blob: e316ea989036f6aeea44dd8f1d77f5f8409d3784 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#include "../default/sqlite3.hpp"

#include "android/database/sqlite/sqlite_database.hpp"
#include "android/database/sqlite/sqlite_exception.hpp"
#include "java/lang/throwable.hpp"

#include "attach_env.hpp"

using namespace mbgl::android;

using android::database::sqlite::SQLiteDatabase;
using android::database::sqlite::SQLiteCantOpenDatabaseException;
using android::database::sqlite::SQLiteDatabaseCorruptException;
using java::lang::Throwable;

namespace mapbox {
namespace sqlite {

template <typename Lambda>
void checkException(jni::JNIEnv& env, Lambda&& lambda) {
    try {
        lambda();
    } catch (const jni::PendingJavaException&) {
        auto throwable = jni::Object<Throwable>(jni::ExceptionOccurred(env));
        jni::ExceptionClear(env);
        const std::string message =
            jni::Make<std::string>(env, Throwable::GetMessage(env, throwable));
        if (throwable.IsInstanceOf(env, SQLiteCantOpenDatabaseException::Class(env))) {
            throw Exception{ Exception::Code::CANTOPEN, message };
        } else if (throwable.IsInstanceOf(env, SQLiteDatabaseCorruptException::Class(env))) {
            throw Exception{ Exception::Code::NOTADB, message };
        } else {
            throw Exception{ 1 /* SQLITE_ERROR */, message };
        }
    }
}

class DatabaseImpl {
public:
    DatabaseImpl(const std::string& filename, OpenFlag openFlags) {
        int flags = 0;
        if (openFlags & OpenFlag::ReadOnly) {
            flags |= SQLiteDatabase::OPEN_READONLY;
        }
        if (openFlags & OpenFlag::ReadWrite) {
            flags |= SQLiteDatabase::OPEN_READWRITE;
        }
        if (openFlags & OpenFlag::Create) {
            flags |= SQLiteDatabase::CREATE_IF_NECESSARY;
        }

        checkException(*env, [&] {
            db = SQLiteDatabase::OpenDatabase(*env, jni::Make<jni::String>(*env, filename), flags).NewGlobalRef(*env);
        });
    }

    UniqueEnv env{ AttachEnv() };
    jni::UniqueObject<android::database::sqlite::SQLiteDatabase> db;
};

Database::Database(const std::string& filename, int flags)
    : impl(std::make_unique<DatabaseImpl>(filename, OpenFlag(flags))) {
}

Database::Database(Database&& other) : impl(std::move(other.impl)) {
}

Database& Database::operator=(Database&& other) {
    std::swap(impl, other.impl);
    return *this;
}

Database::~Database() {
}

Database::operator bool() const {
    return impl.operator bool();
}

void Database::setBusyTimeout(std::chrono::milliseconds timeout) {
    // Java bindings don't support setting a direct busy_timeout.
    exec(std::string{ "PRAGMA busy_timeout = " } +
         std::to_string(std::min<std::chrono::milliseconds::rep>(timeout.count(),
                                                                 std::numeric_limits<int>::max())));
}

void Database::exec(const std::string &sql) {
    assert(impl);
    checkException(*impl->env, [&] {
        SQLiteDatabase::ExecSQL(*impl->env, *impl->db, jni::Make<jni::String>(*impl->env, sql));
    });
}

Statement Database::prepare(const char *query) {
    assert(impl);
    return Statement(this, query);
}

Statement::Statement(Database *db, const char *sql)
    : impl(std::make_unique<StatementImpl>(db->impl->db, sql))
{
}

Statement::Statement(Statement &&other) {
    *this = std::move(other);
}

Statement &Statement::operator=(Statement &&other) {
    std::swap(impl, other.impl);
    return *this;
}

Statement::~Statement() {
}

Statement::operator bool() const {
    return impl.operator bool();
}

} // namespace sqlite
} // namespace mapbox