summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-08-04 18:12:11 +0200
committerFlorian Dold <florian@dold.me>2023-08-10 15:23:47 +0200
commit5e69b78fe469fce5e4e83d5654d1f31334c3cbbe (patch)
treedbc3f3efa498ea6ed8d3e2cb051dc9d6256a1da6
parent629c480cfd83e7b70f2d2de1bba35c0aa2d8c6da (diff)
downloadquickjs-tart-5e69b78fe469fce5e4e83d5654d1f31334c3cbbe.tar.gz
quickjs-tart-5e69b78fe469fce5e4e83d5654d1f31334c3cbbe.tar.bz2
quickjs-tart-5e69b78fe469fce5e4e83d5654d1f31334c3cbbe.zip
sqlite3 support WIP
-rw-r--r--tart_module.c313
-rw-r--r--test_sqlite3.js7
2 files changed, 247 insertions, 73 deletions
diff --git a/tart_module.c b/tart_module.c
index 77e3b5b..314953d 100644
--- a/tart_module.c
+++ b/tart_module.c
@@ -1534,41 +1534,41 @@ static JSValue js_sqlite3_finalize(JSContext *ctx, JSValue this_val,
}
static int sql_exec_cb(void *cls, int numcol, char **res, char **colnames) {
- printf("got row with %d columns\n", numcol);
- return 0;
+ printf("got row with %d columns\n", numcol);
+ return 0;
}
static JSValue js_sqlite3_exec(JSContext *ctx, JSValue this_val,
int argc, JSValueConst *argv)
{
- sqlite3 *sqlite3_db;
- sqlite3_stmt *stmt;
- JSValue db_handle = argv[0];
- JSValue stmt_str = argv[1];
- const char *stmt_cstr;
- int res;
- char *errmsg = NULL;
- JSValue ret_val = JS_UNDEFINED;
+ sqlite3 *sqlite3_db;
+ sqlite3_stmt *stmt;
+ JSValue db_handle = argv[0];
+ JSValue stmt_str = argv[1];
+ const char *stmt_cstr;
+ int res;
+ char *errmsg = NULL;
+ JSValue ret_val = JS_UNDEFINED;
- sqlite3_db = JS_GetOpaque(db_handle, js_sqlite3_database_class_id);
- if (!sqlite3_db) {
- ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
- goto done;
- }
- stmt_cstr = JS_ToCString(ctx, stmt_str);
- if (!stmt_cstr) {
- ret_val = JS_ThrowTypeError(ctx, "invalid prepared statement, string expected");
- goto done;
- }
- res = sqlite3_exec(sqlite3_db, stmt_cstr, &sql_exec_cb, NULL, &errmsg);
- if (SQLITE_OK != res) {
- // FIXME: throw!
- printf("got error: %s\n", errmsg);
- }
+ sqlite3_db = JS_GetOpaque(db_handle, js_sqlite3_database_class_id);
+ if (!sqlite3_db) {
+ ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
+ goto done;
+ }
+ stmt_cstr = JS_ToCString(ctx, stmt_str);
+ if (!stmt_cstr) {
+ ret_val = JS_ThrowTypeError(ctx, "invalid prepared statement, string expected");
+ goto done;
+ }
+ res = sqlite3_exec(sqlite3_db, stmt_cstr, &sql_exec_cb, NULL, &errmsg);
+ if (SQLITE_OK != res) {
+ // FIXME: throw!
+ printf("got error: %s\n", errmsg);
+ }
done:
- sqlite3_free(errmsg);
- JS_FreeCString(ctx, stmt_cstr);
- return JS_UNDEFINED;
+ sqlite3_free(errmsg);
+ JS_FreeCString(ctx, stmt_cstr);
+ return JS_UNDEFINED;
}
static int find_param_index(sqlite3_stmt *stmt, const char *name)
@@ -1626,10 +1626,24 @@ static int bind_from_object(JSContext *ctx, sqlite3_stmt *stmt, JSValueConst obj
JS_ThrowTypeError(ctx, "unable to bind, named param '%s' not found", key);
goto fail;
}
- // FIXME: check if valid parameter!
if (JS_IsNull(val)) {
sqlite3_bind_null(stmt, param_index);
+ continue;
}
+ if (JS_IsString(val)) {
+ const char *cstr;
+ cstr = JS_ToCString(ctx, val);
+ sqlite3_bind_text(stmt, param_index, cstr, strlen(cstr), SQLITE_TRANSIENT);
+ continue;
+ }
+ if (JS_IsNumber(val)) {
+ int64_t n;
+ JS_ToInt64(ctx, &n, val);
+ sqlite3_bind_int64(stmt, param_index, n);
+ continue;
+ }
+ JS_ThrowTypeError(ctx, "unable to bind, unsupported type");
+ goto fail;
}
done:
JS_FreeCString(ctx, key);
@@ -1647,66 +1661,221 @@ fail:
static JSValue js_sqlite3_stmt_run(JSContext *ctx, JSValue this_val,
int argc, JSValueConst *argv)
{
- JSValue ret_val = JS_UNDEFINED;
- JSValue stmt_handle = argv[0];
- sqlite3_stmt *stmt;
- sqlite3 *db;
- int sqlret;
-
- stmt = JS_GetOpaque(stmt_handle, js_sqlite3_statement_class_id);
- if (!stmt) {
- ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
- goto done;
- }
- db = sqlite3_db_handle(stmt);
- sqlret = sqlite3_reset(stmt);
- if (SQLITE_OK != sqlret) {
- ret_val = JS_ThrowTypeError(ctx, "failed to reset");
- goto done;
- }
- sqlret = sqlite3_clear_bindings(stmt);
- if (SQLITE_OK != sqlret) {
- ret_val = JS_ThrowTypeError(ctx, "failed to clear bindings");
- goto done;
- }
- if (argc > 1) {
- if (0 != bind_from_object(ctx, stmt, argv[1])) {
- ret_val = JS_EXCEPTION;
+ JSValue ret_val = JS_UNDEFINED;
+ JSValue stmt_handle = argv[0];
+ sqlite3_stmt *stmt;
+ sqlite3 *db;
+ int sqlret;
+
+ stmt = JS_GetOpaque(stmt_handle, js_sqlite3_statement_class_id);
+ if (!stmt) {
+ ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
goto done;
}
- }
- while (1) {
- sqlret = sqlite3_step(stmt);
- switch (sqlret) {
- case SQLITE_ROW:
- break;
- case SQLITE_DONE: {
- JSValue rowid_val;
- ret_val = JS_NewObject(ctx);
- sqlite3_int64 rowid = sqlite3_last_insert_rowid(db);
- rowid_val = JS_NewBigUint64(ctx, rowid);
- JS_SetPropertyStr(ctx, ret_val, "lastInsertRowid", rowid_val);
+ db = sqlite3_db_handle(stmt);
+ sqlret = sqlite3_reset(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to reset");
goto done;
}
- default:
- ret_val = JS_ThrowTypeError(ctx, "sqlite3_step failed");
+ sqlret = sqlite3_clear_bindings(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to clear bindings");
goto done;
}
- }
+ if (argc > 1) {
+ if (0 != bind_from_object(ctx, stmt, argv[1])) {
+ ret_val = JS_EXCEPTION;
+ goto done;
+ }
+ }
+ while (1) {
+ sqlret = sqlite3_step(stmt);
+ switch (sqlret) {
+ case SQLITE_ROW:
+ break;
+ case SQLITE_DONE: {
+ JSValue rowid_val;
+ ret_val = JS_NewObject(ctx);
+ sqlite3_int64 rowid = sqlite3_last_insert_rowid(db);
+ rowid_val = JS_NewBigUint64(ctx, rowid);
+ JS_SetPropertyStr(ctx, ret_val, "lastInsertRowid", rowid_val);
+ goto done;
+ }
+ default:
+ ret_val = JS_ThrowTypeError(ctx, "sqlite3_step failed");
+ goto done;
+ }
+ }
done:
- return ret_val;
+ return ret_val;
+}
+
+#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1)
+#define MIN_SAFE_INTEGER (-(((int64_t)1 << 53) - 1))
+
+
+static int extract_result_row(JSContext *ctx, sqlite3_stmt *stmt, JSValueConst target)
+{
+ int colcount;
+ int i;
+
+ colcount = sqlite3_column_count(stmt);
+
+ for (i = 0; i < colcount; i++) {
+ const char *colname = sqlite3_column_name(stmt, i);
+ int coltype = sqlite3_column_type(stmt, i);
+ switch (coltype) {
+ case SQLITE_INTEGER: {
+ int64_t val = sqlite3_column_int64(stmt, i);
+ if (val > MAX_SAFE_INTEGER || val < MIN_SAFE_INTEGER) {
+ JS_SetPropertyStr(ctx, target, colname, JS_NewBigInt64(ctx, val));
+ } else {
+ JS_SetPropertyStr(ctx, target, colname, JS_NewInt64(ctx, val));
+ }
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double val = sqlite3_column_double(stmt, i);
+ JS_SetPropertyStr(ctx, target, colname, JS_NewFloat64(ctx, val));
+ break;
+ }
+ case SQLITE_BLOB:
+ JS_ThrowInternalError(ctx, "blob not yet supported");
+ return -1;
+ case SQLITE_NULL:
+ JS_SetPropertyStr(ctx, target, colname, JS_NULL);
+ break;
+ case SQLITE_TEXT:
+ const char *text = sqlite3_column_text(stmt, i);
+ JS_SetPropertyStr(ctx, target, colname, JS_NewString(ctx, text));
+ break;
+ default:
+ JS_ThrowInternalError(ctx, "unexpected type from DB");
+ return -1;
+ }
+ }
+ return 0;
}
static JSValue js_sqlite3_stmt_get_all(JSContext *ctx, JSValue this_val,
int argc, JSValueConst *argv)
{
- return JS_UNDEFINED;
+ JSValue ret_val = JS_UNDEFINED;
+ JSValue stmt_handle = argv[0];
+ sqlite3_stmt *stmt;
+ sqlite3 *db;
+ int sqlret;
+ JSValue rows_array = JS_UNDEFINED;
+
+ stmt = JS_GetOpaque(stmt_handle, js_sqlite3_statement_class_id);
+ if (!stmt) {
+ ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
+ goto done;
+ }
+ db = sqlite3_db_handle(stmt);
+ sqlret = sqlite3_reset(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to reset");
+ goto done;
+ }
+ sqlret = sqlite3_clear_bindings(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to clear bindings");
+ goto done;
+ }
+ if (argc > 1) {
+ if (0 != bind_from_object(ctx, stmt, argv[1])) {
+ ret_val = JS_EXCEPTION;
+ goto done;
+ }
+ }
+ rows_array = JS_NewArray(ctx);
+ while (1) {
+ sqlret = sqlite3_step(stmt);
+ switch (sqlret) {
+ case SQLITE_ROW: {
+ JSValue row_obj = JS_NewObject(ctx);
+ if (0 != extract_result_row(ctx, stmt, row_obj)) {
+ goto fail;
+ }
+ qjs_array_append_new(ctx, rows_array, row_obj);
+ break;
+ }
+ case SQLITE_DONE: {
+ ret_val = JS_DupValue(ctx, rows_array);
+ goto done;
+ }
+ default:
+ ret_val = JS_ThrowTypeError(ctx, "sqlite3_step failed");
+ goto done;
+ }
+ }
+done:
+ JS_FreeValue(ctx, rows_array);
+ return ret_val;
+fail:
+ ret_val = JS_EXCEPTION;
+ goto done;
}
static JSValue js_sqlite3_stmt_get_first(JSContext *ctx, JSValue this_val,
int argc, JSValueConst *argv)
{
- return JS_UNDEFINED;
+ JSValue ret_val = JS_UNDEFINED;
+ JSValue stmt_handle = argv[0];
+ sqlite3_stmt *stmt;
+ sqlite3 *db;
+ int sqlret;
+
+ stmt = JS_GetOpaque(stmt_handle, js_sqlite3_statement_class_id);
+ if (!stmt) {
+ ret_val = JS_ThrowTypeError(ctx, "invalid sqlite3 database handle");
+ goto done;
+ }
+ db = sqlite3_db_handle(stmt);
+ sqlret = sqlite3_reset(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to reset");
+ goto done;
+ }
+ sqlret = sqlite3_clear_bindings(stmt);
+ if (SQLITE_OK != sqlret) {
+ ret_val = JS_ThrowTypeError(ctx, "failed to clear bindings");
+ goto done;
+ }
+ if (argc > 1) {
+ if (0 != bind_from_object(ctx, stmt, argv[1])) {
+ ret_val = JS_EXCEPTION;
+ goto done;
+ }
+ }
+ while (1) {
+ sqlret = sqlite3_step(stmt);
+ switch (sqlret) {
+ case SQLITE_ROW: {
+ JSValue row_obj = JS_NewObject(ctx);
+ if (0 != extract_result_row(ctx, stmt, row_obj)) {
+ goto fail;
+ }
+ ret_val = row_obj;
+ goto done;
+ break;
+ }
+ case SQLITE_DONE: {
+ ret_val = JS_UNDEFINED;
+ goto done;
+ }
+ default:
+ ret_val = JS_ThrowTypeError(ctx, "sqlite3_step failed");
+ goto done;
+ }
+ }
+done:
+ return ret_val;
+fail:
+ ret_val = JS_EXCEPTION;
+ goto done;
}
diff --git a/test_sqlite3.js b/test_sqlite3.js
index 134863b..6de5cf1 100644
--- a/test_sqlite3.js
+++ b/test_sqlite3.js
@@ -15,4 +15,9 @@ console.log("stmt1 res:", res.lastInsertRowid);
const stmt2 = tart.sqlite3Prepare(db, "select * from foo")
res = tart.sqlite3StmtGetAll(stmt2);
-console.log("result:", res);
+console.log("result:", JSON.stringify(res));
+
+const stmt3 = tart.sqlite3Prepare(db, "select * from foo")
+res = tart.sqlite3StmtGetFirst(stmt3);
+
+console.log("result:", JSON.stringify(res));