From fef18d647c1ad788fc38911cece6c804c88ec44e Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Sat, 25 Apr 2026 12:35:12 -0400 Subject: [PATCH 1/2] Fixes sub array insertion --- cpp/utils.cpp | 44 ++++++++++++++++++++++++++++++++------- example/ios/Podfile.lock | 2 +- example/src/tests/blob.ts | 17 +++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 69ae0f6c..48ddab81 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -97,18 +97,48 @@ inline JSVariant to_variant(jsi::Runtime &rt, const jsi::Value &value) { return JSVariant(strVal); } else if (value.isObject()) { auto obj = value.asObject(rt); + size_t byteOffset = 0; + size_t byteLength = 0; + uint8_t *sourceData = nullptr; + + if (obj.isArrayBuffer(rt)) { + auto buffer = obj.getArrayBuffer(rt); + sourceData = buffer.data(rt); + byteLength = buffer.size(rt); + } else { + jsi::Function arrayBufferCtor = + rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); + jsi::Function isViewFn = + arrayBufferCtor.getPropertyAsFunction(rt, "isView"); + bool isArrayBufferView = isViewFn.call(rt, obj).getBool(); + + if (!isArrayBufferView) { + throw std::runtime_error( + "Object is not an ArrayBuffer or ArrayBuffer view, cannot bind " + "to SQLite"); + } + + jsi::Object bufferObject = obj.getPropertyAsObject(rt, "buffer"); + auto buffer = bufferObject.getArrayBuffer(rt); + + byteOffset = + static_cast(obj.getProperty(rt, "byteOffset").asNumber()); + byteLength = + static_cast(obj.getProperty(rt, "byteLength").asNumber()); + + const size_t bufferSize = buffer.size(rt); + if (byteOffset > bufferSize || byteLength > bufferSize - byteOffset) { + throw std::runtime_error("Invalid ArrayBuffer view range"); + } - if (!obj.isArrayBuffer(rt)) { - throw std::runtime_error( - "Object is not an ArrayBuffer, cannot bind to SQLite"); + sourceData = buffer.data(rt) + byteOffset; } - auto buffer = obj.getArrayBuffer(rt); - uint8_t *data = new uint8_t[buffer.size(rt)]; - memcpy(data, buffer.data(rt), buffer.size(rt)); + uint8_t *data = new uint8_t[byteLength]; + memcpy(data, sourceData, byteLength); return JSVariant(ArrayBuffer{.data = std::shared_ptr{data}, - .size = buffer.size(rt)}); + .size = byteLength}); } throw std::runtime_error("Cannot convert JSI value to C++ Variant value"); diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index bfc7418c..c1f0e3bc 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2002,7 +2002,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: 2e5b5553df729e080483373db6f045201ff4e6db hermes-engine: 273e30e7fb618279934b0b95ffab60ecedb7acf5 - op-sqlite: aec4b9ac1b22d981c2ddaf27a0acc199c1187a3c + op-sqlite: 4df330bec6c23e6d9c9a5e256831869c58a618ea RCTDeprecation: c6b36da89aa26090c8684d29c2868dcca2cd4554 RCTRequired: 1413a0844770d00fa1f1bb2da4680adfa8698065 RCTTypeSafety: 354b4bb344998550c45d054ef66913837948f958 diff --git a/example/src/tests/blob.ts b/example/src/tests/blob.ts index 2e5e4c21..d2d10b98 100644 --- a/example/src/tests/blob.ts +++ b/example/src/tests/blob.ts @@ -60,6 +60,23 @@ describe("Blobs", () => { expect(finalUint8[0]).toBe(42); }); + it("Uint8Array subarray", async () => { + const uint8 = new Uint8Array([97, 98, 99, 100]); + const subarray = uint8.subarray(1, 3); + + await db.execute(`INSERT OR REPLACE INTO BlobTable VALUES (?, ?);`, [ + 1, + subarray, + ]); + + const result = await db.execute("SELECT content FROM BlobTable"); + const content = result.rows[0]?.content; + expect(content).toBeTruthy(); + + const finalUint8 = new Uint8Array(content as ArrayBuffer); + expect(Array.from(finalUint8)).toEqual([98, 99]); + }); + it("Uint16Array", async () => { const uint8 = new Uint16Array(2); uint8[0] = 42; From a3dbfe318c6bd2fa3b839a3d0cfe357fe07b9096 Mon Sep 17 00:00:00 2001 From: Oscar Franco Date: Sat, 25 Apr 2026 13:00:46 -0400 Subject: [PATCH 2/2] Do not sanitize params --- cpp/utils.cpp | 23 ++++++------- example/src/tests/blob.ts | 7 ++-- example/src/tests/queries.ts | 2 +- src/functions.ts | 64 +++++------------------------------- 4 files changed, 25 insertions(+), 71 deletions(-) diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 48ddab81..0f441755 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -100,24 +100,17 @@ inline JSVariant to_variant(jsi::Runtime &rt, const jsi::Value &value) { size_t byteOffset = 0; size_t byteLength = 0; uint8_t *sourceData = nullptr; + jsi::Function arrayBufferCtor = + rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); + jsi::Function isViewFn = + arrayBufferCtor.getPropertyAsFunction(rt, "isView"); + bool isArrayBufferView = isViewFn.call(rt, obj).getBool(); if (obj.isArrayBuffer(rt)) { auto buffer = obj.getArrayBuffer(rt); sourceData = buffer.data(rt); byteLength = buffer.size(rt); - } else { - jsi::Function arrayBufferCtor = - rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); - jsi::Function isViewFn = - arrayBufferCtor.getPropertyAsFunction(rt, "isView"); - bool isArrayBufferView = isViewFn.call(rt, obj).getBool(); - - if (!isArrayBufferView) { - throw std::runtime_error( - "Object is not an ArrayBuffer or ArrayBuffer view, cannot bind " - "to SQLite"); - } - + } else if (isArrayBufferView) { jsi::Object bufferObject = obj.getPropertyAsObject(rt, "buffer"); auto buffer = bufferObject.getArrayBuffer(rt); @@ -132,6 +125,10 @@ inline JSVariant to_variant(jsi::Runtime &rt, const jsi::Value &value) { } sourceData = buffer.data(rt) + byteOffset; + } else { + throw std::runtime_error( + "Object is not an ArrayBuffer or ArrayBuffer view, cannot bind " + "to SQLite"); } uint8_t *data = new uint8_t[byteLength]; diff --git a/example/src/tests/blob.ts b/example/src/tests/blob.ts index d2d10b98..7df6c963 100644 --- a/example/src/tests/blob.ts +++ b/example/src/tests/blob.ts @@ -5,6 +5,7 @@ import { describe, expect, it, + itOnly, } from "@op-engineering/op-test"; let db: DB; @@ -56,7 +57,7 @@ describe("Blobs", () => { const result = await db.execute("SELECT content FROM BlobTable"); - const finalUint8 = new Uint8Array(result.rows[0]!.content as any); + const finalUint8 = new Uint8Array(result.rows[0]?.content as any); expect(finalUint8[0]).toBe(42); }); @@ -74,7 +75,9 @@ describe("Blobs", () => { expect(content).toBeTruthy(); const finalUint8 = new Uint8Array(content as ArrayBuffer); - expect(Array.from(finalUint8)).toEqual([98, 99]); + const res = Array.from(finalUint8); + expect(res[0]).toEqual(98); + expect(res[1]).toEqual(99); }); it("Uint16Array", async () => { diff --git a/example/src/tests/queries.ts b/example/src/tests/queries.ts index e1d67859..9f12a73b 100644 --- a/example/src/tests/queries.ts +++ b/example/src/tests/queries.ts @@ -101,7 +101,7 @@ describe("Queries tests", () => { } catch (e: any) { expect( e.message.includes( - "Exception in HostFunction: Object is not an ArrayBuffer, cannot bind to SQLite", + "Object is not an ArrayBuffer or ArrayBuffer view", ), ).toEqual(true); } diff --git a/src/functions.ts b/src/functions.ts index eebc85d5..b1d388c5 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -68,26 +68,6 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { } }; - function sanitizeArrayBuffersInArray( - params?: any[] | any[][], - ): any[] | undefined { - if (!params) { - return params; - } - - return params.map((p) => { - if (Array.isArray(p)) { - return sanitizeArrayBuffersInArray(p); - } - - if (ArrayBuffer.isView(p)) { - return p.buffer; - } - - return p; - }); - } - // spreading the object does not work with HostObjects (db) // We need to manually assign the fields const enhancedDb = { @@ -112,14 +92,6 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { executeBatch: async ( commands: SQLBatchTuple[], ): Promise => { - // Do normal for loop and replace in place for performance - for (let i = 0; i < commands.length; i++) { - // [1] is the params arg - if (commands[i]![1]) { - commands[i]![1] = sanitizeArrayBuffersInArray(commands[i]![1]) as any; - } - } - async function run() { try { enhancedDb.executeSync("BEGIN TRANSACTION;"); @@ -160,33 +132,24 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { query: string, params?: Scalar[], ): Promise => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - - return sanitizedParams - ? await db.executeWithHostObjects(query, sanitizedParams as Scalar[]) + return params + ? await db.executeWithHostObjects(query, params) : await db.executeWithHostObjects(query); }, executeRaw: async (query: string, params?: Scalar[]) => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - - return db.executeRaw(query, sanitizedParams as Scalar[]); + return db.executeRaw(query, params as Scalar[]); }, executeRawSync: (query: string, params?: Scalar[]) => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - return db.executeRawSync(query, sanitizedParams as Scalar[]); + return db.executeRawSync(query, params as Scalar[]); }, // Wrapper for executeRaw, drizzleORM uses this function // at some point I changed the API but they did not pin their dependency to a specific version // so re-inserting this so it starts working again executeRawAsync: async (query: string, params?: Scalar[]) => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - - return db.executeRaw(query, sanitizedParams as Scalar[]); + return db.executeRaw(query, params as Scalar[]); }, executeSync: (query: string, params?: Scalar[]): QueryResult => { - let res = params - ? db.executeSync(query, sanitizeArrayBuffersInArray(params) as Scalar[]) - : db.executeSync(query); + let res = params ? db.executeSync(query, params) : db.executeSync(query); if (!res.rows) { const rows: Record[] = []; @@ -222,12 +185,7 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { query: string, params?: Scalar[] | undefined, ): Promise => { - let res = params - ? await db.execute( - query, - sanitizeArrayBuffersInArray(params) as Scalar[], - ) - : await db.execute(query); + let res = params ? await db.execute(query, params) : await db.execute(query); if (!res.rows) { const rows: Record[] = []; @@ -258,14 +216,10 @@ function enhanceDB(db: _InternalDB, options: DBParams): DB { return { bindSync: (params: Scalar[]) => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - - stmt.bindSync(sanitizedParams!); + stmt.bindSync(params); }, bind: async (params: Scalar[]) => { - const sanitizedParams = sanitizeArrayBuffersInArray(params); - - await stmt.bind(sanitizedParams!); + await stmt.bind(params); }, execute: stmt.execute, };