Skip to content

Commit 65897e9

Browse files
committed
fix(functions): add support for DataView and TypedArray return values in user-defined functions
1 parent d025ee2 commit 65897e9

File tree

3 files changed

+107
-0
lines changed

3 files changed

+107
-0
lines changed

src/aggregate_function.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,26 @@ void CustomAggregate::JSValueToSqliteResult(sqlite3_context *ctx,
578578
std::string str = value.As<Napi::String>().Utf8Value();
579579
sqlite3_result_text(ctx, str.c_str(), SafeCastToInt(str.length()),
580580
SQLITE_TRANSIENT);
581+
} else if (value.IsDataView()) {
582+
// IMPORTANT: Check DataView BEFORE IsBuffer() because N-API's IsBuffer()
583+
// returns true for ALL ArrayBufferViews (including DataView), but
584+
// Buffer::As() doesn't work correctly for DataView (returns length=0).
585+
// See: https://github.com/nodejs/node/pull/56227
586+
Napi::DataView dataView = value.As<Napi::DataView>();
587+
Napi::ArrayBuffer arrayBuffer = dataView.ArrayBuffer();
588+
size_t byteOffset = dataView.ByteOffset();
589+
size_t byteLength = dataView.ByteLength();
590+
591+
if (arrayBuffer.Data() != nullptr && byteLength > 0) {
592+
const uint8_t *data =
593+
static_cast<const uint8_t *>(arrayBuffer.Data()) + byteOffset;
594+
sqlite3_result_blob(ctx, data, SafeCastToInt(byteLength),
595+
SQLITE_TRANSIENT);
596+
} else {
597+
sqlite3_result_zeroblob(ctx, 0);
598+
}
581599
} else if (value.IsBuffer()) {
600+
// Handles both Node.js Buffer and TypedArrays (Uint8Array, etc.)
582601
Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
583602
sqlite3_result_blob(ctx, buffer.Data(), SafeCastToInt(buffer.Length()),
584603
SQLITE_TRANSIENT);

src/user_function.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,30 @@ void UserDefinedFunction::JSValueToSqliteResult(sqlite3_context *ctx,
231231
} catch (const std::overflow_error &) {
232232
sqlite3_result_error(ctx, "String value too long", -1);
233233
}
234+
} else if (value.IsDataView()) {
235+
// IMPORTANT: Check DataView BEFORE IsBuffer() because N-API's IsBuffer()
236+
// returns true for ALL ArrayBufferViews (including DataView), but
237+
// Buffer::As() doesn't work correctly for DataView (returns length=0).
238+
// See: https://github.com/nodejs/node/pull/56227
239+
Napi::DataView dataView = value.As<Napi::DataView>();
240+
Napi::ArrayBuffer arrayBuffer = dataView.ArrayBuffer();
241+
size_t byteOffset = dataView.ByteOffset();
242+
size_t byteLength = dataView.ByteLength();
243+
244+
if (arrayBuffer.Data() != nullptr && byteLength > 0) {
245+
const uint8_t *data =
246+
static_cast<const uint8_t *>(arrayBuffer.Data()) + byteOffset;
247+
try {
248+
sqlite3_result_blob(ctx, data, SafeCastToInt(byteLength),
249+
SQLITE_TRANSIENT);
250+
} catch (const std::overflow_error &) {
251+
sqlite3_result_error(ctx, "DataView too large", -1);
252+
}
253+
} else {
254+
sqlite3_result_zeroblob(ctx, 0);
255+
}
234256
} else if (value.IsBuffer()) {
257+
// Handles both Node.js Buffer and TypedArrays (Uint8Array, etc.)
235258
Napi::Buffer<uint8_t> buffer = value.As<Napi::Buffer<uint8_t>>();
236259
try {
237260
sqlite3_result_blob(ctx, buffer.Data(), SafeCastToInt(buffer.Length()),

test/user-functions.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,71 @@ describe("User-defined Functions Tests", () => {
129129
db.close();
130130
});
131131

132+
test("user-defined functions - DataView return values", () => {
133+
const db = new DatabaseSync(":memory:");
134+
135+
// Register a function that returns a DataView
136+
db.function("dataview_func", () => {
137+
const buffer = new ArrayBuffer(4);
138+
const view = new DataView(buffer);
139+
view.setUint8(0, 0xde);
140+
view.setUint8(1, 0xad);
141+
view.setUint8(2, 0xbe);
142+
view.setUint8(3, 0xef);
143+
return view;
144+
});
145+
146+
const stmt = db.prepare("SELECT dataview_func() as result");
147+
const result = stmt.get();
148+
149+
expect(Buffer.isBuffer(result.result)).toBe(true);
150+
expect(result.result).toEqual(Buffer.from([0xde, 0xad, 0xbe, 0xef]));
151+
152+
db.close();
153+
});
154+
155+
test("user-defined functions - TypedArray return values", () => {
156+
const db = new DatabaseSync(":memory:");
157+
158+
// Register a function that returns a Uint8Array
159+
db.function("uint8array_func", () => {
160+
return new Uint8Array([0x01, 0x02, 0x03, 0x04]);
161+
});
162+
163+
const stmt = db.prepare("SELECT uint8array_func() as result");
164+
const result = stmt.get();
165+
166+
expect(Buffer.isBuffer(result.result)).toBe(true);
167+
expect(result.result).toEqual(Buffer.from([0x01, 0x02, 0x03, 0x04]));
168+
169+
db.close();
170+
});
171+
172+
test("user-defined functions - DataView with offset and length", () => {
173+
const db = new DatabaseSync(":memory:");
174+
175+
// Register a function that returns a DataView with non-zero offset
176+
db.function("dataview_offset_func", () => {
177+
const buffer = new ArrayBuffer(8);
178+
const fullView = new DataView(buffer);
179+
// Fill the whole buffer
180+
for (let i = 0; i < 8; i++) {
181+
fullView.setUint8(i, i + 1);
182+
}
183+
// Return a view that starts at offset 2 and has length 4
184+
return new DataView(buffer, 2, 4);
185+
});
186+
187+
const stmt = db.prepare("SELECT dataview_offset_func() as result");
188+
const result = stmt.get();
189+
190+
expect(Buffer.isBuffer(result.result)).toBe(true);
191+
// Should contain bytes at positions 2,3,4,5 which are 3,4,5,6
192+
expect(result.result).toEqual(Buffer.from([0x03, 0x04, 0x05, 0x06]));
193+
194+
db.close();
195+
});
196+
132197
test("user-defined functions - empty string handling", () => {
133198
const db = new DatabaseSync(":memory:");
134199

0 commit comments

Comments
 (0)