diff --git a/cpp/utils.cpp b/cpp/utils.cpp index 69ae0f6c..0f441755 100644 --- a/cpp/utils.cpp +++ b/cpp/utils.cpp @@ -97,18 +97,45 @@ 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; + 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 if (isArrayBufferView) { + 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)) { + sourceData = buffer.data(rt) + byteOffset; + } else { throw std::runtime_error( - "Object is not an ArrayBuffer, cannot bind to SQLite"); + "Object is not an ArrayBuffer or ArrayBuffer view, cannot bind " + "to SQLite"); } - 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..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,10 +57,29 @@ 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); }); + 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); + const res = Array.from(finalUint8); + expect(res[0]).toEqual(98); + expect(res[1]).toEqual(99); + }); + it("Uint16Array", async () => { const uint8 = new Uint16Array(2); uint8[0] = 42; 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, };