ReviewOS

also looking at this

craft-native/craft

feat(android): dbExecute and dbQuery in Zig — ratchet 84 → 82

#160
Merged glennmichael123 wants to merge feat/android-sqlite into main
7 files +541 -3
packages/zig/src/android_dispatch.zigmodified+94-1
Changes to packages/zig/src/android_dispatch.zig
@@ -47,6 +47,7 @@ const haptics = ;
4747const notifcancel = ;
4848const events = ;
4949const calendar = ;
50const db = ;
5051
5152const Jni = jni.Jni;
5253
@@ -248,6 +249,19 @@ const natives = [_]jni.JNINativeMethod{
248249 .signature = "(Landroid/app/Activity;Ljava/lang/String;)Z",
249250 .fnPtr = @ptrCast(&nativeCreateCalendarEvent),
250251 },
252 // The database arrives as an argument rather than being opened here, so
253 // one connection serves both languages — the same shape the secure store
254 // uses for its SharedPreferences.
255 .{
256 .name = "nativeDbExecute",
257 .signature = "(Landroid/database/sqlite/SQLiteDatabase;Ljava/lang/String;Ljava/lang/String;)Z",
258 .fnPtr = @ptrCast(&nativeDbExecute),
259 },
260 .{
261 .name = "nativeDbQuery",
262 .signature = "(Landroid/database/sqlite/SQLiteDatabase;Ljava/lang/String;Ljava/lang/String;)Z",
263 .fnPtr = @ptrCast(&nativeDbQuery),
264 },
251265};
252266
253267/// How binding went. A value rather than a log line, so every path is
@@ -789,6 +803,85 @@ fn nativeCreateCalendarEvent(
789803 return jni.JNI_TRUE;
790804}
791805
806/// The parameters and SQL both actions start from, or null to decline.
807///
808/// Declining costs nothing here because it happens before any JNI call that
809/// could change the database — the shim then runs the whole method, including
810/// its own parse.
811fn dbCall(
812 j: Jni,
813 allocator: std.mem.Allocator,
814 sql: jni.jstring,
815 params_json: jni.jstring,
816 parsed: *std.json.Parsed(std.json.Value),
817) !?struct { sql: []const u8, args: [][]const u8 } {
818 const sql_text = try j.stringToUtf8(allocator, sql);
819 const params_text = try j.stringToUtf8(allocator, params_json);
820
821 parsed.* = std.json.parseFromSlice(std.json.Value, allocator, params_text, .{}) catch
822 return null;
823
824 const args = (try db.bindArgs(allocator, parsed.value)) orelse return null;
825 return .{ .sql = sql_text, .args = args };
826}
827
828/// `nativeDbExecute(database, sql, paramsJson)`.
829fn nativeDbExecute(
830 env: jni.JNIEnv,
831 _: jni.jobject,
832 database: jni.jobject,
833 sql: jni.jstring,
834 params_json: jni.jstring,
835) callconv(.c) jni.jboolean {
836 const j = Jni.init(env);
837 var arena = std.heap.ArenaAllocator.init(backing);
838 defer arena.deinit();
839 const allocator = arena.allocator();
840
841 var parsed: std.json.Parsed(std.json.Value) = undefined;
842 const call = (dbCall(j, allocator, sql, params_json, &parsed) catch return jni.JNI_FALSE) orelse
843 return jni.JNI_FALSE;
844
845 db.execute(j, allocator, database, call.sql, call.args) catch |err| {
846 // The shim catches and rejects with the exception's message; the
847 // throwable was described to logcat and cleared by `Jni.check` before
848 // this point, so the error name is what is left to say.
849 calendar.rejectOn(allocator, db.exec_reject_global, @errorName(err)) catch {};
850 return jni.JNI_TRUE;
851 };
852
853 events.settle(allocator, db.exec_resolve_global, db.exec_result) catch return jni.JNI_FALSE;
854 return jni.JNI_TRUE;
855}
856
857/// `nativeDbQuery(database, sql, paramsJson)`.
858fn nativeDbQuery(
859 env: jni.JNIEnv,
860 _: jni.jobject,
861 database: jni.jobject,
862 sql: jni.jstring,
863 params_json: jni.jstring,
864) callconv(.c) jni.jboolean {
865 const j = Jni.init(env);
866 var arena = std.heap.ArenaAllocator.init(backing);
867 defer arena.deinit();
868 const allocator = arena.allocator();
869
870 var parsed: std.json.Parsed(std.json.Value) = undefined;
871 const call = (dbCall(j, allocator, sql, params_json, &parsed) catch return jni.JNI_FALSE) orelse
872 return jni.JNI_FALSE;
873
874 var payload: std.ArrayListUnmanaged(u8) = .empty;
875 defer payload.deinit(allocator);
876 db.query(j, allocator, database, call.sql, call.args, &payload) catch |err| {
877 calendar.rejectOn(allocator, db.query_reject_global, @errorName(err)) catch {};
878 return jni.JNI_TRUE;
879 };
880
881 events.settle(allocator, db.query_resolve_global, payload.items) catch return jni.JNI_FALSE;
882 return jni.JNI_TRUE;
883}
884
792885// =============================================================================
793886// Tests
794887// =============================================================================
@@ -900,7 +993,7 @@ test "the registered natives name methods the Kotlin actually declares" {
900993 // A descriptor is checked by the JVM at registration, so a wrong one fails
901994 // at load rather than at call — but only if the *name* matches something.
902995 // These two strings are the contract with CraftBridge.kt.
903 try testing.expectEqual(@as(usize, 19), natives.len);
996 try testing.expectEqual(@as(usize, 21), natives.len);
904997 try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name));
905998
906999 // And the class they bind to is the fixed one, not the templated bridge.