Skip to content

Commit d3a156c

Browse files
committed
bundle SQLean extensions
A common complain with libSQL is how to run extensions. The main mechanism, with a .so, has a lot of issues around how those .so are distributed. The most common extensions are the ones in the sqlean package. We can improve this experience by bundling them in our sqlite build. Not all SQLean extensions are kosher: some of them, like fileio, use the vfs. Others, are deemed too complex. The extensions included here are a subset that we deem important enough, and low risk enough, to just be a part of the main bundle.
1 parent 5b8934e commit d3a156c

File tree

143 files changed

+67523
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

143 files changed

+67523
-4
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libsql-ffi/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ libsql-wasmtime-bindings = { version = "0.2.1", optional = true }
1616
[build-dependencies]
1717
bindgen = "0.66.1"
1818
cc = "1.0"
19+
glob = "0.3"
1920

2021
[features]
2122
session = []
@@ -28,3 +29,17 @@ wasm32-wasi-vfs = []
2829
unlock_notify = []
2930
preupdate_hook = []
3031
sqlcipher = []
32+
sqlean-extension-uuid = []
33+
sqlean-extension-crypto = []
34+
sqlean-extension-fuzzy = []
35+
sqlean-extension-math = []
36+
sqlean-extension-stats = []
37+
sqlean-extension-text = []
38+
sqlean-extensions = [
39+
"sqlean-extension-uuid",
40+
"sqlean-extension-crypto",
41+
"sqlean-extension-fuzzy",
42+
"sqlean-extension-math",
43+
"sqlean-extension-stats",
44+
"sqlean-extension-text"
45+
]

libsql-ffi/build.rs

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use glob::glob;
12
use std::env;
23
use std::ffi::OsString;
34
use std::fs::{self, OpenOptions};
@@ -130,6 +131,57 @@ fn make_amalgamation() {
130131
.unwrap();
131132
}
132133

