also looking at this
feat(android): dbExecute and dbQuery in Zig — ratchet 84 → 82
#160
7 files
+541
-3
| @@ -47,6 +47,7 @@ const haptics = ; | ||
| 47 | 47 | const notifcancel = ; |
| 48 | 48 | const events = ; |
| 49 | 49 | const calendar = ; |
| 50 | const db = ; | |
| 50 | 51 | |
| 51 | 52 | const Jni = jni.Jni; |
| 52 | 53 | |
| @@ -248,6 +249,19 @@ const natives = [_]jni.JNINativeMethod{ | ||
| 248 | 249 | .signature = "(Landroid/app/Activity;Ljava/lang/String;)Z", |
| 249 | 250 | .fnPtr = @ptrCast(&nativeCreateCalendarEvent), |
| 250 | 251 | }, |
| 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 | }, | |
| 251 | 265 | }; |
| 252 | 266 | |
| 253 | 267 | /// How binding went. A value rather than a log line, so every path is |
| @@ -789,6 +803,85 @@ fn nativeCreateCalendarEvent( | ||
| 789 | 803 | return jni.JNI_TRUE; |
| 790 | 804 | } |
| 791 | 805 | |
| 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. | |
| 811 | fn 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)`. | |
| 829 | fn 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)`. | |
| 858 | fn 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 | ||
| 792 | 885 | // ============================================================================= |
| 793 | 886 | // Tests |
| 794 | 887 | // ============================================================================= |
| @@ -900,7 +993,7 @@ test "the registered natives name methods the Kotlin actually declares" { | ||
| 900 | 993 | // A descriptor is checked by the JVM at registration, so a wrong one fails |
| 901 | 994 | // at load rather than at call — but only if the *name* matches something. |
| 902 | 995 | // 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); | |
| 904 | 997 | try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name)); |
| 905 | 998 | |
| 906 | 999 | // And the class they bind to is the fixed one, not the templated bridge. |