From a7933f5f0897f42787106ca6bb2998355d3a1ef0 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 25 Dec 2018 17:33:35 -0500 Subject: [PATCH] Move janet natives to new repos. --- doc/SQLite.md | 31 -- natives/json/build.janet | 5 - natives/json/json.c | 605 ------------------------------------- natives/sqlite3/.gitignore | 62 ---- natives/sqlite3/Makefile | 60 ---- natives/sqlite3/main.c | 428 -------------------------- 6 files changed, 1191 deletions(-) delete mode 100644 doc/SQLite.md delete mode 100644 natives/json/build.janet delete mode 100644 natives/json/json.c delete mode 100644 natives/sqlite3/.gitignore delete mode 100644 natives/sqlite3/Makefile delete mode 100644 natives/sqlite3/main.c diff --git a/doc/SQLite.md b/doc/SQLite.md deleted file mode 100644 index 00b68359..00000000 --- a/doc/SQLite.md +++ /dev/null @@ -1,31 +0,0 @@ -# SQLite bindings - -There are some sqlite3 bindings in the directory natives/sqlite3 bundled with -the janet source code. They serve mostly as a -proof of concept external c library. To use, first compile the module with Make. - -```sh -make natives -``` - -Next, enter the repl and create a database and a table. - -``` -janet:1:> (import natives/sqlite3 :as sql) -nil -janet:2:> (def db (sql/open "test.db")) - -janet:3:> (sql/eval db `CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT);`) -@[] -janet:4:> (sql/eval db `INSERT INTO customers VALUES(:id, :name);` {:name "John" :id 12345}) -@[] -janet:5:> (sql/eval db `SELECT * FROM customers;`) -@[{"id" 12345 "name" "John"}] -``` - -Finally, close the database connection when done with it. - -``` -janet:6:> (sql/close db) -nil -``` diff --git a/natives/json/build.janet b/natives/json/build.janet deleted file mode 100644 index b5987342..00000000 --- a/natives/json/build.janet +++ /dev/null @@ -1,5 +0,0 @@ -(import cook) - -(cook/make-native - :name "json" - :source ["json.c"]) diff --git a/natives/json/json.c b/natives/json/json.c deleted file mode 100644 index c49f2db7..00000000 --- a/natives/json/json.c +++ /dev/null @@ -1,605 +0,0 @@ -/* -* Copyright (c) 2018 Calvin Rose -* -* 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: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* 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. -*/ - -#include -#include -#include - -/*****************/ -/* JSON Decoding */ -/*****************/ - -/* Check if a character is whitespace */ -static int white(uint8_t c) { - return c == '\t' || c == '\n' || c == ' ' || c == '\r'; -} - -/* Skip whitespace */ -static void skipwhite(const char **p) { - const char *cp = *p; - for (;;) { - if (white(*cp)) - cp++; - else - break; - } - *p = cp; -} - -/* Get a hex digit value */ -static int hexdig(char dig) { - if (dig >= '0' && dig <= '9') - return dig - '0'; - if (dig >= 'a' && dig <= 'f') - return 10 + dig - 'a'; - if (dig >= 'A' && dig <= 'F') - return 10 + dig - 'A'; - return -1; -} - -/* Read the hex value for a unicode escape */ -static const char *decode_utf16_escape(const char *p, uint32_t *outpoint) { - if (!p[0] || !p[1] || !p[2] || !p[3]) - return "unexpected end of source"; - int d1 = hexdig(p[0]); - int d2 = hexdig(p[1]); - int d3 = hexdig(p[2]); - int d4 = hexdig(p[3]); - if (d1 < 0 || d2 < 0 || d3 < 0 || d4 < 0) - return "invalid hex digit"; - *outpoint = d4 | (d3 << 4) | (d2 << 8) | (d1 << 12); - return NULL; -} - -/* Parse a string. Also handles the conversion of utf-16 to - * utf-8. */ -static const char *decode_string(const char **p, Janet *out) { - JanetBuffer *buffer = janet_buffer(0); - const char *cp = *p; - while (*cp != '"') { - uint8_t b = (uint8_t) *cp; - if (b < 32) return "invalid character in string"; - if (b == '\\') { - cp++; - switch(*cp) { - default: - return "unknown string escape"; - case 'b': - b = '\b'; - break; - case 'f': - b = '\f'; - break; - case 'n': - b = '\n'; - break; - case 'r': - b = '\r'; - break; - case 't': - b = '\t'; - break; - case '"': - b = '"'; - break; - case '\\': - b = '\\'; - break; - case 'u': - { - /* Get codepoint and check for surrogate pair */ - uint32_t codepoint; - const char *err = decode_utf16_escape(cp + 1, &codepoint); - if (err) return err; - if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) { - return "unexpected utf-16 low surrogate"; - } else if (codepoint >= 0xD800 && codepoint <= 0xDBFF) { - if (cp[5] != '\\') return "expected utf-16 low surrogate pair"; - if (cp[6] != 'u') return "expected utf-16 low surrogate pair"; - uint32_t lowsur; - const char *err = decode_utf16_escape(cp + 7, &lowsur); - if (err) return err; - if (lowsur < 0xDC00 || lowsur > 0xDFFF) - return "expected utf-16 low surrogate pair"; - codepoint = ((codepoint - 0xD800) << 10) + - (lowsur - 0xDC00) + 0x10000; - cp += 11; - } else { - cp += 5; - } - /* Write codepoint */ - if (codepoint <= 0x7F) { - janet_buffer_push_u8(buffer, codepoint); - } else if (codepoint <= 0x7FF) { - janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x1F) | 0xC0); - janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); - } else if (codepoint <= 0xFFFF) { - janet_buffer_push_u8(buffer, ((codepoint >> 12) & 0x0F) | 0xE0); - janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80); - janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); - } else { - janet_buffer_push_u8(buffer, ((codepoint >> 18) & 0x07) | 0xF0); - janet_buffer_push_u8(buffer, ((codepoint >> 12) & 0x3F) | 0x80); - janet_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80); - janet_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80); - } - } - continue; - } - } - janet_buffer_push_u8(buffer, b); - cp++; - } - *out = janet_stringv(buffer->data, buffer->count); - *p = cp + 1; - return NULL; -} - -static const char *decode_one(const char **p, Janet *out, int depth) { - - /* Prevent stack overflow */ - if (depth > JANET_RECURSION_GUARD) goto recurdepth; - - /* Skip leading whitepspace */ - skipwhite(p); - - /* Main switch */ - switch (**p) { - default: - goto badchar; - case '\0': - goto eos; - /* Numbers */ - case '-': case '0': case '1' : case '2': case '3' : case '4': - case '5': case '6': case '7' : case '8': case '9': - { - errno = 0; - char *end = NULL; - double x = strtod(*p, &end); - if (end == *p) goto badnum; - *p = end; - *out = janet_wrap_real(x); - break; - } - /* false, null, true */ - case 'f': - { - const char *cp = *p; - if (cp[1] != 'a' || cp[2] != 'l' || cp[3] != 's' || cp[4] != 'e') - goto badident; - *out = janet_wrap_false(); - *p = cp + 5; - break; - } - case 'n': - { - const char *cp = *p; - - if (cp[1] != 'u' || cp[2] != 'l' || cp[3] != 'l') - goto badident; - *out = janet_csymbolv(":null"); - *p = cp + 4; - break; - } - case 't': - { - const char *cp = *p; - if (cp[1] != 'r' || cp[2] != 'u' || cp[3] != 'e') - goto badident; - *out = janet_wrap_true(); - *p = cp + 4; - break; - } - /* String */ - case '"': - { - const char *cp = *p + 1; - const char *start = cp; - while (*cp >= 32 && *cp != '"' && *cp != '\\') - cp++; - /* Only use a buffer for strings with escapes, else just copy - * memory from source */ - if (*cp == '\\') { - *p = *p + 1; - const char *err = decode_string(p, out); - if (err) return err; - break; - } - if (*cp != '"') goto badchar; - *p = cp + 1; - *out = janet_stringv((const uint8_t *)start, cp - start); - break; - } - /* Array */ - case '[': - { - *p = *p + 1; - JanetArray *array = janet_array(0); - const char *err; - Janet subval; - skipwhite(p); - while (**p != ']') { - err = decode_one(p, &subval, depth + 1); - if (err) return err; - janet_array_push(array, subval); - skipwhite(p); - if (**p == ']') break; - if (**p != ',') goto wantcomma; - *p = *p + 1; - } - *p = *p + 1; - *out = janet_wrap_array(array); - } - break; - /* Object */ - case '{': - { - *p = *p + 1; - JanetTable *table = janet_table(0); - const char *err; - Janet subkey, subval; - skipwhite(p); - while (**p != '}') { - skipwhite(p); - if (**p != '"') goto wantstring; - err = decode_one(p, &subkey, depth + 1); - if (err) return err; - skipwhite(p); - if (**p != ':') goto wantcolon; - *p = *p + 1; - err = decode_one(p, &subval, depth + 1); - if (err) return err; - janet_table_put(table, subkey, subval); - skipwhite(p); - if (**p == '}') break; - if (**p != ',') goto wantcomma; - *p = *p + 1; - } - *p = *p + 1; - *out = janet_wrap_table(table); - break; - } - } - - /* Good return */ - return NULL; - - /* Errors */ -recurdepth: - return "recured too deeply"; -eos: - return "unexpected end of source"; -badident: - return "bad identifier"; -badnum: - return "bad number"; -wantcomma: - return "expected comma"; -wantcolon: - return "expected colon"; -badchar: - return "unexpected character"; -wantstring: - return "expected json string"; -} - -static int json_decode(JanetArgs args) { - Janet ret; - JANET_FIXARITY(args, 1); - const char *err; - const char *start; - const char *p; - if (janet_checktype(args.v[0], JANET_BUFFER)) { - JanetBuffer *buffer = janet_unwrap_buffer(args.v[0]); - /* Ensure 0 padded */ - janet_buffer_push_u8(buffer, 0); - start = p = (const char *)buffer->data; - err = decode_one(&p, &ret, 0); - buffer->count--; - } else { - const uint8_t *bytes; - int32_t len; - JANET_ARG_BYTES(bytes, len, args, 0); - start = p = (const char *)bytes; - err = decode_one(&p, &ret, 0); - } - /* Check trailing values */ - if (!err) { - skipwhite(&p); - if (*p) - err = "unexpected extra token"; - } - if (err) { - JANET_THROWV(args, janet_wrap_string(janet_formatc( - "decode error at postion %d: %s", - p - start, - err))); - } - JANET_RETURN(args, ret); -} - -/*****************/ -/* JSON Encoding */ -/*****************/ - -typedef struct { - JanetBuffer *buffer; - int32_t indent; - const uint8_t *tab; - const uint8_t *newline; - int32_t tablen; - int32_t newlinelen; -} Encoder; - -static const char *encode_newline(Encoder *e) { - if (janet_buffer_push_bytes(e->buffer, e->newline, e->newlinelen)) - return "buffer overflow"; - /* Skip loop if no tab string */ - if (e->tablen) { - for (int32_t i = 0; i < e->indent; i++) - if (janet_buffer_push_bytes(e->buffer, e->tab, e->tablen)) - return "buffer overflow"; - } - return NULL; -} - -static const char *encode_one(Encoder *e, Janet x, int depth) { - switch(janet_type(x)) { - default: - goto badtype; - case JANET_NIL: - { - if (janet_buffer_push_cstring(e->buffer, "null")) - goto overflow; - } - break; - case JANET_FALSE: - { - if (janet_buffer_push_cstring(e->buffer, "false")) - goto overflow; - } - break; - case JANET_TRUE: - { - if (janet_buffer_push_cstring(e->buffer, "true")) - goto overflow; - } - break; - case JANET_INTEGER: - { - char cbuf[20]; - sprintf(cbuf, "%d", janet_unwrap_integer(x)); - if (janet_buffer_push_cstring(e->buffer, cbuf)) - goto overflow; - } - break; - case JANET_REAL: - { - char cbuf[25]; - sprintf(cbuf, "%.17g", janet_unwrap_real(x)); - if (janet_buffer_push_cstring(e->buffer, cbuf)) - goto overflow; - } - break; - case JANET_STRING: - case JANET_SYMBOL: - case JANET_BUFFER: - { - const uint8_t *bytes; - const uint8_t *c; - const uint8_t *end; - int32_t len; - janet_bytes_view(x, &bytes, &len); - if (janet_buffer_push_u8(e->buffer, '"')) goto overflow; - c = bytes; - end = bytes + len; - while (c < end) { - - /* get codepoint */ - uint32_t codepoint; - if (*c < 0x80) { - /* one byte */ - codepoint = *c++; - } else if (*c < 0xE0) { - /* two bytes */ - if (c + 2 > end) goto overflow; - codepoint = ((c[0] & 0x1F) << 6) | - (c[1] & 0x3F); - c += 2; - } else if (*c < 0xF0) { - /* three bytes */ - if (c + 3 > end) goto overflow; - codepoint = ((c[0] & 0x0F) << 12) | - ((c[1] & 0x3F) << 6) | - (c[2] & 0x3F); - c += 3; - } else if (*c < 0xF8) { - /* four bytes */ - if (c + 4 > end) goto overflow; - codepoint = ((c[0] & 0x07) << 18) | - ((c[1] & 0x3F) << 12) | - ((c[3] & 0x3F) << 6) | - (c[3] & 0x3F); - c += 4; - } else { - /* invalid */ - goto invalidutf8; - } - - /* write codepoint */ - if (codepoint > 0x1F && codepoint < 0x80) { - /* Normal, no escape */ - if (codepoint == '\\' || codepoint == '"') - if (janet_buffer_push_u8(e->buffer, '\\')) - goto overflow; - if (janet_buffer_push_u8(e->buffer, (uint8_t) codepoint)) - goto overflow; - } else if (codepoint < 0x10000) { - /* One unicode escape */ - uint8_t buf[6]; - buf[0] = '\\'; - buf[1] = 'u'; - buf[2] = (codepoint >> 12) & 0xF; - buf[3] = (codepoint >> 8) & 0xF; - buf[4] = (codepoint >> 4) & 0xF; - buf[5] = codepoint & 0xF; - if (janet_buffer_push_bytes(e->buffer, buf, sizeof(buf))) - goto overflow; - } else { - /* Two unicode escapes (surrogate pair) */ - uint32_t hi, lo; - uint8_t buf[12]; - hi = ((codepoint - 0x10000) >> 10) + 0xD800; - lo = ((codepoint - 0x10000) & 0x3FF) + 0xDC00; - buf[0] = '\\'; - buf[1] = 'u'; - buf[2] = (hi >> 12) & 0xF; - buf[3] = (hi >> 8) & 0xF; - buf[4] = (hi >> 4) & 0xF; - buf[5] = hi & 0xF; - buf[6] = '\\'; - buf[7] = 'u'; - buf[8] = (lo >> 12) & 0xF; - buf[9] = (lo >> 8) & 0xF; - buf[10] = (lo >> 4) & 0xF; - buf[11] = lo & 0xF; - if (janet_buffer_push_bytes(e->buffer, buf, sizeof(buf))) - goto overflow; - } - } - if (janet_buffer_push_u8(e->buffer, '"')) goto overflow; - } - break; - case JANET_TUPLE: - case JANET_ARRAY: - { - const char *err; - const Janet *items; - int32_t len; - janet_indexed_view(x, &items, &len); - if (janet_buffer_push_u8(e->buffer, '[')) goto overflow; - e->indent++; - for (int32_t i = 0; i < len; i++) { - if ((err = encode_newline(e))) return err; - if ((err = encode_one(e, items[i], depth + 1))) - return err; - if (janet_buffer_push_u8(e->buffer, ',')) - goto overflow; - } - e->indent--; - if (e->buffer->data[e->buffer->count - 1] == ',') { - e->buffer->count--; - if ((err = encode_newline(e))) return err; - } - if (janet_buffer_push_u8(e->buffer, ']')) goto overflow; - } - break; - case JANET_TABLE: - case JANET_STRUCT: - { - const char *err; - const JanetKV *kvs; - int32_t count, capacity; - janet_dictionary_view(x, &kvs, &count, &capacity); - if (janet_buffer_push_u8(e->buffer, '{')) goto overflow; - e->indent++; - for (int32_t i = 0; i < capacity; i++) { - if (janet_checktype(kvs[i].key, JANET_NIL)) - continue; - if (!janet_checktype(kvs[i].key, JANET_STRING)) - return "only strings keys are allowed in objects"; - if ((err = encode_newline(e))) return err; - if ((err = encode_one(e, kvs[i].key, depth + 1))) - return err; - const char *sep = e->tablen ? ": " : ":"; - if (janet_buffer_push_cstring(e->buffer, sep)) - goto overflow; - if ((err = encode_one(e, kvs[i].value, depth + 1))) - return err; - if (janet_buffer_push_u8(e->buffer, ',')) - goto overflow; - } - e->indent--; - if (e->buffer->data[e->buffer->count - 1] == ',') { - e->buffer->count--; - if ((err = encode_newline(e))) return err; - } - if (janet_buffer_push_u8(e->buffer, '}')) goto overflow; - } - break; - } - return NULL; - - /* Errors */ -overflow: - return "buffer overflow"; -badtype: - return "type not supported"; -invalidutf8: - return "string contains invalid utf-8"; -} - -static int json_encode(JanetArgs args) { - JANET_MINARITY(args, 1); - JANET_MAXARITY(args, 3); - Encoder e; - e.indent = 0; - e.buffer = janet_buffer(10); - e.tab = NULL; - e.newline = NULL; - e.tablen = 0; - e.newlinelen = 0; - if (args.n >= 2) { - JANET_ARG_BYTES(e.tab, e.tablen, args, 1); - if (args.n >= 3) { - JANET_ARG_BYTES(e.newline, e.newlinelen, args, 2); - } else { - e.newline = (const uint8_t *)"\r\n"; - e.newlinelen = 2; - } - } - const char *err = encode_one(&e, args.v[0], 0); - if (err) JANET_THROW(args, err); - JANET_RETURN_BUFFER(args, e.buffer); -} - -/****************/ -/* Module Entry */ -/****************/ - -static const JanetReg cfuns[] = { - {"encode", json_encode, - "(json/encode x [,tab [,newline]])\n\n" - "Encodes a janet value in JSON (utf-8)." - }, - {"decode", json_decode, - "(json/decode json-source)\n\n" - "Returns a janet object after parsing JSON." - }, - {NULL, NULL, NULL} -}; - -JANET_MODULE_ENTRY(JanetArgs args) { - JanetTable *env = janet_env(args); - janet_cfuns(env, "json", cfuns); - return 0; -} diff --git a/natives/sqlite3/.gitignore b/natives/sqlite3/.gitignore deleted file mode 100644 index c4245504..00000000 --- a/natives/sqlite3/.gitignore +++ /dev/null @@ -1,62 +0,0 @@ -# Created by https://www.gitignore.io/api/c - -### C ### -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf - - -# End of https://www.gitignore.io/api/c - -sqlite3.c -sqlite3.h -sqlite-autoconf-3230100 diff --git a/natives/sqlite3/Makefile b/natives/sqlite3/Makefile deleted file mode 100644 index d9b4526f..00000000 --- a/natives/sqlite3/Makefile +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (c) 2018 Calvin Rose -# -# 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: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# 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. - -CFLAGS=-std=c99 -Wall -Wextra -I../../src/include -O2 -shared -fpic \ - -DSQLITE_THREADSAFE=0 \ - -DSQLITE_OMIT_LOAD_EXTENSION -TARGET=sqlite3.so - -# MacOS specifics -UNAME:=$(shell uname -s) -ifeq ($(UNAME), Darwin) - CFLAGS:=$(CFLAGS) -undefined dynamic_lookup -endif - -# Default target -all: $(TARGET) - -OBJECTS:=main.o sqlite3.o -$(TARGET): $(OBJECTS) - $(CC) $(CFLAGS) -o $@ $^ - -sqlite-autoconf-3230100/sqlite3.%: - curl https://www.sqlite.org/2018/sqlite-autoconf-3230100.tar.gz | tar -xvz - -sqlite3.c: sqlite-autoconf-3230100/sqlite3.c - cp $< $@ -sqlite3.h: sqlite-autoconf-3230100/sqlite3.h - cp $< $@ - -%.o: %.c sqlite3.h - $(CC) $(CFLAGS) -c $< - -clean: - rm -rf sqlite-autoconf-3230100 - rm *.o - rm sqlite3.c - rm sqlite3.h - rm $(TARGET) - -install: - cp $(TARGET) $(DST_PATH) - -.PHONY: clean all diff --git a/natives/sqlite3/main.c b/natives/sqlite3/main.c deleted file mode 100644 index d0dc31d7..00000000 --- a/natives/sqlite3/main.c +++ /dev/null @@ -1,428 +0,0 @@ -/* -* Copyright (c) 2018 Calvin Rose -* -* 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: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* 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. -*/ - -#include "sqlite3.h" -#include - -#define FLAG_CLOSED 1 - -#define MSG_DB_CLOSED "database already closed" - -typedef struct { - sqlite3* handle; - int flags; -} Db; - -/* Close a db, noop if already closed */ -static void closedb(Db *db) { - if (!(db->flags & FLAG_CLOSED)) { - db->flags |= FLAG_CLOSED; - sqlite3_close_v2(db->handle); - } -} - -/* Called to garbage collect a sqlite3 connection */ -static int gcsqlite(void *p, size_t s) { - (void) s; - Db *db = (Db *)p; - closedb(db); - return 0; -} - -static const JanetAbstractType sql_conn_type = { - ":sqlite3.connection", - gcsqlite, - NULL, -}; - -/* Open a new database connection */ -static int sql_open(JanetArgs args) { - sqlite3 *conn; - const uint8_t *filename; - int status; - JANET_FIXARITY(args, 1); - JANET_ARG_STRING(filename, args, 0); - status = sqlite3_open((const char *)filename, &conn); - if (status == SQLITE_OK) { - Db *db = (Db *) janet_abstract(&sql_conn_type, sizeof(Db)); - db->handle = conn; - db->flags = 0; - JANET_RETURN_ABSTRACT(args, db); - } else { - const char *err = sqlite3_errmsg(conn); - JANET_THROW(args, err); - } -} - -/* Close a database connection */ -static int sql_close(JanetArgs args) { - Db *db; - JANET_FIXARITY(args, 1); - JANET_ARG_ABSTRACT(db, args, 0, &sql_conn_type); - closedb(db); - JANET_RETURN_NIL(args); -} - -/* Check for embedded NULL bytes */ -static int has_null(const uint8_t *str, int32_t len) { - while (len--) { - if (!str[len]) - return 1; - } - return 0; -} - -/* Bind a single parameter */ -static const char *bind1(sqlite3_stmt *stmt, int index, Janet value) { - int res; - switch (janet_type(value)) { - default: - return "invalid sql value"; - case JANET_NIL: - res = sqlite3_bind_null(stmt, index); - break; - case JANET_FALSE: - res = sqlite3_bind_int(stmt, index, 0); - break; - case JANET_TRUE: - res = sqlite3_bind_int(stmt, index, 1); - break; - case JANET_REAL: - res = sqlite3_bind_double(stmt, index, janet_unwrap_real(value)); - break; - case JANET_INTEGER: - res = sqlite3_bind_int64(stmt, index, janet_unwrap_integer(value)); - break; - case JANET_STRING: - case JANET_SYMBOL: - { - const uint8_t *str = janet_unwrap_string(value); - int32_t len = janet_string_length(str); - if (has_null(str, len)) { - return "cannot have embedded nulls in text values"; - } else { - res = sqlite3_bind_text(stmt, index, (const char *)str, len + 1, SQLITE_STATIC); - } - } - break; - case JANET_BUFFER: - { - JanetBuffer *buffer = janet_unwrap_buffer(value); - res = sqlite3_bind_blob(stmt, index, buffer->data, buffer->count, SQLITE_STATIC); - } - break; - } - if (res != SQLITE_OK) { - sqlite3 *db = sqlite3_db_handle(stmt); - return sqlite3_errmsg(db); - } - return NULL; -} - -/* Bind many parameters */ -static const char *bindmany(sqlite3_stmt *stmt, Janet params) { - /* parameters */ - const Janet *seq; - const JanetKV *kvs; - int32_t len, cap; - int limitindex = sqlite3_bind_parameter_count(stmt); - if (janet_indexed_view(params, &seq, &len)) { - if (len > limitindex + 1) { - return "invalid index in sql parameters"; - } - for (int i = 0; i < len; i++) { - const char *err = bind1(stmt, i + 1, seq[i]); - if (err) { - return err; - } - } - } else if (janet_dictionary_view(params, &kvs, &len, &cap)) { - for (int i = 0; i < cap; i++) { - int index = 0; - switch (janet_type(kvs[i].key)) { - default: - /* Will fail */ - break; - case JANET_NIL: - /* Will skip as nil keys indicate empty hash table slot */ - continue; - case JANET_INTEGER: - index = janet_unwrap_integer(kvs[i].key); - break; - case JANET_STRING: - case JANET_SYMBOL: - { - const uint8_t *s = janet_unwrap_string(kvs[i].key); - index = sqlite3_bind_parameter_index( - stmt, - (const char *)s); - } - break; - } - if (index <= 0 || index > limitindex) { - return "invalid index in sql parameters"; - } - const char *err = bind1(stmt, index, kvs[i].value); - if (err) { - return err; - } - } - } else { - return "invalid type for sql parameters"; - } - return NULL; -} - -/* Execute a statement but don't collect results */ -static const char *execute(sqlite3_stmt *stmt) { - int status; - const char *ret = NULL; - do { - status = sqlite3_step(stmt); - } while (status == SQLITE_ROW); - /* Check for errors */ - if (status != SQLITE_DONE) { - sqlite3 *db = sqlite3_db_handle(stmt); - ret = sqlite3_errmsg(db); - } - return ret; -} - -/* Execute and return values from prepared statement */ -static const char *execute_collect(sqlite3_stmt *stmt, JanetArray *rows) { - /* Count number of columns in result */ - int ncol = sqlite3_column_count(stmt); - int status; - const char *ret = NULL; - - /* Get column names */ - Janet *tupstart = janet_tuple_begin(ncol); - for (int i = 0; i < ncol; i++) { - tupstart[i] = janet_cstringv(sqlite3_column_name(stmt, i)); - } - const Janet *colnames = janet_tuple_end(tupstart); - - do { - status = sqlite3_step(stmt); - if (status == SQLITE_ROW) { - JanetKV *row = janet_struct_begin(ncol); - for (int i = 0; i < ncol; i++) { - int t = sqlite3_column_type(stmt, i); - Janet value; - switch (t) { - case SQLITE_NULL: - value = janet_wrap_nil(); - break; - case SQLITE_INTEGER: - value = janet_wrap_integer(sqlite3_column_int(stmt, i)); - break; - case SQLITE_FLOAT: - value = janet_wrap_real(sqlite3_column_double(stmt, i)); - break; - case SQLITE_TEXT: - { - int nbytes = sqlite3_column_bytes(stmt, i); - uint8_t *str = janet_string_begin(nbytes); - memcpy(str, sqlite3_column_text(stmt, i), nbytes); - value = janet_wrap_string(janet_string_end(str)); - } - break; - case SQLITE_BLOB: - { - int nbytes = sqlite3_column_bytes(stmt, i); - JanetBuffer *b = janet_buffer(nbytes); - memcpy(b->data, sqlite3_column_blob(stmt, i), nbytes); - b->count = nbytes; - value = janet_wrap_buffer(b); - } - break; - } - janet_struct_put(row, colnames[i], value); - } - janet_array_push(rows, janet_wrap_struct(janet_struct_end(row))); - } - } while (status == SQLITE_ROW); - - /* Check for errors */ - if (status != SQLITE_DONE) { - sqlite3 *db = sqlite3_db_handle(stmt); - ret = sqlite3_errmsg(db); - } - return ret; -} - -/* Evaluate a string of sql */ -static int sql_eval(JanetArgs args) { - const char *err; - sqlite3_stmt *stmt = NULL, *stmt_next = NULL; - const uint8_t *query; - - JANET_MINARITY(args, 2); - JANET_MAXARITY(args, 3); - JANET_CHECKABSTRACT(args, 0, &sql_conn_type); - Db *db = (Db *)janet_unwrap_abstract(args.v[0]); - if (db->flags & FLAG_CLOSED) { - JANET_THROW(args, MSG_DB_CLOSED); - } - JANET_ARG_STRING(query, args, 1); - if (has_null(query, janet_string_length(query))) { - err = "cannot have embedded NULL in sql statememts"; - goto error; - } - JanetArray *rows = janet_array(10); - const char *c = (const char *)query; - - /* Evaluate all statements in a loop */ - do { - /* Compile the next statement */ - if (sqlite3_prepare_v2(db->handle, c, -1, &stmt_next, &c) != SQLITE_OK) { - err = sqlite3_errmsg(db->handle); - goto error; - } - /* Check if we have found last statement */ - if (NULL == stmt_next) { - /* Execute current statement and collect results */ - if (stmt) { - err = execute_collect(stmt, rows); - if (err) goto error; - } - } else { - /* Execute current statement but don't collect results. */ - if (stmt) { - err = execute(stmt); - if (err) goto error; - } - /* Bind params to next statement*/ - if (args.n == 3) { - /* parameters */ - err = bindmany(stmt_next, args.v[2]); - if (err) goto error; - } - } - /* rotate stmt and stmt_next */ - if (stmt) sqlite3_finalize(stmt); - stmt = stmt_next; - stmt_next = NULL; - } while (NULL != stmt); - - /* Good return path */ - JANET_RETURN_ARRAY(args, rows); - -error: - if (stmt) sqlite3_finalize(stmt); - if (stmt_next) sqlite3_finalize(stmt_next); - JANET_THROW(args, err); -} - -/* Convert int64_t to a string */ -static const uint8_t *coerce_int64(int64_t x) { - uint8_t bytes[40]; - int i = 0; - /* Edge cases */ - if (x == 0) return janet_cstring("0"); - if (x == INT64_MIN) return janet_cstring("-9,223,372,036,854,775,808"); - /* Negative becomes pos */ - if (x < 0) { - bytes[i++] = '-'; - x = -x; - } - while (x) { - bytes[i++] = x % 10; - x = x / 10; - } - bytes[i] = '\0'; - return janet_string(bytes, i); -} - -/* Gets the last inserted row id */ -static int sql_last_insert_rowid(JanetArgs args) { - JANET_FIXARITY(args, 1); - JANET_CHECKABSTRACT(args, 0, &sql_conn_type); - Db *db = (Db *)janet_unwrap_abstract(args.v[0]); - if (db->flags & FLAG_CLOSED) { - JANET_THROW(args, MSG_DB_CLOSED); - } - sqlite3_int64 id = sqlite3_last_insert_rowid(db->handle); - if (id >= INT32_MIN && id <= INT32_MAX) { - JANET_RETURN_INTEGER(args, (int32_t) id); - } - /* Convert to string */ - JANET_RETURN_STRING(args, coerce_int64(id)); -} - -/* Get the sqlite3 errcode */ -static int sql_error_code(JanetArgs args) { - JANET_FIXARITY(args, 1); - JANET_CHECKABSTRACT(args, 0, &sql_conn_type); - Db *db = (Db *)janet_unwrap_abstract(args.v[0]); - if (db->flags & FLAG_CLOSED) { - JANET_THROW(args, MSG_DB_CLOSED); - } - int errcode = sqlite3_errcode(db->handle); - JANET_RETURN_INTEGER(args, errcode); -} - -/*****************************************************************************/ - -static const JanetReg cfuns[] = { - {"open", sql_open, - "(sqlite3/open path)\n\n" - "Opens a sqlite3 database on disk. Returns the database handle if the database was opened " - "successfully, and otheriwse throws an error." - }, - {"close", sql_close, - "(sqlite3/close db)\n\n" - "Closes a database. Use this to free a database after use. Returns nil." - }, - {"eval", sql_eval, - "(sqlite3/eval db sql [,params])\n\n" - "Evaluate sql in the context of database db. Multiple sql statements " - "can be changed together, and optionally parameters maybe passed in. " - "The optional parameters maybe either an indexed data type (tuple or array), or a dictionary " - "data type (struct or table). If params is a tuple or array, then sqlite " - "parameters are substituted using indices. For example:\n\n" - "\t(sqlite3/eval db `SELECT * FROM tab WHERE id = ?;` [123])\n\n" - "Will select rows from tab where id is equal to 123. Alternatively, " - "the programmer can use named parameters with tables or structs, like so:\n\n" - "\t(sqlite3/eval db `SELECT * FROM tab WHERE id = :id;` {:id 123})\n\n" - "Will return an array of rows, where each row contains a table where columns names " - "are keys for column values." - }, - {"last-insert-rowid", sql_last_insert_rowid, - "(sqlite3/last-insert-rowid db)\n\n" - "Returns the id of the last inserted row. If the id will fit into a 32-bit" - "signed integer, will returned an integer, otherwise will return a string representation " - "of the id (an 8 bytes string containing a long integer)." - }, - {"error-code", sql_error_code, - "(sqlite3/error-code db)\n\n" - "Returns the error number of the last sqlite3 command that threw an error. Cross " - "check these numbers with the SQLite documentation for more information." - }, - {NULL, NULL, NULL} -}; - -JANET_MODULE_ENTRY(JanetArgs args) { - JanetTable *env = janet_env(args); - janet_cfuns(env, "sqlite3", cfuns); - return 0; -}