From b6b0901ca021eb52be33658185a8c765eac696e4 Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sat, 28 Dec 2024 12:04:20 -0800 Subject: [PATCH] sqlite: support TypedArray and DataView in `StatementSync` Co-authored-by: Antoine du Hamel --- doc/api/sqlite.md | 24 ++++++-- src/node_sqlite.cc | 4 +- .../test-sqlite-typed-array-and-data-view.js | 61 +++++++++++++++++++ 3 files changed, 83 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-sqlite-typed-array-and-data-view.js diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 9e18a12ea90a71..a15ff13600b259 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -322,11 +322,15 @@ over hand-crafted SQL strings when handling user input. * `namedParameters` {Object} An optional object used to bind named parameters. The keys of this object are used to configure the mapping. -* `...anonymousParameters` {null|number|bigint|string|Buffer|Uint8Array} Zero or +* `...anonymousParameters` {null|number|bigint|string|Buffer|TypedArray|DataView} Zero or more values to bind to anonymous parameters. * Returns: {Array} An array of objects. Each object corresponds to a row returned by executing the prepared statement. The keys and values of each @@ -354,11 +358,15 @@ execution of this prepared statement. This property is a wrapper around * `namedParameters` {Object} An optional object used to bind named parameters. The keys of this object are used to configure the mapping. -* `...anonymousParameters` {null|number|bigint|string|Buffer|Uint8Array} Zero or +* `...anonymousParameters` {null|number|bigint|string|Buffer|TypedArray|DataView} Zero or more values to bind to anonymous parameters. * Returns: {Object|undefined} An object corresponding to the first row returned by executing the prepared statement. The keys and values of the object @@ -374,11 +382,15 @@ values in `namedParameters` and `anonymousParameters`. * `namedParameters` {Object} An optional object used to bind named parameters. The keys of this object are used to configure the mapping. -* `...anonymousParameters` {null|number|bigint|string|Buffer|Uint8Array} Zero or +* `...anonymousParameters` {null|number|bigint|string|Buffer|TypedArray|DataView} Zero or more values to bind to anonymous parameters. * Returns: {Iterator} An iterable iterator of objects. Each object corresponds to a row returned by executing the prepared statement. The keys and values of each @@ -393,11 +405,15 @@ the values in `namedParameters` and `anonymousParameters`. * `namedParameters` {Object} An optional object used to bind named parameters. The keys of this object are used to configure the mapping. -* `...anonymousParameters` {null|number|bigint|string|Buffer|Uint8Array} Zero or +* `...anonymousParameters` {null|number|bigint|string|Buffer|TypedArray|DataView} Zero or more values to bind to anonymous parameters. * Returns: {Object} * `changes`: {number|bigint} The number of rows modified, inserted, or deleted diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 7f5e2f89ce9dba..5c289d4fda030a 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -930,7 +930,7 @@ bool StatementSync::BindParams(const FunctionCallbackInfo& args) { int anon_idx = 1; int anon_start = 0; - if (args[0]->IsObject() && !args[0]->IsUint8Array()) { + if (args[0]->IsObject() && !args[0]->IsArrayBufferView()) { Local obj = args[0].As(); Local context = obj->GetIsolate()->GetCurrentContext(); Local keys; @@ -1035,7 +1035,7 @@ bool StatementSync::BindValue(const Local& value, const int index) { statement_, index, *val, val.length(), SQLITE_TRANSIENT); } else if (value->IsNull()) { r = sqlite3_bind_null(statement_, index); - } else if (value->IsUint8Array()) { + } else if (value->IsArrayBufferView()) { ArrayBufferViewContents buf(value); r = sqlite3_bind_blob( statement_, index, buf.data(), buf.length(), SQLITE_TRANSIENT); diff --git a/test/parallel/test-sqlite-typed-array-and-data-view.js b/test/parallel/test-sqlite-typed-array-and-data-view.js new file mode 100644 index 00000000000000..1cc75c541b6261 --- /dev/null +++ b/test/parallel/test-sqlite-typed-array-and-data-view.js @@ -0,0 +1,61 @@ +'use strict'; +require('../common'); +const tmpdir = require('../common/tmpdir'); +const { join } = require('node:path'); +const { DatabaseSync } = require('node:sqlite'); +const { suite, test } = require('node:test'); +let cnt = 0; + +tmpdir.refresh(); + +function nextDb() { + return join(tmpdir.path, `database-${cnt++}.db`); +} + +const arrayBuffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]).buffer; +const TypedArrays = [ + ['Int8Array', Int8Array], + ['Uint8Array', Uint8Array], + ['Uint8ClampedArray', Uint8ClampedArray], + ['Int16Array', Int16Array], + ['Uint16Array', Uint16Array], + ['Int32Array', Int32Array], + ['Uint32Array', Uint32Array], + ['Float32Array', Float32Array], + ['Float64Array', Float64Array], + ['BigInt64Array', BigInt64Array], + ['BigUint64Array', BigUint64Array], + ['DataView', DataView], +]; + +suite('StatementSync with TypedArray/DataView', () => { + for (const [displayName, TypedArray] of TypedArrays) { + test(displayName, (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + db.exec('CREATE TABLE test (data BLOB)'); + // insert + { + const stmt = db.prepare('INSERT INTO test VALUES (?)'); + stmt.run(new TypedArray(arrayBuffer)); + } + // select all + { + const stmt = db.prepare('SELECT * FROM test'); + const row = stmt.get(); + t.assert.ok(row.data instanceof Uint8Array); + t.assert.strictEqual(row.data.length, 8); + t.assert.deepStrictEqual(row.data, new Uint8Array(arrayBuffer)); + } + // query + { + const stmt = db.prepare('SELECT * FROM test WHERE data = ?'); + const rows = stmt.all(new TypedArray(arrayBuffer)); + t.assert.strictEqual(rows.length, 1); + t.assert.ok(rows[0].data instanceof Uint8Array); + t.assert.strictEqual(rows[0].data.length, 8); + t.assert.deepStrictEqual(rows[0].data, new Uint8Array(arrayBuffer)); + } + }); + } +});