sqlite_web_vfs 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE-3RD-PARTY.md +32 -0
- data/README.md +118 -0
- data/examples/sqlite3_example.rb +21 -0
- data/examples/sqlite3_ffi_example.rb +25 -0
- data/ext/sqlite_web_vfs/Makefile +267 -0
- data/ext/sqlite_web_vfs/_wrap_web_vfs.o +0 -0
- data/ext/sqlite_web_vfs/extconf.rb +177 -0
- data/ext/sqlite_web_vfs/readerwriterqueue.h +5 -0
- data/ext/sqlite_web_vfs/shim.c +16 -0
- data/ext/sqlite_web_vfs/shim.o +0 -0
- data/ext/sqlite_web_vfs/upstream/HTTP.h +435 -0
- data/ext/sqlite_web_vfs/upstream/SQLiteVFS.h +530 -0
- data/ext/sqlite_web_vfs/upstream/ThreadPool.h +209 -0
- data/ext/sqlite_web_vfs/upstream/atomicops.h +772 -0
- data/ext/sqlite_web_vfs/upstream/dbi.h +203 -0
- data/ext/sqlite_web_vfs/upstream/readerwriterqueue.h +979 -0
- data/ext/sqlite_web_vfs/upstream/web_vfs.cc +21 -0
- data/ext/sqlite_web_vfs/upstream/web_vfs.h +823 -0
- data/lib/sqlite_web_vfs/loader.rb +69 -0
- data/lib/sqlite_web_vfs.rb +12 -0
- data/spec/integration/sq_lite_web_vfs_integration_spec.rb +45 -0
- data/spec/integration/sqlite_web_vfs_integration_spec.rb +45 -0
- data/spec/spec_helper.rb +7 -0
- metadata +67 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
class dbiHelper {
|
|
4
|
+
std::shared_ptr<sqlite3> dbiconn_ = nullptr;
|
|
5
|
+
std::shared_ptr<sqlite3_stmt> cursor_;
|
|
6
|
+
std::string temp_dbifile_, last_error_;
|
|
7
|
+
|
|
8
|
+
dbiHelper(std::shared_ptr<sqlite3> dbiconn) : dbiconn_(dbiconn) {}
|
|
9
|
+
|
|
10
|
+
int Prepare(bool web, std::string &error) noexcept {
|
|
11
|
+
int rc;
|
|
12
|
+
if (web) {
|
|
13
|
+
// download remote .dbi to local temporary file and reopen that
|
|
14
|
+
temp_dbifile_ = unixTempFileDir();
|
|
15
|
+
if (!temp_dbifile_.empty() && temp_dbifile_[temp_dbifile_.size() - 1] != '/') {
|
|
16
|
+
temp_dbifile_ += '/';
|
|
17
|
+
}
|
|
18
|
+
temp_dbifile_ += "sqlite_web_vfs_dbi.XXXXXX";
|
|
19
|
+
int rc = mkstemp((char *)temp_dbifile_.data());
|
|
20
|
+
if (rc < 0) {
|
|
21
|
+
error = "failed generating temporary filename for .dbi download: ";
|
|
22
|
+
error += sqlite3_errmsg(dbiconn_.get());
|
|
23
|
+
return SQLITE_ERROR;
|
|
24
|
+
}
|
|
25
|
+
close(rc);
|
|
26
|
+
|
|
27
|
+
// download from web_vfs by VACUUM INTO, which buys us parallel, chunked downloads
|
|
28
|
+
// with retry logic!
|
|
29
|
+
auto vacuum_sql = "vacuum into '" + temp_dbifile_ + "'";
|
|
30
|
+
rc = sqlite3_exec(dbiconn_.get(), vacuum_sql.c_str(), nullptr, nullptr, nullptr);
|
|
31
|
+
if (rc != SQLITE_OK) {
|
|
32
|
+
error = "failed downloading .dbi to temporary file: ";
|
|
33
|
+
error += sqlite3_errmsg(dbiconn_.get());
|
|
34
|
+
return rc;
|
|
35
|
+
}
|
|
36
|
+
dbiconn_.reset();
|
|
37
|
+
|
|
38
|
+
std::string temp_uri = "file:" + temp_dbifile_ + "?mode=ro&immutable=1";
|
|
39
|
+
sqlite3 *raw_dbiconn = nullptr;
|
|
40
|
+
rc = sqlite3_open_v2(temp_uri.c_str(), &raw_dbiconn,
|
|
41
|
+
SQLITE_OPEN_URI | SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
|
|
42
|
+
nullptr);
|
|
43
|
+
if (rc != SQLITE_OK) {
|
|
44
|
+
error = "failed opening .dbi after downloading: ";
|
|
45
|
+
error += sqlite3_errstr(rc);
|
|
46
|
+
return rc;
|
|
47
|
+
}
|
|
48
|
+
dbiconn_ = std::shared_ptr<sqlite3>(raw_dbiconn, sqlite3_close_v2);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// tune connection and prepare query
|
|
52
|
+
rc = sqlite3_exec(dbiconn_.get(), "pragma cache_size = -16384", nullptr, nullptr, nullptr);
|
|
53
|
+
if (rc != SQLITE_OK) {
|
|
54
|
+
error = sqlite3_errmsg(dbiconn_.get());
|
|
55
|
+
return rc;
|
|
56
|
+
}
|
|
57
|
+
// TODO: check pragma application_id = 0x646269;
|
|
58
|
+
sqlite3_stmt *raw_get_page = nullptr;
|
|
59
|
+
rc = sqlite3_prepare_v3(dbiconn_.get(), "select data from web_dbi_pages where offset = ?",
|
|
60
|
+
-1, 0, &raw_get_page, nullptr);
|
|
61
|
+
if (rc != SQLITE_OK) {
|
|
62
|
+
error = "invalid .dbi (wrong internal schema)";
|
|
63
|
+
return rc;
|
|
64
|
+
}
|
|
65
|
+
cursor_ = std::shared_ptr<sqlite3_stmt>(raw_get_page, sqlite3_finalize);
|
|
66
|
+
return Seek(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
** Originally from sqlite os_unix.c:
|
|
71
|
+
**
|
|
72
|
+
** Return the name of a directory in which to put temporary files.
|
|
73
|
+
** If no suitable temporary file directory can be found, return NULL.
|
|
74
|
+
*/
|
|
75
|
+
const char *unixTempFileDir(void) {
|
|
76
|
+
static const char *azDirs[] = {0, 0, "/tmp", "."};
|
|
77
|
+
unsigned int i = 0;
|
|
78
|
+
struct stat buf;
|
|
79
|
+
const char *zDir = 0;
|
|
80
|
+
|
|
81
|
+
if (!azDirs[0])
|
|
82
|
+
azDirs[0] = getenv("SQLITE_TMPDIR");
|
|
83
|
+
if (!azDirs[1])
|
|
84
|
+
azDirs[1] = getenv("TMPDIR");
|
|
85
|
+
while (1) {
|
|
86
|
+
if (zDir != 0 && stat(zDir, &buf) == 0 && S_ISDIR(buf.st_mode) &&
|
|
87
|
+
access(zDir, 03) == 0) {
|
|
88
|
+
return zDir;
|
|
89
|
+
}
|
|
90
|
+
if (i >= sizeof(azDirs) / sizeof(azDirs[0]))
|
|
91
|
+
break;
|
|
92
|
+
zDir = azDirs[i++];
|
|
93
|
+
}
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public:
|
|
98
|
+
virtual ~dbiHelper() {
|
|
99
|
+
dbiconn_.reset();
|
|
100
|
+
if (!temp_dbifile_.empty()) {
|
|
101
|
+
unlink(temp_dbifile_.c_str());
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static int Open(HTTP::CURLconn *curlconn, const std::string &dbi_url, bool web_insecure,
|
|
106
|
+
int log_level, std::unique_ptr<dbiHelper> &dbi, std::string &error) noexcept {
|
|
107
|
+
error.clear();
|
|
108
|
+
|
|
109
|
+
bool web = true;
|
|
110
|
+
std::string open_uri = "?mode=ro&immutable=1";
|
|
111
|
+
if (dbi_url.substr(0, 5) == "file:") {
|
|
112
|
+
open_uri = dbi_url + open_uri;
|
|
113
|
+
web = false;
|
|
114
|
+
} else {
|
|
115
|
+
// Open .dbi through web_vfs. This is quite tricky and cute: the process of opening one
|
|
116
|
+
// web_vfs db file triggers opening another. (&web_nodbi=1 prevents infinite recursion)
|
|
117
|
+
std::string encoded_url;
|
|
118
|
+
if (!curlconn->escape(dbi_url, encoded_url) || encoded_url.size() < dbi_url.size()) {
|
|
119
|
+
error = "Failed percent-encoding dbi_url";
|
|
120
|
+
return SQLITE_CANTOPEN;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
open_uri = "file:/__web__" + open_uri + "&vfs=web&web_nodbi=1&web_url=" + encoded_url;
|
|
124
|
+
|
|
125
|
+
if (web_insecure) {
|
|
126
|
+
open_uri += "&web_insecure=1";
|
|
127
|
+
}
|
|
128
|
+
open_uri += "&web_small_KiB=1048576&vfs_log=" + std::to_string(log_level);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
sqlite3 *raw_dbiconn = nullptr;
|
|
132
|
+
int rc =
|
|
133
|
+
sqlite3_open_v2(open_uri.c_str(), &raw_dbiconn,
|
|
134
|
+
SQLITE_OPEN_URI | SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, nullptr);
|
|
135
|
+
if (rc != SQLITE_OK) {
|
|
136
|
+
error = sqlite3_errstr(rc);
|
|
137
|
+
return rc;
|
|
138
|
+
}
|
|
139
|
+
dbi.reset(new dbiHelper(std::shared_ptr<sqlite3>(raw_dbiconn, sqlite3_close_v2)));
|
|
140
|
+
rc = dbi->Prepare(web, error);
|
|
141
|
+
if (rc != SQLITE_OK) {
|
|
142
|
+
dbi.reset();
|
|
143
|
+
return rc;
|
|
144
|
+
}
|
|
145
|
+
return SQLITE_OK;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
std::string GetLastError() const noexcept { return last_error_; }
|
|
149
|
+
|
|
150
|
+
// Seek cursor to the page at given offset; if successful, returns SQLITE_OK and page of size
|
|
151
|
+
// PageSize() can be found at PageData().
|
|
152
|
+
// Otherwise, PageSize() and PageData() are undefined; returns SQLITE_NOTFOUND if desired page
|
|
153
|
+
// isn't present in DBI, or any other error code.
|
|
154
|
+
int Seek(sqlite_int64 offset) noexcept {
|
|
155
|
+
int rc = sqlite3_reset(cursor_.get());
|
|
156
|
+
if (rc != SQLITE_OK) {
|
|
157
|
+
last_error_ = sqlite3_errstr(rc);
|
|
158
|
+
return rc;
|
|
159
|
+
}
|
|
160
|
+
rc = sqlite3_bind_int64(cursor_.get(), 1, offset);
|
|
161
|
+
if (rc != SQLITE_OK) {
|
|
162
|
+
last_error_ = sqlite3_errmsg(dbiconn_.get());
|
|
163
|
+
return rc;
|
|
164
|
+
}
|
|
165
|
+
rc = sqlite3_step(cursor_.get());
|
|
166
|
+
if (rc != SQLITE_ROW) {
|
|
167
|
+
if (rc == SQLITE_DONE) {
|
|
168
|
+
return SQLITE_NOTFOUND;
|
|
169
|
+
}
|
|
170
|
+
last_error_ = sqlite3_errmsg(dbiconn_.get());
|
|
171
|
+
return SQLITE_ERROR;
|
|
172
|
+
}
|
|
173
|
+
if (sqlite3_column_type(cursor_.get(), 0) != SQLITE_BLOB) {
|
|
174
|
+
last_error_ = "corrupt .dbi (invalid stored page blob)";
|
|
175
|
+
return SQLITE_CORRUPT;
|
|
176
|
+
}
|
|
177
|
+
auto sz = sqlite3_column_bytes(cursor_.get(), 0);
|
|
178
|
+
if (sz < 512 || sz > 65536) {
|
|
179
|
+
last_error_ = "corrupt .dbi (invalid stored page size)";
|
|
180
|
+
return SQLITE_CORRUPT;
|
|
181
|
+
}
|
|
182
|
+
return SQLITE_OK;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
int PageSize() const noexcept { return sqlite3_column_bytes(cursor_.get(), 0); }
|
|
186
|
+
|
|
187
|
+
const void *PageData() const noexcept { return sqlite3_column_blob(cursor_.get(), 0); }
|
|
188
|
+
|
|
189
|
+
// Seek to page 1 (offset 0) and set header to its first 100 bytes
|
|
190
|
+
int MainDatabaseHeader(std::string &header) noexcept {
|
|
191
|
+
int rc = Seek(0);
|
|
192
|
+
if (rc == SQLITE_NOTFOUND) {
|
|
193
|
+
last_error_ = "corrupt .dbi (page 1 missing)";
|
|
194
|
+
return SQLITE_CORRUPT;
|
|
195
|
+
}
|
|
196
|
+
if (rc != SQLITE_OK) {
|
|
197
|
+
return rc;
|
|
198
|
+
}
|
|
199
|
+
assert(PageSize() > 100);
|
|
200
|
+
header.assign((const char *)PageData(), 100);
|
|
201
|
+
return SQLITE_OK;
|
|
202
|
+
}
|
|
203
|
+
};
|