134+
fn generate_sqlean(enabled_extensions: &[&str], output_path: &Path) -> io::Result<()> {
135+
let mut content = String::from(
136+
r#"// Generated by build.rs
137+
138+
139+
#include "sqlite3.c"
140+
SQLITE_EXTENSION_INIT1
141+
142+
"#,
143+
);
144+
145+
for ext in enabled_extensions {
146+
content.push_str(&format!("#include \"{}/extension.h\"\n", ext));
147+
}
148+
149+
content.push_str(
150+
r#"
151+
#include "sqlean.h"
152+
153+
static void sqlean_version(sqlite3_context* context, int argc, sqlite3_value** argv) {
154+
sqlite3_result_text(context, SQLEAN_VERSION, -1, SQLITE_STATIC);
155+
}
156+
157+
#ifdef _WIN32
158+
__declspec(dllexport)
159+
#endif
160+
int sqlite3_sqlean_init(sqlite3* db, char** errmsg_ptr, const sqlite3_api_routines* api) {
161+
(void)errmsg_ptr;
162+
SQLITE_EXTENSION_INIT2(api);
163+
static const int flags = SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC;
164+
sqlite3_create_function(db, "sqlean_version", 0, flags, 0, sqlean_version, 0, 0);
165+
"#,
166+
);
167+
168+
for ext in enabled_extensions {
169+
content.push_str(&format!(" {}_init(db);\n", ext));
170+
}
171+
172+
content.push_str(
173+
r#" return SQLITE_OK;
174+
}
175+
176+
int core_init(const char* dummy) {
177+
return sqlite3_auto_extension((void*)sqlite3_sqlean_init);
178+
}
179+
"#,
180+
);
181+
182+
std::fs::write(output_path, content)
183+
}
184+
133185
pub fn build_bundled(out_dir: &str, out_path: &Path) {
134186
let bindgen_rs_path = if cfg!(feature = "session") {
135187
"bundled/bindings/session_bindgen.rs"
@@ -146,8 +198,7 @@ pub fn build_bundled(out_dir: &str, out_path: &Path) {
146198
std::fs::copy(format!("{dir}/{bindgen_rs_path}"), out_path).unwrap();
147199

148200
let mut cfg = cc::Build::new();
149-
cfg.file(format!("{BUNDLED_DIR}/src/sqlite3.c"))
150-
.flag("-std=c11")
201+
cfg.flag("-std=c11")
151202
.flag("-DSQLITE_CORE")
152203
.flag("-DSQLITE_DEFAULT_FOREIGN_KEYS=1")
153204
.flag("-DSQLITE_ENABLE_API_ARMOR")
@@ -169,6 +220,62 @@ pub fn build_bundled(out_dir: &str, out_path: &Path) {
169220
.flag("-D_POSIX_THREAD_SAFE_FUNCTIONS") // cross compile with MinGW
170221
.warnings(false);
171222

223+
let mut sqlean_patterns = vec![];
224+
let mut enabled_extensions = Vec::new();
225+
226+
if cfg!(feature = "sqlean-extension-crypto") {
227+
enabled_extensions.push("crypto");
228+
sqlean_patterns.push("crypto/*.c");
229+
}
230+
231+
if cfg!(feature = "sqlean-extension-fuzzy") {
232+
enabled_extensions.push("fuzzy");
233+
sqlean_patterns.push("fuzzy/*.c");
234+
}
235+
236+
if cfg!(feature = "sqlean-extension-math") {
237+
enabled_extensions.push("math");
238+
sqlean_patterns.push("math/*.c");
239+
}
240+
241+
if cfg!(feature = "sqlean-extension-stats") {
242+
enabled_extensions.push("stats");
243+
sqlean_patterns.push("stats/*.c");
244+
}
245+
246+
if cfg!(feature = "sqlean-extension-text") {
247+
enabled_extensions.push("text");
248+
sqlean_patterns.push("text/*.c");
249+
sqlean_patterns.push("text/*/*.c");
250+
}
251+
252+
if cfg!(feature = "sqlean-extension-uuid") {
253+
enabled_extensions.push("uuid");
254+
sqlean_patterns.push("uuid/*.c");
255+
}
256+
257+
if sqlean_patterns.is_empty() {
258+
cfg.file(format!("{BUNDLED_DIR}/src/sqlite3.c"));
259+
} else {
260+
cfg.flag("-DSQLITE_EXTRA_INIT=core_init");
261+
262+
let mut sqlean_sources = Vec::new();
263+
for pattern in sqlean_patterns {
264+
let full_pattern = format!("{BUNDLED_DIR}/sqlean/{}", pattern);
265+
sqlean_sources.extend(glob(&full_pattern).unwrap().filter_map(Result::ok));
266+
}
267+
268+
cfg.files(sqlean_sources);
269+
270+
let sqlean = Path::new(BUNDLED_DIR)
271+
.join("src")
272+
.join("sqlite3-sqlean-generated.c");
273+
generate_sqlean(&enabled_extensions, &sqlean).unwrap();
274+
cfg.file(&sqlean);
275+
276+
cfg.include(format!("{BUNDLED_DIR}/sqlean/"));
277+
}
278+
172279
if cfg!(feature = "wasmtime-bindings") {
173280
cfg.flag("-DLIBSQL_ENABLE_WASM_RUNTIME=1");
174281
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2023 Anton Zhiyanov, MIT License
2+
// https://github.com/nalgeon/sqlean
3+
4+
// Base32 encoding/decoding (RFC 4648)
5+
6+
#include <stdint.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
10+
static const char base32_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
11+
12+
uint8_t* base32_encode(const uint8_t* src, size_t len, size_t* out_len) {
13+
*out_len = ((len + 4) / 5) * 8;
14+
uint8_t* encoded = malloc(*out_len + 1);
15+
if (encoded == NULL) {
16+
*out_len = 0;
17+
return NULL;
18+
}
19+
20+
for (size_t i = 0, j = 0; i < len;) {
21+
uint32_t octet0 = i < len ? src[i++] : 0;
22+
uint32_t octet1 = i < len ? src[i++] : 0;
23+
uint32_t octet2 = i < len ? src[i++] : 0;
24+
uint32_t octet3 = i < len ? src[i++] : 0;
25+
uint32_t octet4 = i < len ? src[i++] : 0;
26+
27+
encoded[j++] = base32_chars[octet0 >> 3];
28+
encoded[j++] = base32_chars[((octet0 & 0x07) << 2) | (octet1 >> 6)];
29+
encoded[j++] = base32_chars[(octet1 >> 1) & 0x1F];
30+
encoded[j++] = base32_chars[((octet1 & 0x01) << 4) | (octet2 >> 4)];
31+
encoded[j++] = base32_chars[((octet2 & 0x0F) << 1) | (octet3 >> 7)];
32+
encoded[j++] = base32_chars[(octet3 >> 2) & 0x1F];
33+
encoded[j++] = base32_chars[((octet3 & 0x03) << 3) | (octet4 >> 5)];
34+
encoded[j++] = base32_chars[octet4 & 0x1F];
35+
}
36+
37+
if (len % 5 != 0) {
38+
size_t padding = 7 - (len % 5) * 8 / 5;
39+
for (size_t i = 0; i < padding; i++) {
40+
encoded[*out_len - padding + i] = '=';
41+
}
42+
}
43+
44+
encoded[*out_len] = '\0';
45+
return encoded;
46+
}
47+
48+
uint8_t* base32_decode(const uint8_t* src, size_t len, size_t* out_len) {
49+
while (len > 0 && src[len - 1] == '=') {
50+
len--;
51+
}
52+
*out_len = len * 5 / 8;
53+
uint8_t* decoded = malloc(*out_len);
54+
if (decoded == NULL) {
55+
*out_len = 0;
56+
return NULL;
57+
}
58+
59+
size_t bits = 0, value = 0, count = 0;
60+
for (size_t i = 0; i < len; i++) {
61+
uint8_t c = src[i];
62+
if (c >= 'A' && c <= 'Z') {
63+
c -= 'A';
64+
} else if (c >= '2' && c <= '7') {
65+
c -= '2' - 26;
66+
} else {
67+
continue;
68+
}
69+
value = (value << 5) | c;
70+
bits += 5;
71+
if (bits >= 8) {
72+
decoded[count++] = (uint8_t)(value >> (bits - 8));
73+
bits -= 8;
74+
}
75+
}
76+
if (bits >= 5 || (value & ((1 << bits) - 1)) != 0) {
77+
free(decoded);
78+
return NULL;
79+
}
80+
*out_len = count;
81+
return decoded;
82+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) 2023 Anton Zhiyanov, MIT License
2+
// https://github.com/nalgeon/sqlean
3+
4+
// Base32 encoding/decoding (RFC 4648)
5+
6+
#ifndef _BASE32_H_
7+
#define _BASE32_H_
8+
9+
#include <stdint.h>
10+
11+
uint8_t* base32_encode(const uint8_t* src, size_t len, size_t* out_len);
12+
uint8_t* base32_decode(const uint8_t* src, size_t len, size_t* out_len);
13+
14+
#endif /* _BASE32_H_ */
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright (c) 2023 Anton Zhiyanov, MIT License
2+
// https://github.com/nalgeon/sqlean
3+
4+
// Base64 encoding/decoding (RFC 4648)
5+
6+
#include <stdint.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
10+
static const char base64_chars[] =
11+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
12+
13+
uint8_t* base64_encode(const uint8_t* src, size_t len, size_t* out_len) {
14+
uint8_t* encoded = NULL;
15+
size_t i, j;
16+
uint32_t octets;
17+
18+
*out_len = ((len + 2) / 3) * 4;
19+
encoded = malloc(*out_len + 1);
20+
if (encoded == NULL) {
21+
*out_len = 0;
22+
return NULL;
23+
}
24+
25+
for (i = 0, j = 0; i < len; i += 3, j += 4) {
26+
octets =
27+
(src[i] << 16) | ((i + 1 < len ? src[i + 1] : 0) << 8) | (i + 2 < len ? src[i + 2] : 0);
28+
encoded[j] = base64_chars[(octets >> 18) & 0x3f];
29+
encoded[j + 1] = base64_chars[(octets >> 12) & 0x3f];
30+
encoded[j + 2] = base64_chars[(octets >> 6) & 0x3f];
31+
encoded[j + 3] = base64_chars[octets & 0x3f];
32+
}
33+
34+
if (len % 3 == 1) {
35+
encoded[*out_len - 1] = '=';
36+
encoded[*out_len - 2] = '=';
37+
} else if (len % 3 == 2) {
38+
encoded[*out_len - 1] = '=';
39+
}
40+
41+
encoded[*out_len] = '\0';
42+
return encoded;
43+
}
44+
45+
static const uint8_t base64_table[] = {
46+
// Map base64 characters to their corresponding values
47+
['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7,
48+
['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15,
49+
['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,
50+
['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31,
51+
['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39,
52+
['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,
53+
['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55,
54+
['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63,
55+
};
56+
57+
uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len) {
58+
if (len % 4 != 0) {
59+
return NULL;
60+
}
61+
62+
size_t padding = 0;
63+
if (src[len - 1] == '=') {
64+
padding++;
65+
}
66+
if (src[len - 2] == '=') {
67+
padding++;
68+
}
69+
70+
*out_len = (len / 4) * 3 - padding;
71+
uint8_t* decoded = malloc(*out_len);
72+
if (decoded == NULL) {
73+
*out_len = 0;
74+
return NULL;
75+
}
76+
77+
for (size_t i = 0, j = 0; i < len; i += 4, j += 3) {
78+
uint32_t block = 0;
79+
for (size_t k = 0; k < 4; k++) {
80+
block <<= 6;
81+
if (src[i + k] == '=') {
82+
padding--;
83+
} else {
84+
uint8_t index = base64_table[src[i + k]];
85+
if (index == 0 && src[i + k] != 'A') {
86+
free(decoded);
87+
return NULL;
88+
}
89+
block |= index;
90+
}
91+
}
92+
93+
decoded[j] = (block >> 16) & 0xFF;
94+
if (j + 1 < *out_len) {
95+
decoded[j + 1] = (block >> 8) & 0xFF;
96+
}
97+
if (j + 2 < *out_len) {
98+
decoded[j + 2] = block & 0xFF;
99+
}
100+
}
101+
102+
return decoded;
103+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) 2023 Anton Zhiyanov, MIT License
2+
// https://github.com/nalgeon/sqlean
3+
4+
// Base64 encoding/decoding (RFC 4648)
5+
6+
#ifndef BASE64_H
7+
#define BASE64_H
8+
9+
#include <stddef.h>
10+
#include <stdint.h>
11+
12+
uint8_t* base64_encode(const uint8_t* src, size_t len, size_t* out_len);
13+
uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len);
14+
15+
#endif /* BASE64_H */

0 commit comments

Comments
 (0)