├── .gitignore
├── .vscode
└── settings.json
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── example.ts
├── src
├── bufferToBase64.js
├── lib.rs
├── mod.ts
└── prepared.ts
└── tests
├── _open_plugin.js
├── e2e_test.js
└── ops_test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | *.sqlite3
3 | .deno_plugins
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.unstable": true,
4 | "[typescript]": {
5 | "editor.defaultFormatter": "axetroy.vscode-deno"
6 | },
7 | "[javascript]": {
8 | "editor.defaultFormatter": "axetroy.vscode-deno"
9 | }
10 | }
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | [[package]]
4 | name = "backtrace"
5 | version = "0.3.45"
6 | source = "registry+https://github.com/rust-lang/crates.io-index"
7 | checksum = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8"
8 | dependencies = [
9 | "backtrace-sys",
10 | "cfg-if",
11 | "libc",
12 | "rustc-demangle",
13 | ]
14 |
15 | [[package]]
16 | name = "backtrace-sys"
17 | version = "0.1.34"
18 | source = "registry+https://github.com/rust-lang/crates.io-index"
19 | checksum = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
20 | dependencies = [
21 | "cc",
22 | "libc",
23 | ]
24 |
25 | [[package]]
26 | name = "base64"
27 | version = "0.11.0"
28 | source = "registry+https://github.com/rust-lang/crates.io-index"
29 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
30 |
31 | [[package]]
32 | name = "bitflags"
33 | version = "1.2.1"
34 | source = "registry+https://github.com/rust-lang/crates.io-index"
35 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
36 |
37 | [[package]]
38 | name = "cargo_gn"
39 | version = "0.0.15"
40 | source = "registry+https://github.com/rust-lang/crates.io-index"
41 | checksum = "5ba7d7f7b201dfcbc314b14f2176c92f8ba521dab538b40e426ffed25ed7cd80"
42 |
43 | [[package]]
44 | name = "cc"
45 | version = "1.0.50"
46 | source = "registry+https://github.com/rust-lang/crates.io-index"
47 | checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
48 |
49 | [[package]]
50 | name = "cfg-if"
51 | version = "0.1.10"
52 | source = "registry+https://github.com/rust-lang/crates.io-index"
53 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
54 |
55 | [[package]]
56 | name = "deno_core"
57 | version = "0.45.0"
58 | source = "registry+https://github.com/rust-lang/crates.io-index"
59 | checksum = "9e22a025647cd78e2444cf422022fb00e5fc6e7c30d63b0611ca1e6c39faddd5"
60 | dependencies = [
61 | "downcast-rs",
62 | "futures 0.3.4",
63 | "lazy_static",
64 | "libc",
65 | "log",
66 | "rusty_v8",
67 | "serde_json",
68 | "url",
69 | ]
70 |
71 | [[package]]
72 | name = "deno_sqlite_plugin"
73 | version = "0.4.0"
74 | dependencies = [
75 | "base64",
76 | "deno_core",
77 | "futures 0.3.4",
78 | "rusqlite",
79 | "serde",
80 | "serde_json",
81 | ]
82 |
83 | [[package]]
84 | name = "downcast-rs"
85 | version = "1.1.1"
86 | source = "registry+https://github.com/rust-lang/crates.io-index"
87 | checksum = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6"
88 |
89 | [[package]]
90 | name = "failure"
91 | version = "0.1.7"
92 | source = "registry+https://github.com/rust-lang/crates.io-index"
93 | checksum = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b"
94 | dependencies = [
95 | "backtrace",
96 | ]
97 |
98 | [[package]]
99 | name = "fallible-iterator"
100 | version = "0.2.0"
101 | source = "registry+https://github.com/rust-lang/crates.io-index"
102 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
103 |
104 | [[package]]
105 | name = "fallible-streaming-iterator"
106 | version = "0.1.9"
107 | source = "registry+https://github.com/rust-lang/crates.io-index"
108 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
109 |
110 | [[package]]
111 | name = "futures"
112 | version = "0.1.29"
113 | source = "registry+https://github.com/rust-lang/crates.io-index"
114 | checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
115 |
116 | [[package]]
117 | name = "futures"
118 | version = "0.3.4"
119 | source = "registry+https://github.com/rust-lang/crates.io-index"
120 | checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
121 | dependencies = [
122 | "futures-channel",
123 | "futures-core",
124 | "futures-executor",
125 | "futures-io",
126 | "futures-sink",
127 | "futures-task",
128 | "futures-util",
129 | ]
130 |
131 | [[package]]
132 | name = "futures-channel"
133 | version = "0.3.4"
134 | source = "registry+https://github.com/rust-lang/crates.io-index"
135 | checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
136 | dependencies = [
137 | "futures-core",
138 | "futures-sink",
139 | ]
140 |
141 | [[package]]
142 | name = "futures-core"
143 | version = "0.3.4"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
146 |
147 | [[package]]
148 | name = "futures-executor"
149 | version = "0.3.4"
150 | source = "registry+https://github.com/rust-lang/crates.io-index"
151 | checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
152 | dependencies = [
153 | "futures-core",
154 | "futures-task",
155 | "futures-util",
156 | "num_cpus",
157 | ]
158 |
159 | [[package]]
160 | name = "futures-io"
161 | version = "0.3.4"
162 | source = "registry+https://github.com/rust-lang/crates.io-index"
163 | checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
164 |
165 | [[package]]
166 | name = "futures-macro"
167 | version = "0.3.4"
168 | source = "registry+https://github.com/rust-lang/crates.io-index"
169 | checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
170 | dependencies = [
171 | "proc-macro-hack",
172 | "proc-macro2",
173 | "quote",
174 | "syn",
175 | ]
176 |
177 | [[package]]
178 | name = "futures-sink"
179 | version = "0.3.4"
180 | source = "registry+https://github.com/rust-lang/crates.io-index"
181 | checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
182 |
183 | [[package]]
184 | name = "futures-task"
185 | version = "0.3.4"
186 | source = "registry+https://github.com/rust-lang/crates.io-index"
187 | checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
188 |
189 | [[package]]
190 | name = "futures-util"
191 | version = "0.3.4"
192 | source = "registry+https://github.com/rust-lang/crates.io-index"
193 | checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
194 | dependencies = [
195 | "futures 0.1.29",
196 | "futures-channel",
197 | "futures-core",
198 | "futures-io",
199 | "futures-macro",
200 | "futures-sink",
201 | "futures-task",
202 | "memchr",
203 | "pin-utils",
204 | "proc-macro-hack",
205 | "proc-macro-nested",
206 | "slab",
207 | ]
208 |
209 | [[package]]
210 | name = "hermit-abi"
211 | version = "0.1.8"
212 | source = "registry+https://github.com/rust-lang/crates.io-index"
213 | checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
214 | dependencies = [
215 | "libc",
216 | ]
217 |
218 | [[package]]
219 | name = "idna"
220 | version = "0.2.0"
221 | source = "registry+https://github.com/rust-lang/crates.io-index"
222 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
223 | dependencies = [
224 | "matches",
225 | "unicode-bidi",
226 | "unicode-normalization",
227 | ]
228 |
229 | [[package]]
230 | name = "itoa"
231 | version = "0.4.5"
232 | source = "registry+https://github.com/rust-lang/crates.io-index"
233 | checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
234 |
235 | [[package]]
236 | name = "lazy_static"
237 | version = "1.4.0"
238 | source = "registry+https://github.com/rust-lang/crates.io-index"
239 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
240 |
241 | [[package]]
242 | name = "libc"
243 | version = "0.2.69"
244 | source = "registry+https://github.com/rust-lang/crates.io-index"
245 | checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
246 |
247 | [[package]]
248 | name = "libsqlite3-sys"
249 | version = "0.18.0"
250 | source = "registry+https://github.com/rust-lang/crates.io-index"
251 | checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd"
252 | dependencies = [
253 | "cc",
254 | "pkg-config",
255 | "vcpkg",
256 | ]
257 |
258 | [[package]]
259 | name = "linked-hash-map"
260 | version = "0.5.2"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 | checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
263 |
264 | [[package]]
265 | name = "log"
266 | version = "0.4.8"
267 | source = "registry+https://github.com/rust-lang/crates.io-index"
268 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
269 | dependencies = [
270 | "cfg-if",
271 | ]
272 |
273 | [[package]]
274 | name = "lru-cache"
275 | version = "0.1.2"
276 | source = "registry+https://github.com/rust-lang/crates.io-index"
277 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
278 | dependencies = [
279 | "linked-hash-map",
280 | ]
281 |
282 | [[package]]
283 | name = "matches"
284 | version = "0.1.8"
285 | source = "registry+https://github.com/rust-lang/crates.io-index"
286 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
287 |
288 | [[package]]
289 | name = "memchr"
290 | version = "2.3.3"
291 | source = "registry+https://github.com/rust-lang/crates.io-index"
292 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
293 |
294 | [[package]]
295 | name = "num_cpus"
296 | version = "1.12.0"
297 | source = "registry+https://github.com/rust-lang/crates.io-index"
298 | checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
299 | dependencies = [
300 | "hermit-abi",
301 | "libc",
302 | ]
303 |
304 | [[package]]
305 | name = "percent-encoding"
306 | version = "2.1.0"
307 | source = "registry+https://github.com/rust-lang/crates.io-index"
308 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
309 |
310 | [[package]]
311 | name = "pin-utils"
312 | version = "0.1.0-alpha.4"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
315 |
316 | [[package]]
317 | name = "pkg-config"
318 | version = "0.3.17"
319 | source = "registry+https://github.com/rust-lang/crates.io-index"
320 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
321 |
322 | [[package]]
323 | name = "proc-macro-hack"
324 | version = "0.5.11"
325 | source = "registry+https://github.com/rust-lang/crates.io-index"
326 | checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
327 | dependencies = [
328 | "proc-macro2",
329 | "quote",
330 | "syn",
331 | ]
332 |
333 | [[package]]
334 | name = "proc-macro-nested"
335 | version = "0.1.3"
336 | source = "registry+https://github.com/rust-lang/crates.io-index"
337 | checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
338 |
339 | [[package]]
340 | name = "proc-macro2"
341 | version = "1.0.9"
342 | source = "registry+https://github.com/rust-lang/crates.io-index"
343 | checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
344 | dependencies = [
345 | "unicode-xid",
346 | ]
347 |
348 | [[package]]
349 | name = "quote"
350 | version = "1.0.3"
351 | source = "registry+https://github.com/rust-lang/crates.io-index"
352 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
353 | dependencies = [
354 | "proc-macro2",
355 | ]
356 |
357 | [[package]]
358 | name = "redox_syscall"
359 | version = "0.1.56"
360 | source = "registry+https://github.com/rust-lang/crates.io-index"
361 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
362 |
363 | [[package]]
364 | name = "rusqlite"
365 | version = "0.23.1"
366 | source = "registry+https://github.com/rust-lang/crates.io-index"
367 | checksum = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b"
368 | dependencies = [
369 | "bitflags",
370 | "fallible-iterator",
371 | "fallible-streaming-iterator",
372 | "libsqlite3-sys",
373 | "lru-cache",
374 | "memchr",
375 | "smallvec",
376 | "time",
377 | ]
378 |
379 | [[package]]
380 | name = "rustc-demangle"
381 | version = "0.1.16"
382 | source = "registry+https://github.com/rust-lang/crates.io-index"
383 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
384 |
385 | [[package]]
386 | name = "rusty_v8"
387 | version = "0.4.2"
388 | source = "registry+https://github.com/rust-lang/crates.io-index"
389 | checksum = "acb0ad56a57c42009a8d16df5fa94ae882ad0ffe0e88fe1a23b261b3affbccf2"
390 | dependencies = [
391 | "bitflags",
392 | "cargo_gn",
393 | "lazy_static",
394 | "libc",
395 | "which",
396 | ]
397 |
398 | [[package]]
399 | name = "ryu"
400 | version = "1.0.2"
401 | source = "registry+https://github.com/rust-lang/crates.io-index"
402 | checksum = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
403 |
404 | [[package]]
405 | name = "serde"
406 | version = "1.0.106"
407 | source = "registry+https://github.com/rust-lang/crates.io-index"
408 | checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
409 | dependencies = [
410 | "serde_derive",
411 | ]
412 |
413 | [[package]]
414 | name = "serde_derive"
415 | version = "1.0.106"
416 | source = "registry+https://github.com/rust-lang/crates.io-index"
417 | checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
418 | dependencies = [
419 | "proc-macro2",
420 | "quote",
421 | "syn",
422 | ]
423 |
424 | [[package]]
425 | name = "serde_json"
426 | version = "1.0.53"
427 | source = "registry+https://github.com/rust-lang/crates.io-index"
428 | checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2"
429 | dependencies = [
430 | "itoa",
431 | "ryu",
432 | "serde",
433 | ]
434 |
435 | [[package]]
436 | name = "slab"
437 | version = "0.4.2"
438 | source = "registry+https://github.com/rust-lang/crates.io-index"
439 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
440 |
441 | [[package]]
442 | name = "smallvec"
443 | version = "1.4.0"
444 | source = "registry+https://github.com/rust-lang/crates.io-index"
445 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
446 |
447 | [[package]]
448 | name = "syn"
449 | version = "1.0.16"
450 | source = "registry+https://github.com/rust-lang/crates.io-index"
451 | checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
452 | dependencies = [
453 | "proc-macro2",
454 | "quote",
455 | "unicode-xid",
456 | ]
457 |
458 | [[package]]
459 | name = "time"
460 | version = "0.1.42"
461 | source = "registry+https://github.com/rust-lang/crates.io-index"
462 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
463 | dependencies = [
464 | "libc",
465 | "redox_syscall",
466 | "winapi",
467 | ]
468 |
469 | [[package]]
470 | name = "unicode-bidi"
471 | version = "0.3.4"
472 | source = "registry+https://github.com/rust-lang/crates.io-index"
473 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
474 | dependencies = [
475 | "matches",
476 | ]
477 |
478 | [[package]]
479 | name = "unicode-normalization"
480 | version = "0.1.12"
481 | source = "registry+https://github.com/rust-lang/crates.io-index"
482 | checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
483 | dependencies = [
484 | "smallvec",
485 | ]
486 |
487 | [[package]]
488 | name = "unicode-xid"
489 | version = "0.2.0"
490 | source = "registry+https://github.com/rust-lang/crates.io-index"
491 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
492 |
493 | [[package]]
494 | name = "url"
495 | version = "2.1.1"
496 | source = "registry+https://github.com/rust-lang/crates.io-index"
497 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
498 | dependencies = [
499 | "idna",
500 | "matches",
501 | "percent-encoding",
502 | ]
503 |
504 | [[package]]
505 | name = "vcpkg"
506 | version = "0.2.8"
507 | source = "registry+https://github.com/rust-lang/crates.io-index"
508 | checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
509 |
510 | [[package]]
511 | name = "which"
512 | version = "3.1.1"
513 | source = "registry+https://github.com/rust-lang/crates.io-index"
514 | checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
515 | dependencies = [
516 | "failure",
517 | "libc",
518 | ]
519 |
520 | [[package]]
521 | name = "winapi"
522 | version = "0.3.8"
523 | source = "registry+https://github.com/rust-lang/crates.io-index"
524 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
525 | dependencies = [
526 | "winapi-i686-pc-windows-gnu",
527 | "winapi-x86_64-pc-windows-gnu",
528 | ]
529 |
530 | [[package]]
531 | name = "winapi-i686-pc-windows-gnu"
532 | version = "0.4.0"
533 | source = "registry+https://github.com/rust-lang/crates.io-index"
534 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
535 |
536 | [[package]]
537 | name = "winapi-x86_64-pc-windows-gnu"
538 | version = "0.4.0"
539 | source = "registry+https://github.com/rust-lang/crates.io-index"
540 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
541 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "deno_sqlite_plugin"
3 | version = "0.4.0"
4 | authors = ["Daniel Buckmaster"]
5 | edition = "2018"
6 | publish = false
7 |
8 | [lib]
9 | crate-type = ["cdylib"]
10 |
11 | [dependencies]
12 | base64 = "0.11.0"
13 | futures = "0.3.1"
14 | deno_core = "0.45.0"
15 | serde = { version = "1.0.104", features = ["derive"] }
16 | serde_json = "1.0.48"
17 |
18 | [dependencies.rusqlite]
19 | version = "0.23.1"
20 | features = ["bundled"]
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Daniel Buckmaster
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :rotating_light: Use [this](https://github.com/denodrivers/sqlite3) instead
2 |
3 | # Deno SQLite plugin
4 |
5 |
6 |
7 |
8 | Bindings to [rusqlite](https://github.com/jgallagher/rusqlite) for [deno](https://deno.land).
9 |
10 | ## Stability
11 |
12 | **NOT PRODUCTION READY**
13 |
14 | This plugin **will panic** if anything goes slightly wrong.
15 | Probably don't use it in production just yet.
16 |
17 | **COMPATIBILITY** 🦕
18 |
19 | This plugin has been tested against Deno v1.0.0.
20 | It appears that Deno is [removing its plugin API](https://github.com/denoland/deno/pull/11152) in favour of an FFI, possibly in version 1.13, so this plugin will no longer work.
21 |
22 | ## Usage
23 |
24 | First, download the compiled plugin (~2MB).
25 | If you're not using Linux, you will have to compile from source for now (see below).
26 |
27 | ```bash
28 | wget https://github.com/crabmusket/deno_sqlite_plugin/releases/download/v0.4/libdeno_sqlite_plugin.so
29 | ```
30 |
31 | Now copy this to `sqlite.ts`:
32 |
33 | ```ts
34 | import { Sqlite } from "https://deno.land/x/sqlite_plugin@v0.4/src/mod.ts";
35 |
36 | Deno.openPlugin("./libdeno_sqlite_plugin.so");
37 |
38 | const sqlite = new Sqlite();
39 | const db = await sqlite.connect(":memory:");
40 |
41 | await db.execute(`
42 | CREATE TABLE IF NOT EXISTS podcasts (
43 | name TEXT,
44 | subject TEXT
45 | )
46 | `);
47 |
48 | await db.execute(
49 | `
50 | INSERT INTO podcasts (name, subject)
51 | VALUES (?, ?), (?, ?), (?, ?)
52 | `,
53 | [
54 | ["Econtalk", "economics"],
55 | ["Random Shipping Forecast", "shipping"],
56 | ["Revolutions", "revolutions"],
57 | ].flat(),
58 | );
59 |
60 | console.log(
61 | await db.query("SELECT name, subject FROM podcasts", []),
62 | );
63 |
64 | ```
65 |
66 | And then run the script:
67 |
68 | ```bash
69 | $ deno run --unstable --allow-plugin sqlite.ts
70 | [
71 | [ "Econtalk", "economics" ],
72 | [ "Random Shipping Forecast", "shipping" ],
73 | [ "Revolutions", "revolutions" ]
74 | ]
75 | ```
76 |
77 | ## Auto-download plugin
78 |
79 | You can also import `prepared.ts` to fetch the plugin transparently using [plugin_prepare](https://github.com/manyuanrong/deno-plugin-prepare).
80 | Replace the top line of the example above with:
81 |
82 | ```ts
83 | import { Sqlite } from "https://deno.land/x/sqlite_plugin@v0.4/src/prepared.ts";
84 | ```
85 |
86 | This may be more ergonomic if you want to use Sqlite in a library that others will depend on.
87 |
88 | ## Build from source
89 |
90 | Install Rust (I recommend [rustup](https://rustup.rs/)) and [deno](https://deno.land/#install) and build with Cargo:
91 |
92 | ```bash
93 | cargo build --release
94 | ```
95 |
96 | This will take a few minutes.
97 | A release build will use a few hundred MB of disk space, and a debug build (if you don't specify the `--release` flag) may use up to 600MB.
98 |
99 | After you compile, I recommend `strip`ping Linux libraries:
100 |
101 | ```bash
102 | strip ./target/release/libdeno_sqlite_plugin.so
103 | ```
104 |
105 | This will reduce the filesize from ~10MB to ~2MB.
106 |
107 | ## When would I use this?
108 |
109 | Use this plugin whenever you would embed an SQLite database into any other program.
110 | It's essentially just a JavaScript wrapper around a Rust wrapper around the actual SQLite C code.
111 |
112 | [deno-sqlite](https://github.com/dyedgreen/deno-sqlite), which is awesome, works in browsers; this plugin _does not_.
113 | This plugin _does_ allow you to work with SQLite databases from the filesystem with all the durability and performance SQLite provides.
114 | Wasm-based SQLite ports require you to load the entire database file into memory, operate on it, then save the whole thing back to disk again.
115 |
116 | SQLite is [_very good_](https://sqlite.org/testing.html).
117 | You might not always need a remote database like MySQL or Postgres.
118 | But if you do, check out [deno_mysql](https://github.com/manyuanrong/deno_mysql) or [deno-postgres](https://github.com/buildondata/deno-postgres).
119 |
120 | ## Security
121 |
122 | There's a lot of discussion about Deno's [security model](https://deno.land/manual/getting_started/permissions) and how it can help application developers.
123 |
124 | Be aware that when running with the `--use-plugin` flag which is required in order to use this plugin, all code running inside your script (including 3rd-party code) may call `Deno.openPlugin` and open arbitrary plugins.
125 | The current plugin API does not seem to respect `--allow-read` whitelisting.
126 | However, the code cannot download plugins from the internet (unless you allow it to with `--allow-net`), so the application can only load plugins that already exist on your filesystem.
127 |
128 | When running, Deno's permissions API does not apply to the plugin code.
129 | So, for example, even if you don't specify `--allow-write`, this plugin can be used to create SQLite files in arbitrary locations on disk.
130 |
131 | ## How does it work?
132 |
133 | Query parameters are encoded to JSON text and sent from deno's JS runtime to the plugin.
134 | The plugin decodes the JSON then performs the query against SQLite using rusqlite.
135 | It then re-encodes the result as JSON and sends it back to JS-land.
136 |
137 | SQLite's [BLOB type](https://www.sqlite.org/datatype3.html) is encoded using base64 for transmission via JSON and exposed in the deno interface as an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer).
138 | (It might be nice to use a binary serialisation format like CBOR instead of JSON to avoid the base64 encode/decode on either side.)
139 |
140 | ## TODO
141 |
142 | - [ ] Add checks/warnings for Deno version within the code, since plugin API may be unstable
143 | - [ ] Please don't look at any of my code, it's awful
144 | - [ ] Remove all uses of `unwrap()` in Rust; pass errors to JS gracefully
145 | - [ ] Test performance of JSON serialisation for ops and investigate CBOR
146 | - [ ] Implement more [connection methods](https://docs.rs/rusqlite/0.21.0/rusqlite/struct.Connection.html)?
147 | - [ ] What are the implications of using `thread_local!` for `CONNECTION_MAP`?
148 | - [ ] [Embed version](https://stackoverflow.com/a/27841363)
149 | - [ ] Improve [docs](https://doc.deno.land/https/raw.githubusercontent.com/crabmusket/deno_sqlite_plugin/master/src/mod.ts)
150 | - [ ] Use Deno's resource table instead of maintaining a connection map
151 | - [ ] Tests 😬
152 |
153 | ## Licenses
154 |
155 | * SQLite is [public domain](https://sqlite.org/copyright.html)
156 | * rusqlite is [MIT](https://github.com/jgallagher/rusqlite/blob/master/LICENSE)
157 | * [Buffer to base64](./src/bufferToBase64.js) implementation is [MIT](https://gist.githubusercontent.com/jonleighton/958841/raw/fb05a8632efb75d85d43deb593df04367ce48371/base64ArrayBuffer.js)
158 | * This package's code is [MIT](./LICENSE)
159 |
--------------------------------------------------------------------------------
/example.ts:
--------------------------------------------------------------------------------
1 | import { Sqlite } from "./src/mod.ts";
2 |
3 | Deno.openPlugin("./target/debug/libdeno_sqlite_plugin.so");
4 |
5 | const sqlite = new Sqlite();
6 | const db = await sqlite.connect(":memory:");
7 |
8 | await db.execute(`
9 | CREATE TABLE IF NOT EXISTS podcasts (
10 | name TEXT,
11 | subject TEXT
12 | )
13 | `);
14 |
15 | await db.execute(
16 | `
17 | INSERT INTO podcasts (name, subject)
18 | VALUES (?, ?), (?, ?), (?, ?)
19 | `,
20 | [
21 | ["Econtalk", "economics"],
22 | ["Random Shipping Forecast", "shipping"],
23 | ["Revolutions", "revolutions"],
24 | ].flat(),
25 | );
26 |
27 | console.log(
28 | await db.query("SELECT name, subject FROM podcasts", []),
29 | );
30 |
--------------------------------------------------------------------------------
/src/bufferToBase64.js:
--------------------------------------------------------------------------------
1 | // https://gist.githubusercontent.com/jonleighton/958841/raw/fb05a8632efb75d85d43deb593df04367ce48371/base64ArrayBuffer.js
2 |
3 | // Converts an ArrayBuffer directly to base64, without any intermediate 'convert to string then
4 | // use window.btoa' step. According to my tests, this appears to be a faster approach:
5 | // http://jsperf.com/encoding-xhr-image-data/5
6 |
7 | /*
8 | MIT LICENSE
9 |
10 | Copyright 2011 Jon Leighton
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 | */
18 |
19 | export function bufferToBase64(arrayBuffer) {
20 | var base64 = "";
21 | var encodings =
22 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
23 |
24 | var bytes = new Uint8Array(arrayBuffer);
25 | var byteLength = bytes.byteLength;
26 | var byteRemainder = byteLength % 3;
27 | var mainLength = byteLength - byteRemainder;
28 |
29 | var a, b, c, d;
30 | var chunk;
31 |
32 | // Main loop deals with bytes in chunks of 3
33 | for (var i = 0; i < mainLength; i = i + 3) {
34 | // Combine the three bytes into a single integer
35 | chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
36 |
37 | // Use bitmasks to extract 6-bit segments from the triplet
38 | a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
39 | b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
40 | c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
41 | d = chunk & 63; // 63 = 2^6 - 1
42 |
43 | // Convert the raw binary segments to the appropriate ASCII encoding
44 | base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
45 | }
46 |
47 | // Deal with the remaining bytes and padding
48 | if (byteRemainder == 1) {
49 | chunk = bytes[mainLength];
50 |
51 | a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
52 |
53 | // Set the 4 least significant bits to zero
54 | b = (chunk & 3) << 4; // 3 = 2^2 - 1
55 |
56 | base64 += encodings[a] + encodings[b] + "==";
57 | } else if (byteRemainder == 2) {
58 | chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
59 |
60 | a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
61 | b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
62 |
63 | // Set the 2 least significant bits to zero
64 | c = (chunk & 15) << 2; // 15 = 2^4 - 1
65 |
66 | base64 += encodings[a] + encodings[b] + encodings[c] + "=";
67 | }
68 |
69 | return base64;
70 | }
71 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | use deno_core::plugin_api::Buf;
2 | use deno_core::plugin_api::Interface;
3 | use deno_core::plugin_api::Op;
4 | use deno_core::plugin_api::ZeroCopyBuf;
5 |
6 | use rusqlite::{Connection, types::ValueRef, types::Value as SqliteValue};
7 |
8 | use std::cell::RefCell;
9 | use std::collections::HashMap;
10 |
11 | use serde::{Deserialize, Serialize};
12 | use serde_json::value::Value as JsonValue;
13 |
14 | thread_local! {
15 | static CONNECTION_INDEX: RefCell = RefCell::new(0);
16 | static CONNECTION_MAP: RefCell> = RefCell::new(HashMap::new());
17 | }
18 |
19 | #[no_mangle]
20 | pub fn deno_plugin_init(interface: &mut dyn Interface) {
21 | interface.register_op("tag:crabmusket.github.io,2020:sqliteOpenConnection", op_open_connection);
22 | interface.register_op("tag:crabmusket.github.io,2020:sqliteExecute", op_execute);
23 | interface.register_op("tag:crabmusket.github.io,2020:sqliteQuery", op_query);
24 | }
25 |
26 | #[derive(Serialize, Deserialize)]
27 | struct OpOpenConnectionParams {
28 | path: String,
29 | }
30 |
31 | #[derive(Serialize, Deserialize)]
32 | struct OpOpenConnectionResponse {
33 | error: Option,
34 | connection_id: Option,
35 | }
36 |
37 | pub fn op_open_connection(
38 | _interface: &mut dyn Interface,
39 | data: &[u8],
40 | _zero_copy: Option,
41 | ) -> Op {
42 | let params: OpOpenConnectionParams = serde_json::from_slice(data).unwrap();
43 | let mut connection_id = 0;
44 | CONNECTION_INDEX.with(|cell| {
45 | connection_id = cell.replace_with(|&mut i| i + 1);
46 | });
47 | let conn = Connection::open(params.path.clone()).unwrap();
48 | CONNECTION_MAP.with(|cell| {
49 | cell.borrow_mut().insert(connection_id, conn);
50 | });
51 | let response = OpOpenConnectionResponse {
52 | error: None,
53 | connection_id: Some(connection_id),
54 | };
55 | let result = serde_json::to_vec(&response).unwrap();
56 | Op::Sync(result.into_boxed_slice())
57 | }
58 |
59 | #[derive(Serialize, Deserialize)]
60 | struct OpExecuteParams {
61 | connection_id: u32,
62 | statement: String,
63 | params: Vec,
64 | }
65 |
66 | #[derive(Serialize, Deserialize)]
67 | struct OpExecuteResponse {
68 | error: String,
69 | rows_affected: usize,
70 | }
71 |
72 | pub fn op_execute(
73 | _interface: &mut dyn Interface,
74 | data: &[u8],
75 | _zero_copy: Option,
76 | ) -> Op {
77 | let params: OpExecuteParams = serde_json::from_slice(data).unwrap();
78 | let mut rows_affected: Option = None;
79 | CONNECTION_MAP.with(|cell| {
80 | rows_affected = cell.borrow()
81 | .get(¶ms.connection_id)
82 | .map(|conn| {
83 | let mut stmt = conn.prepare_cached(¶ms.statement).unwrap();
84 | stmt.execute(json_to_params(params.params)).unwrap()
85 | });
86 | });
87 | let response = OpExecuteResponse {
88 | error: "".to_string(),
89 | rows_affected: rows_affected.unwrap(),
90 | };
91 | let result = serde_json::to_vec(&response).unwrap();
92 | Op::Sync(result.into_boxed_slice())
93 | }
94 |
95 | #[derive(Serialize, Deserialize)]
96 | struct OpQueryParams {
97 | connection_id: u32,
98 | statement: String,
99 | params: Vec,
100 | }
101 |
102 | #[derive(Serialize, Deserialize)]
103 | struct OpQueryResponse {
104 | error: String,
105 | result: Vec>,
106 | }
107 |
108 | pub fn op_query(
109 | _interface: &mut dyn Interface,
110 | data: &[u8],
111 | _zero_copy: Option,
112 | ) -> Op {
113 | let params: OpQueryParams = serde_json::from_slice(data).unwrap();
114 | let mut result_rows = Vec::new();
115 | CONNECTION_MAP.with(|cell| {
116 | cell.borrow()
117 | .get(¶ms.connection_id)
118 | .map(|conn| {
119 | let mut stmt = conn.prepare_cached(¶ms.statement).unwrap();
120 | let mut rows = stmt.query(json_to_params(params.params)).unwrap();
121 | while let Some(row) = rows.next().unwrap() {
122 | let mut result_row = Vec::new();
123 | for i in 0..row.column_count() {
124 | result_row.push(match row.get_raw(i) {
125 | ValueRef::Null => JsonValue::Null,
126 | ValueRef::Integer(i) => JsonValue::Number(serde_json::Number::from(i)),
127 | ValueRef::Real(r) => JsonValue::Number(serde_json::Number::from_f64(r).unwrap()),
128 | ValueRef::Text(t) => JsonValue::String(String::from_utf8_lossy(t).to_string()),
129 | ValueRef::Blob(b) => JsonValue::Array(vec![
130 | JsonValue::String("blob:base64".to_string()),
131 | JsonValue::String(base64::encode(b))
132 | ]),
133 | });
134 | }
135 | result_rows.push(result_row)
136 | }
137 | });
138 | });
139 | let response = OpQueryResponse {
140 | error: "".to_string(),
141 | result: result_rows,
142 | };
143 | let result = serde_json::to_vec(&response).unwrap();
144 | Op::Sync(result.into_boxed_slice())
145 | }
146 |
147 | fn json_to_params(params: Vec) -> Vec {
148 | params.iter().map(|val| {
149 | match val {
150 | JsonValue::Null => SqliteValue::Null,
151 | JsonValue::Bool(b) => SqliteValue::Integer(if *b { 1 } else { 0 }),
152 | JsonValue::Number(n) => SqliteValue::Real(n.as_f64().unwrap()),
153 | JsonValue::String(s) => SqliteValue::Text(s.to_string()),
154 | // TODO: warn/error instead of silent NULL?
155 | JsonValue::Array(a) => parse_blob(&a).unwrap_or(SqliteValue::Null),
156 | // TODO: warn/error here?
157 | JsonValue::Object(_m) => SqliteValue::Null,
158 | }
159 | }).collect()
160 | }
161 |
162 | fn parse_blob(array: &Vec) -> Option {
163 | match array.as_slice() {
164 | [JsonValue::String(s1), JsonValue::String(s2)] => if s1.eq("blob:base64") {
165 | base64::decode(s2).ok().map(|data| SqliteValue::Blob(data))
166 | } else {
167 | None
168 | }
169 | _ => None
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/mod.ts:
--------------------------------------------------------------------------------
1 | import { bufferToBase64 } from "./bufferToBase64.js";
2 |
3 | const encoder = new TextEncoder();
4 | const decoder = new TextDecoder();
5 |
6 | /**
7 | * This class is intended to be a holder for configuration. At the moment though,
8 | * there is no configuration! It's just a factory for Connections.
9 | *
10 | * This class does store the IDs of the internal ops used by the library, but
11 | * that may change in the future. I'm leaving this class as part of the API for
12 | * now, but it may be removed at some point if it doesn't become useful. This
13 | * may depend on how Deno's plugin API evolves.
14 | */
15 | export class Sqlite {
16 | /** @ignore */
17 | _openConnection: number;
18 |
19 | /** @ignore */
20 | _execute: number;
21 |
22 | /** @ignore */
23 | _query: number;
24 |
25 | /** No need to pass any arguments yet. */
26 | constructor() {
27 | let ops = (Deno as any).core.ops();
28 | let tag = "tag:crabmusket.github.io,2020";
29 | this._openConnection = ops[tag + ":sqliteOpenConnection"];
30 | this._execute = ops[tag + ":sqliteExecute"];
31 | this._query = ops[tag + ":sqliteQuery"];
32 | if (!this._openConnection || !this._execute || !this._query) {
33 | throw new Error("could not find ops");
34 | }
35 | }
36 |
37 | /**
38 | * Open a connection to a database file. Supports the string ":memory:" for
39 | * creating temporary in-memory databases. There is no way to save in-memory
40 | * databases beyond the lifetime of the process.
41 | *
42 | * @param path Path (relative to the current working dir) to the database file.
43 | * @throws {Error} If the database cannot be opened, an exception will be thrown.
44 | */
45 | async connect(path: string): Promise {
46 | let response = jsonSyncOp(
47 | this._openConnection,
48 | { path },
49 | );
50 | if (response.error) {
51 | throw new Error(response.error);
52 | }
53 | if (response.connection_id === null) {
54 | throw new Error(
55 | "missing connection id when opening: " + path,
56 | );
57 | }
58 | return new Connection(this, path, response.connection_id);
59 | }
60 | }
61 |
62 | /**
63 | * SQLite knows only a few native types, represented here. All other types (dates,
64 | * booleans, etc.) must be encoded as one of these types.
65 | * https://www.sqlite.org/datatype3.html
66 | */
67 | export type Value = null | number | string | Blob;
68 |
69 | export type Values = Array;
70 |
71 | /**
72 | * Raw ArrayBuffers are used to transfer binary data. For example, if you have
73 | * a typed array like a Uint8Array, you can use `theArray.buffer` to get its
74 | * raw data for insertion into SQLite.
75 | */
76 | export type Blob = ArrayBuffer;
77 |
78 | type EncodedValue = null | number | string | EncodedBlob;
79 | type EncodedBlob = ["blob:base64", string];
80 | type EncodedValues = Array;
81 |
82 | /** Represents an open database. */
83 | export class Connection {
84 | /** @ignore */
85 | _sqlite: Sqlite;
86 | /** @ignore */
87 | _original_path: string;
88 | /** @ignore */
89 | _connection_id: number;
90 |
91 | /** @ignore */
92 | constructor(sqlite: Sqlite, path: string, id: number) {
93 | this._sqlite = sqlite;
94 | this._original_path = path;
95 | this._connection_id = id;
96 | }
97 |
98 | /**
99 | * Run a sqlite statement. Use ? in the statement string as a placeholder and
100 | * pass the values in the second argument.
101 | *
102 | * @return The number of rows affected by the statement.
103 | * @throws {Error} Errors will have a hopefully-helpful message.
104 | */
105 | async execute(statement: string, params: Values = []): Promise {
106 | let response = jsonSyncOp(
107 | this._sqlite._execute,
108 | {
109 | connection_id: this._connection_id,
110 | statement,
111 | params: encodeBlobs(params),
112 | },
113 | );
114 | if (response.error) {
115 | throw new Error(response.error);
116 | }
117 | return response.rows_affected;
118 | }
119 |
120 | /**
121 | * Perform a query to get data out.
122 | *
123 | * @return List of resulting rows that mathed the query. Note this is an array
124 | * of arrays.
125 | * @throws {Error} Throws if anything goes wrong.
126 | */
127 | async query(statement: string, params: Values = []): Promise {
128 | let response = jsonSyncOp(
129 | this._sqlite._query,
130 | {
131 | connection_id: this._connection_id,
132 | statement,
133 | params: encodeBlobs(params),
134 | },
135 | );
136 | if (response.error) {
137 | throw new Error(response.error);
138 | }
139 | return decodeBlobs(response.result);
140 | }
141 | }
142 |
143 | function encodeBlobs(params: Values): EncodedValues {
144 | return params.map((param) => {
145 | if (param instanceof ArrayBuffer) {
146 | return ["blob:base64", bufferToBase64(param)];
147 | }
148 | return param;
149 | });
150 | }
151 |
152 | function decodeBlobs(rows: EncodedValues[]): Values[] {
153 | return rows.map((row) => row.map(convertBlobs));
154 |
155 | function convertBlobs(col: EncodedValue): Value {
156 | if (col instanceof Array) {
157 | if (
158 | col.length === 2 &&
159 | col[0] === "blob:base64" &&
160 | typeof col[1] === "string"
161 | ) {
162 | return base64ToBuffer(col[1]);
163 | } else {
164 | throw new Error(
165 | "bad response from plugin; array which is not a blob",
166 | );
167 | }
168 | }
169 | return col;
170 | }
171 | }
172 |
173 | function base64ToBuffer(data: string): ArrayBuffer {
174 | let array = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
175 | return array.buffer;
176 | }
177 |
178 | type OpenConnectionRequest = {
179 | path: string;
180 | };
181 |
182 | type OpenConnectionResponse = {
183 | error: string | null;
184 | connection_id: number | null;
185 | };
186 |
187 | type ExecuteRequest = {
188 | connection_id: number;
189 | statement: string;
190 | params: EncodedValues;
191 | };
192 |
193 | type ExecuteResponse = {
194 | error: string;
195 | rows_affected: number;
196 | };
197 |
198 | type QueryRequest = {
199 | connection_id: number;
200 | statement: string;
201 | params: EncodedValues;
202 | };
203 |
204 | type QueryResponse = {
205 | error: string;
206 | result: EncodedValues[];
207 | };
208 |
209 | function jsonSyncOp(op: number, request: Req): Res {
210 | let encodedRequest = encoder.encode(JSON.stringify(request));
211 | let rawResponse = (Deno as any).core.dispatch(op, encodedRequest);
212 | if (!rawResponse) {
213 | throw new Error("plugin op returned null");
214 | }
215 | let responseObject = JSON.parse(decoder.decode(rawResponse)) as Res;
216 | return responseObject;
217 | }
218 |
--------------------------------------------------------------------------------
/src/prepared.ts:
--------------------------------------------------------------------------------
1 | import { prepare } from "https://deno.land/x/plugin_prepare/mod.ts";
2 |
3 | export * from "./mod.ts";
4 |
5 | const releaseUrl =
6 | "https://github.com/crabmusket/deno_sqlite_plugin/releases/download/v0.2";
7 |
8 | export const sqlitePlugin = await prepare({
9 | name: "deno_sqlite_plugin",
10 | urls: {
11 | linux: `${releaseUrl}/libdeno_sqlite_plugin.so`,
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/tests/_open_plugin.js:
--------------------------------------------------------------------------------
1 | const base = "deno_sqlite_plugin";
2 |
3 | let suffix = ".so";
4 | let prefix = "lib";
5 |
6 | if (Deno.build.os === "win") {
7 | suffix = ".dll";
8 | prefix = "";
9 | }
10 | if (Deno.build.os === "mac") {
11 | suffix = ".dylib";
12 | }
13 |
14 | const target = Deno.args[0] || "debug";
15 | const filename = `./target/${target}/${prefix}${base}${suffix}`;
16 |
17 | Deno.openPlugin(filename);
18 |
--------------------------------------------------------------------------------
/tests/e2e_test.js:
--------------------------------------------------------------------------------
1 | import { Sqlite } from "../src/mod.ts";
2 | import { assert, assertEquals } from "https://deno.land/std/testing/asserts.ts";
3 | import "./_open_plugin.js";
4 |
5 | Deno.test("[e2e] stuart", async function () {
6 | let sqlite = new Sqlite();
7 |
8 | let connection = await sqlite.connect(":memory:");
9 | await connection.execute(`CREATE TABLE IF NOT EXISTS person (
10 | id INTEGER PRIMARY KEY,
11 | name TEXT NOT NULL,
12 | height REAL NOT NULL,
13 | time_created TEXT NOT NULL
14 | )`);
15 |
16 | let now = new Date().toISOString();
17 | let rowsAffected = await connection.execute(
18 | `INSERT INTO person (name, height, time_created) VALUES (?, ?, ?)`,
19 | ["stuart", 12.4, now],
20 | );
21 | assertEquals(1, rowsAffected, "inserted 1 row");
22 |
23 | let result = await connection.query(
24 | `SELECT * FROM person WHERE name = ?`,
25 | ["stuart"],
26 | );
27 | assertEquals([
28 | [1, "stuart", 12.4, now],
29 | ], result);
30 | });
31 |
32 | Deno.test("[e2e] byte blob", async function () {
33 | let sqlite = new Sqlite();
34 |
35 | let connection = await sqlite.connect(":memory:");
36 | await connection.execute(`CREATE TABLE IF NOT EXISTS blobs (
37 | id INTEGER PRIMARY KEY,
38 | content BLOB
39 | )`);
40 |
41 | let bytes = new Uint8Array([0, 1, 2, 3, 4]);
42 | let rowsAffected = await connection.execute(
43 | `INSERT INTO blobs (content) VALUES (?)`,
44 | [bytes.buffer],
45 | );
46 | assertEquals(1, rowsAffected, "inserted 1 row");
47 |
48 | let result = await connection.query(`SELECT * FROM blobs`);
49 | assertEquals(1, result.length, "got 1 result");
50 | assertEquals(2, result[0].length, "result row has 2 cols");
51 | assertEquals(1, result[0][0], "row id is 1");
52 | let resultArray = new Uint8Array(result[0][1]);
53 | // make sure the result matches the input exactly
54 | assert(
55 | resultArray.every((val, i) => {
56 | return val === bytes[i];
57 | }),
58 | "values match input array",
59 | );
60 | });
61 |
62 | Deno.test("[e2e] float32 blob", async function () {
63 | let sqlite = new Sqlite();
64 |
65 | let connection = await sqlite.connect(":memory:");
66 | await connection.execute(`CREATE TABLE IF NOT EXISTS blobs (
67 | id INTEGER PRIMARY KEY,
68 | content BLOB
69 | )`);
70 |
71 | let floatArray = new Float32Array(5);
72 | floatArray.fill(42.42);
73 | let rowsAffected = await connection.execute(
74 | `INSERT INTO blobs (content) VALUES (?)`,
75 | [floatArray.buffer],
76 | );
77 | assertEquals(1, rowsAffected, "inserted 1 row");
78 |
79 | let result = await connection.query(`SELECT * FROM blobs`);
80 | assertEquals(1, result.length, "got 1 result");
81 | assertEquals(2, result[0].length, "result row has 2 cols");
82 | assertEquals(1, result[0][0], "row id is 1");
83 | let resultArray = new Float32Array(result[0][1]);
84 | const epsilon = 0.0001;
85 | assert(
86 | resultArray.every(matchFp(42.42, epsilon)),
87 | `values match to within ${epsilon}`,
88 | );
89 | });
90 |
91 | function matchFp(target, eps = 0.00001) {
92 | return function (value) {
93 | return Math.abs(target - value) < eps;
94 | };
95 | }
96 |
--------------------------------------------------------------------------------
/tests/ops_test.js:
--------------------------------------------------------------------------------
1 | import { assert } from "https://deno.land/std/testing/asserts.ts";
2 | import "./_open_plugin.js";
3 |
4 | Deno.test("[ops] expected ops exist", function () {
5 | const ops = Deno.core.ops();
6 | assert("tag:crabmusket.github.io,2020:sqliteOpenConnection" in ops);
7 | assert("tag:crabmusket.github.io,2020:sqliteExecute" in ops);
8 | assert("tag:crabmusket.github.io,2020:sqliteQuery" in ops);
9 | });
10 |
--------------------------------------------------------------------------------