├── .gitignore ├── LICENSE ├── README.md ├── dl-sqlite3 ├── main.c ├── project.janet ├── sqlite3.c ├── sqlite3.h └── test └── test.janet /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | test.db 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Calvin Rose 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SQLite bindings 2 | 3 | This native module proves sqlite bindings for janet. 4 | 5 | ## Install 6 | 7 | ``` 8 | jpm install sqlite3 9 | ``` 10 | 11 | ## Building 12 | 13 | To build, use the `jpm` tool and make sure you have janet installed. 14 | 15 | ``` 16 | jpm build 17 | ``` 18 | ## Using the System's SQLite library 19 | 20 | Usually, a Linux system has a package for SQLite installed globally. 21 | This packages will usually have many plugins for SQlite enabled (e.g 22 | JSON1, FTS3 etc.). The [Debian package][1] or [Gentoo ebuild][2] are good examples. 23 | 24 | This package allows one to use it instead of the SQLite sources included with the package. 25 | To do this use: 26 | 27 | ``` 28 | export JANET_SYSTEM_SQLITE=1 29 | jpm build 30 | ``` 31 | 32 | Note, if you intead to install the package globally, use: 33 | 34 | ``` 35 | sudo -E jpm build 36 | ``` 37 | 38 | ## Update the embedded SQLite version 39 | 40 | You can use the jpm rule to update the version of SQLite included. 41 | ``` 42 | jpm run update-sqlite3 43 | ``` 44 | 45 | You can find the latest version https://sqlite.org/index.html. 46 | 47 | ## Example Usage 48 | 49 | Next, enter the repl and create a database and a table. 50 | By default, the generated module will be in the build folder. 51 | 52 | ``` 53 | janet:1:> (import build/sqlite3 :as sql) 54 | nil 55 | janet:2:> (def db (sql/open "test.db")) 56 | 57 | janet:3:> (sql/eval db `CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);`) 58 | @[] 59 | janet:4:> (sql/eval db `INSERT INTO customers VALUES(:id, :name);` {:name "John" :id 12345}) 60 | @[] 61 | janet:5:> (sql/eval db `SELECT * FROM customers;`) 62 | @[{"id" 12345 "name" "John"}] 63 | ``` 64 | 65 | Load and use SQLite extensions. 66 | 67 | ``` 68 | janet:6:> (sql/allow-loading-extensions db) 69 | false 70 | janet:7:> (sql/load-extension db "/tmp/base64") 71 | error: not authorized 72 | in sqlite3/load-extension 73 | in _thunk [janet] (tailcall) on line 4, column 1 74 | janet:8:> (sql/allow-loading-extensions db true) 75 | true 76 | janet:9:> (sql/load-extension db "/tmp/base64") 77 | "/tmp/base64" 78 | janet:10:> (sql/eval db "select base64('YWJjMTIz') as b64") 79 | @[{:b64 @"abc123"}] 80 | ``` 81 | 82 | Finally, close the database connection when done with it. 83 | 84 | ``` 85 | janet:11:> (sql/close db) 86 | nil 87 | ``` 88 | 89 | [1]: https://git.launchpad.net/ubuntu/+source/sqlite3/tree/debian/rules?h=debian/sid#n41 90 | [2]: https://github.com/gentoo/gentoo/blob/653b190ffe5f4433112ad6786d1bfd2e26143711/dev-db/sqlite/sqlite-3.34.0.ebuild 91 | -------------------------------------------------------------------------------- /dl-sqlite3: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env janet 2 | 3 | # Update to latest version at https://www.sqlite.org/download.html 4 | (def version "3440200") 5 | 6 | (def unpacked (string "sqlite-amalgamation-" version)) 7 | (def zip-file (string "sqlite-amalgamation-" version ".zip")) 8 | (def url (string "https://www.sqlite.org/2023/" zip-file)) 9 | 10 | (defn run [& args] 11 | (os/execute args :px)) 12 | 13 | (run "wget" url) 14 | (run "unzip" "-o" zip-file) 15 | (run "cp" "-v" (string unpacked "/sqlite3.c") "./") 16 | (run "cp" "-v" (string unpacked "/sqlite3.h") "./") 17 | (run "rm" "-rfv" zip-file unpacked) 18 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 Calvin Rose 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifdef USE_SYSTEM_SQLITE 24 | #include 25 | #else 26 | #include "sqlite3.h" 27 | #endif 28 | #include 29 | 30 | #define FLAG_CLOSED 1 31 | 32 | #define MSG_DB_CLOSED "database already closed" 33 | 34 | typedef struct { 35 | sqlite3* handle; 36 | int flags; 37 | } Db; 38 | 39 | /* Close a db, noop if already closed */ 40 | static void closedb(Db *db) { 41 | if (!(db->flags & FLAG_CLOSED)) { 42 | db->flags |= FLAG_CLOSED; 43 | sqlite3_close_v2(db->handle); 44 | } 45 | } 46 | 47 | /* Called to garbage collect a sqlite3 connection */ 48 | static int gcsqlite(void *p, size_t s) { 49 | (void) s; 50 | Db *db = (Db *)p; 51 | closedb(db); 52 | return 0; 53 | } 54 | 55 | #if JANET_VERSION_MAJOR == 1 && JANET_VERSION_MINOR < 6 56 | static Janet sql_conn_get(void *p, Janet key); 57 | #else 58 | static int sql_conn_get(void *p, Janet key, Janet *out); 59 | #endif 60 | 61 | static const JanetAbstractType sql_conn_type = { 62 | "sqlite3.connection", 63 | gcsqlite, 64 | NULL, 65 | sql_conn_get, 66 | #ifdef JANET_ATEND_GET 67 | JANET_ATEND_GET 68 | #endif 69 | }; 70 | 71 | /* Open a new database connection */ 72 | static Janet sql_open(int32_t argc, Janet *argv) { 73 | janet_fixarity(argc, 1); 74 | const uint8_t *filename = janet_getstring(argv, 0); 75 | sqlite3 *conn; 76 | int status = sqlite3_open((const char *)filename, &conn); 77 | if (status != SQLITE_OK) janet_panic(sqlite3_errmsg(conn)); 78 | Db *db = (Db *) janet_abstract(&sql_conn_type, sizeof(Db)); 79 | db->handle = conn; 80 | db->flags = 0; 81 | return janet_wrap_abstract(db); 82 | } 83 | 84 | /* Close a database connection */ 85 | static Janet sql_close(int32_t argc, Janet *argv) { 86 | janet_fixarity(argc, 1); 87 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 88 | closedb(db); 89 | return janet_wrap_nil(); 90 | } 91 | 92 | /* Check for embedded NULL bytes */ 93 | static int has_null(const uint8_t *str, int32_t len) { 94 | while (len--) { 95 | if (!str[len]) 96 | return 1; 97 | } 98 | return 0; 99 | } 100 | 101 | /* Bind a single parameter */ 102 | static const char *bind1(sqlite3_stmt *stmt, int index, Janet value) { 103 | int res; 104 | switch (janet_type(value)) { 105 | default: 106 | return "invalid sql value"; 107 | case JANET_NIL: 108 | res = sqlite3_bind_null(stmt, index); 109 | break; 110 | case JANET_BOOLEAN: 111 | res = sqlite3_bind_int(stmt, index, janet_unwrap_boolean(value)); 112 | break; 113 | case JANET_NUMBER: 114 | res = sqlite3_bind_double(stmt, index, janet_unwrap_number(value)); 115 | break; 116 | case JANET_STRING: 117 | case JANET_SYMBOL: 118 | case JANET_KEYWORD: 119 | { 120 | const uint8_t *str = janet_unwrap_string(value); 121 | int32_t len = janet_string_length(str); 122 | if (has_null(str, len)) { 123 | return "cannot have embedded nulls in text values"; 124 | } else { 125 | res = sqlite3_bind_text(stmt, index, (const char *)str, len, SQLITE_STATIC); 126 | } 127 | } 128 | break; 129 | case JANET_BUFFER: 130 | { 131 | JanetBuffer *buffer = janet_unwrap_buffer(value); 132 | res = sqlite3_bind_blob(stmt, index, buffer->data, buffer->count, SQLITE_STATIC); 133 | } 134 | break; 135 | } 136 | if (res != SQLITE_OK) { 137 | sqlite3 *db = sqlite3_db_handle(stmt); 138 | return sqlite3_errmsg(db); 139 | } 140 | return NULL; 141 | } 142 | 143 | /* Bind many parameters */ 144 | static const char *bindmany(sqlite3_stmt *stmt, Janet params) { 145 | /* parameters */ 146 | const Janet *seq; 147 | const JanetKV *kvs; 148 | int32_t len, cap; 149 | int limitindex = sqlite3_bind_parameter_count(stmt); 150 | if (janet_indexed_view(params, &seq, &len)) { 151 | if (len > limitindex + 1) { 152 | return "invalid index in sql parameters"; 153 | } 154 | for (int i = 0; i < len; i++) { 155 | const char *err = bind1(stmt, i + 1, seq[i]); 156 | if (err) { 157 | return err; 158 | } 159 | } 160 | } else if (janet_dictionary_view(params, &kvs, &len, &cap)) { 161 | for (int i = 0; i < cap; i++) { 162 | int index = 0; 163 | switch (janet_type(kvs[i].key)) { 164 | default: 165 | /* Will fail */ 166 | break; 167 | case JANET_NIL: 168 | /* Will skip as nil keys indicate empty hash table slot */ 169 | continue; 170 | case JANET_NUMBER: 171 | if (!janet_checkint(kvs[i].key)) break; 172 | index = janet_unwrap_integer(kvs[i].key); 173 | break; 174 | case JANET_KEYWORD: 175 | { 176 | char *kw = (char *)janet_unwrap_keyword(kvs[i].key); 177 | /* Quick hack for keywords */ 178 | char old = kw[-1]; 179 | kw[-1] = ':'; 180 | index = sqlite3_bind_parameter_index(stmt, kw - 1); 181 | kw[-1] = old; 182 | } 183 | break; 184 | case JANET_STRING: 185 | case JANET_SYMBOL: 186 | { 187 | const uint8_t *s = janet_unwrap_string(kvs[i].key); 188 | index = sqlite3_bind_parameter_index( 189 | stmt, 190 | (const char *)s); 191 | } 192 | break; 193 | } 194 | if (index <= 0 || index > limitindex) { 195 | return "invalid index in sql parameters"; 196 | } 197 | const char *err = bind1(stmt, index, kvs[i].value); 198 | if (err) { 199 | return err; 200 | } 201 | } 202 | } else { 203 | return "invalid type for sql parameters"; 204 | } 205 | return NULL; 206 | } 207 | 208 | /* Execute a statement but don't collect results */ 209 | static const char *execute(sqlite3_stmt *stmt) { 210 | int status; 211 | const char *ret = NULL; 212 | do { 213 | status = sqlite3_step(stmt); 214 | } while (status == SQLITE_ROW); 215 | /* Check for errors */ 216 | if (status != SQLITE_DONE) { 217 | sqlite3 *db = sqlite3_db_handle(stmt); 218 | ret = sqlite3_errmsg(db); 219 | } 220 | return ret; 221 | } 222 | 223 | /* Execute and return values from prepared statement */ 224 | static const char *execute_collect(sqlite3_stmt *stmt, JanetArray *rows) { 225 | /* Count number of columns in result */ 226 | int ncol = sqlite3_column_count(stmt); 227 | int status; 228 | const char *ret = NULL; 229 | 230 | /* Get column names */ 231 | Janet *tupstart = janet_tuple_begin(ncol); 232 | for (int i = 0; i < ncol; i++) { 233 | tupstart[i] = janet_ckeywordv(sqlite3_column_name(stmt, i)); 234 | } 235 | const Janet *colnames = janet_tuple_end(tupstart); 236 | 237 | do { 238 | status = sqlite3_step(stmt); 239 | if (status == SQLITE_ROW) { 240 | JanetKV *row = janet_struct_begin(ncol); 241 | for (int i = 0; i < ncol; i++) { 242 | int t = sqlite3_column_type(stmt, i); 243 | Janet value; 244 | switch (t) { 245 | case SQLITE_NULL: 246 | value = janet_wrap_nil(); 247 | break; 248 | case SQLITE_INTEGER: 249 | value = janet_wrap_integer(sqlite3_column_int(stmt, i)); 250 | break; 251 | case SQLITE_FLOAT: 252 | value = janet_wrap_number(sqlite3_column_double(stmt, i)); 253 | break; 254 | case SQLITE_TEXT: 255 | { 256 | int nbytes = sqlite3_column_bytes(stmt, i); 257 | uint8_t *str = janet_string_begin(nbytes); 258 | memcpy(str, sqlite3_column_text(stmt, i), nbytes); 259 | value = janet_wrap_string(janet_string_end(str)); 260 | } 261 | break; 262 | case SQLITE_BLOB: 263 | { 264 | int nbytes = sqlite3_column_bytes(stmt, i); 265 | JanetBuffer *b = janet_buffer(nbytes); 266 | memcpy(b->data, sqlite3_column_blob(stmt, i), nbytes); 267 | b->count = nbytes; 268 | value = janet_wrap_buffer(b); 269 | } 270 | break; 271 | } 272 | janet_struct_put(row, colnames[i], value); 273 | } 274 | janet_array_push(rows, janet_wrap_struct(janet_struct_end(row))); 275 | } 276 | } while (status == SQLITE_ROW); 277 | 278 | /* Check for errors */ 279 | if (status != SQLITE_DONE) { 280 | sqlite3 *db = sqlite3_db_handle(stmt); 281 | ret = sqlite3_errmsg(db); 282 | } 283 | return ret; 284 | } 285 | 286 | /* Evaluate a string of sql */ 287 | static Janet sql_eval(int32_t argc, Janet *argv) { 288 | janet_arity(argc, 2, 3); 289 | const char *err; 290 | sqlite3_stmt *stmt = NULL, *stmt_next = NULL; 291 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 292 | if (db->flags & FLAG_CLOSED) janet_panic(MSG_DB_CLOSED); 293 | const uint8_t *query = janet_getstring(argv, 1); 294 | if (has_null(query, janet_string_length(query))) { 295 | err = "cannot have embedded NULL in sql statememts"; 296 | goto error; 297 | } 298 | JanetArray *rows = janet_array(10); 299 | const char *c = (const char *)query; 300 | 301 | /* Evaluate all statements in a loop */ 302 | do { 303 | /* Compile the next statement */ 304 | if (sqlite3_prepare_v2(db->handle, c, -1, &stmt_next, &c) != SQLITE_OK) { 305 | err = sqlite3_errmsg(db->handle); 306 | goto error; 307 | } 308 | /* Check if we have found last statement */ 309 | if (NULL == stmt_next) { 310 | /* Execute current statement and collect results */ 311 | if (stmt) { 312 | err = execute_collect(stmt, rows); 313 | if (err) goto error; 314 | } 315 | } else { 316 | /* Execute current statement but don't collect results. */ 317 | if (stmt) { 318 | err = execute(stmt); 319 | if (err) goto error; 320 | } 321 | /* Bind params to next statement*/ 322 | if (argc == 3) { 323 | /* parameters */ 324 | err = bindmany(stmt_next, argv[2]); 325 | if (err) goto error; 326 | } 327 | } 328 | /* rotate stmt and stmt_next */ 329 | if (stmt) sqlite3_finalize(stmt); 330 | stmt = stmt_next; 331 | stmt_next = NULL; 332 | } while (NULL != stmt); 333 | 334 | /* Good return path */ 335 | return janet_wrap_array(rows); 336 | 337 | error: 338 | if (stmt) sqlite3_finalize(stmt); 339 | if (stmt_next) sqlite3_finalize(stmt_next); 340 | janet_panic(err); 341 | return janet_wrap_nil(); 342 | } 343 | 344 | /* Gets the last inserted row id */ 345 | static Janet sql_last_insert_rowid(int32_t argc, Janet *argv) { 346 | janet_fixarity(argc, 1); 347 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 348 | if (db->flags & FLAG_CLOSED) janet_panic(MSG_DB_CLOSED); 349 | sqlite3_int64 id = sqlite3_last_insert_rowid(db->handle); 350 | return janet_wrap_number((double) id); 351 | } 352 | 353 | /* Get the sqlite3 errcode */ 354 | static Janet sql_error_code(int32_t argc, Janet *argv) { 355 | janet_fixarity(argc, 1); 356 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 357 | if (db->flags & FLAG_CLOSED) janet_panic(MSG_DB_CLOSED); 358 | int errcode = sqlite3_errcode(db->handle); 359 | return janet_wrap_integer(errcode); 360 | } 361 | 362 | /* Toggle or report whether extension loading is allowed */ 363 | static Janet sql_allow_loading_extensions(int32_t argc, Janet *argv) { 364 | janet_arity(argc, 1, 2); 365 | const char *err; 366 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 367 | if (db->flags & FLAG_CLOSED) janet_panic(MSG_DB_CLOSED); 368 | int enable_loading = janet_optboolean(argv, argc, 1, -1); 369 | int setting; 370 | int status = sqlite3_db_config(db->handle, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable_loading, &setting); 371 | if (status != SQLITE_OK) { 372 | err = sqlite3_errmsg(db->handle); 373 | janet_panic(err); 374 | } 375 | return janet_wrap_boolean(setting); 376 | } 377 | 378 | /* Load extension */ 379 | static Janet sql_load_extension(int32_t argc, Janet *argv) { 380 | janet_arity(argc, 2, 3); 381 | Db *db = janet_getabstract(argv, 0, &sql_conn_type); 382 | if (db->flags & FLAG_CLOSED) janet_panic(MSG_DB_CLOSED); 383 | const char *zFile = janet_getcstring(argv, 1); 384 | const char *zProc = janet_optcstring(argv, argc, 2, NULL); 385 | char *pzErrMsg; 386 | int status = sqlite3_load_extension(db->handle, zFile, zProc, &pzErrMsg); 387 | if (status != SQLITE_OK) { 388 | size_t n = strlen(pzErrMsg); 389 | void *jErrMsg = janet_smalloc(n); 390 | memcpy(jErrMsg, pzErrMsg, n); 391 | sqlite3_free(pzErrMsg); 392 | janet_panic(jErrMsg); 393 | } 394 | return janet_wrap_string(zFile); 395 | } 396 | 397 | static JanetMethod conn_methods[] = { 398 | {"error-code", sql_error_code}, 399 | {"close", sql_close}, 400 | {"eval", sql_eval}, 401 | {"last-insert-rowid", sql_last_insert_rowid}, 402 | {"allow-loading-extensions", sql_allow_loading_extensions}, 403 | {"load-extension", sql_load_extension}, 404 | {NULL, NULL} 405 | }; 406 | 407 | #if JANET_VERSION_MAJOR == 1 && JANET_VERSION_MINOR < 6 408 | static Janet sql_conn_get(void *p, Janet key) { 409 | (void) p; 410 | if (!janet_checktype(key, JANET_KEYWORD)) { 411 | janet_panicf("expected keyword, get %v", key); 412 | } 413 | return janet_getmethod(janet_unwrap_keyword(key), conn_methods); 414 | } 415 | #else 416 | static int sql_conn_get(void *p, Janet key, Janet *out) { 417 | (void) p; 418 | if (!janet_checktype(key, JANET_KEYWORD)) { 419 | janet_panicf("expected keyword, get %v", key); 420 | } 421 | return janet_getmethod(janet_unwrap_keyword(key), conn_methods, out); 422 | } 423 | #endif 424 | 425 | /*****************************************************************************/ 426 | 427 | static const JanetReg cfuns[] = { 428 | {"open", sql_open, 429 | "(sqlite3/open path)\n\n" 430 | "Opens a sqlite3 database on disk or in-memory when passed \":memory:\" as path. Returns the database handle if the database was opened " 431 | "successfully, and otherwise throws an error." 432 | }, 433 | {"close", sql_close, 434 | "(sqlite3/close db)\n\n" 435 | "Closes a database. Use this to free a database after use. Returns nil." 436 | }, 437 | {"eval", sql_eval, 438 | "(sqlite3/eval db sql [,params])\n\n" 439 | "Evaluate sql in the context of database db. Multiple sql statements " 440 | "can be chained together, and optionally parameters maybe passed in. " 441 | "The optional parameters maybe either an indexed data type (tuple or array), or a dictionary " 442 | "data type (struct or table). If params is a tuple or array, then sqlite " 443 | "parameters are substituted using indices. For example:\n\n" 444 | "\t(sqlite3/eval db `SELECT * FROM tab WHERE id = ?;` [123])\n\n" 445 | "Will select rows from tab where id is equal to 123. Alternatively, " 446 | "the programmer can use named parameters with tables or structs, like so:\n\n" 447 | "\t(sqlite3/eval db `SELECT * FROM tab WHERE id = :id;` {:id 123})\n\n" 448 | "Will return an array of rows, where each row contains a table where columns names " 449 | "are keys for column values." 450 | }, 451 | {"last-insert-rowid", sql_last_insert_rowid, 452 | "(sqlite3/last-insert-rowid db)\n\n" 453 | "Returns the id of the last inserted row." 454 | }, 455 | {"error-code", sql_error_code, 456 | "(sqlite3/error-code db)\n\n" 457 | "Returns the error number of the last sqlite3 command that threw an error. Cross " 458 | "check these numbers with the SQLite documentation for more information." 459 | }, 460 | {"allow-loading-extensions", sql_allow_loading_extensions, 461 | "(sqlite3/allow-loading-extensions db [boolean-param])\n\n" 462 | "Reports or toggles the setting to load SQLite extensions according to presence and value " 463 | "of boolean-param. Returns a boolean value indicating whether extension loading is allowed." 464 | }, 465 | {"load-extension", sql_load_extension, 466 | "(sqlite3/load-extension db library-file-path [library-entrypoint])\n\n" 467 | "Loads the SQLite extension library from library-file-path, optionally specifying " 468 | "the library-entrypoint. Extension loading must be enabled prior to calling this function. " 469 | "Returns library-file-path." 470 | }, 471 | {NULL, NULL, NULL} 472 | }; 473 | 474 | JANET_MODULE_ENTRY(JanetTable *env) { 475 | janet_cfuns(env, "sqlite3", cfuns); 476 | } 477 | -------------------------------------------------------------------------------- /project.janet: -------------------------------------------------------------------------------- 1 | (declare-project 2 | :name "sqlite3" 3 | :description "Janet bindings to SQLite." 4 | :author "Calvin Rose" 5 | :license "MIT" 6 | :url "https://github.com/janet-lang/sqlite3" 7 | :repo "git+https://github.com/janet-lang/sqlite3.git") 8 | 9 | 10 | (def use-system-lib (= "1" (os/getenv "JANET_SYSTEM_SQLITE" 0))) 11 | 12 | (defn pkg-config [what &opt env] 13 | (default env {}) 14 | (def p (os/spawn ["pkg-config" ;what] :pe (merge {:out :pipe} env))) 15 | (:wait p) 16 | (unless (zero? (p :return-code)) 17 | (error "pkg-config failed!")) 18 | (def v (->> 19 | (:read (p :out) :all) 20 | (string/trim) 21 | (string/split " "))) 22 | v) 23 | 24 | (if use-system-lib 25 | (declare-native 26 | :name "sqlite3" 27 | :cflags (pkg-config ["sqlite3" "--cflags"] 28 | {"PKG_CONFIG_ALLOW_SYSTEM_CFLAGS" "1"}) 29 | :lflags (pkg-config ["sqlite3" "--libs"]) 30 | :source @["main.c"] 31 | :defines {"USE_SYSTEM_SQLITE" use-system-lib}) 32 | (declare-native 33 | :name "sqlite3" 34 | :source @["sqlite3.c" "main.c"]) 35 | ) 36 | 37 | (sh-phony "update-sqlite3" [] 38 | (print "updating sqlite3 local libs ...") 39 | (os/shell "janet dl-sqlite3")) 40 | -------------------------------------------------------------------------------- /test/test.janet: -------------------------------------------------------------------------------- 1 | (import /build/sqlite3 :as sql) 2 | 3 | # 4 | # Testing 5 | # 6 | 7 | (defn assert [c] 8 | (if (not c) (error "failed assertion"))) 9 | (def db (sql/open "build/test.db")) 10 | (try (sql/eval db `DROP TABLE people`) ([_])) 11 | (sql/eval db `CREATE TABLE people(name TEXT, age INTEGER, bool INTEGER);`) 12 | (sql/eval db `INSERT INTO people values(:name, :age, :bool)` {:name "John" :age 20 :bool false}) 13 | (sql/eval db `INSERT INTO people values(:name, :age, :bool)` {:name "Paul" :age 30 :bool true}) 14 | (sql/eval db `INSERT INTO people values(:name, :age, :bool)` {:name "Bob" :age 40 :bool false}) 15 | (sql/eval db `INSERT INTO people values(:name, :age, :bool)` {:name "Joe" :age 50 :bool true}) 16 | (def results (sql/eval db `SELECT * FROM people`)) 17 | (assert (= (length results) 4)) 18 | 19 | (def update-result 20 | (-> (sql/eval db 21 | `UPDATE people set name = :new_name where name = :old_name RETURNING name, age, bool` 22 | {:new_name "Harry" :old_name "Paul"}) 23 | (first))) 24 | (assert (= update-result {:name "Harry" :age 30 :bool 1})) 25 | 26 | 27 | (sql/close db) 28 | --------------------------------------------------------------------------------