@@ -1400,9 +1400,15 @@ Napi::Value StatementSync::Run(const Napi::CallbackInfo &info) {
14001400 return env.Undefined ();
14011401 }
14021402
1403- int result = sqlite3_step (statement_);
1403+ // Execute the statement
1404+ sqlite3_step (statement_);
1405+ // Reset immediately after step to ensure sqlite3_changes() returns
1406+ // correct value. This fixes an issue where RETURNING queries would
1407+ // report changes: 0 on the first call.
1408+ // See: https://github.com/nodejs/node/issues/57344
1409+ int result = sqlite3_reset (statement_);
14041410
1405- if (result != SQLITE_DONE && result != SQLITE_ROW ) {
1411+ if (result != SQLITE_OK ) {
14061412 std::string error = sqlite3_errmsg (database_->connection ());
14071413 ThrowEnhancedSqliteErrorWithDB (env, database_, database_->connection (),
14081414 result, error);
@@ -1943,7 +1949,27 @@ void StatementSync::BindSingleParameter(int param_index, Napi::Value param) {
19431949 } else if (param.IsBoolean ()) {
19441950 sqlite3_bind_int (statement_, param_index,
19451951 param.As <Napi::Boolean>().Value () ? 1 : 0 );
1952+ } else if (param.IsDataView ()) {
1953+ // IMPORTANT: Check DataView BEFORE IsBuffer() because N-API's IsBuffer()
1954+ // returns true for ALL ArrayBufferViews (including DataView), but
1955+ // Buffer::As() doesn't work correctly for DataView (returns length=0).
1956+ Napi::DataView dataView = param.As <Napi::DataView>();
1957+ Napi::ArrayBuffer arrayBuffer = dataView.ArrayBuffer ();
1958+ size_t byteOffset = dataView.ByteOffset ();
1959+ size_t byteLength = dataView.ByteLength ();
1960+
1961+ if (arrayBuffer.Data () != nullptr && byteLength > 0 ) {
1962+ const uint8_t *data =
1963+ static_cast <const uint8_t *>(arrayBuffer.Data ()) + byteOffset;
1964+ sqlite3_bind_blob (statement_, param_index, data,
1965+ static_cast <int >(byteLength), SQLITE_TRANSIENT);
1966+ } else {
1967+ sqlite3_bind_null (statement_, param_index);
1968+ }
19461969 } else if (param.IsBuffer ()) {
1970+ // Handles Buffer and TypedArray (both are ArrayBufferViews that work
1971+ // correctly with Buffer cast - Buffer::Data() handles byte offsets
1972+ // internally)
19471973 Napi::Buffer<uint8_t > buffer = param.As <Napi::Buffer<uint8_t >>();
19481974 sqlite3_bind_blob (statement_, param_index, buffer.Data (),
19491975 static_cast <int >(buffer.Length ()), SQLITE_TRANSIENT);
@@ -1960,99 +1986,13 @@ void StatementSync::BindSingleParameter(int param_index, Napi::Value param) {
19601986 } else {
19611987 sqlite3_bind_null (statement_, param_index);
19621988 }
1963- } else if (param.IsTypedArray () || param.IsDataView ()) {
1964- // Handle TypedArray and DataView as binary data (both are ArrayBufferView
1965- // types)
1966- if (param.IsTypedArray ()) {
1967- // Handle TypedArray using native methods
1968- Napi::TypedArray typedArray = param.As <Napi::TypedArray>();
1969- Napi::ArrayBuffer arrayBuffer = typedArray.ArrayBuffer ();
1970-
1971- if (!arrayBuffer.IsUndefined () && arrayBuffer.Data () != nullptr ) {
1972- const uint8_t *data =
1973- static_cast <const uint8_t *>(arrayBuffer.Data ()) +
1974- typedArray.ByteOffset ();
1975- sqlite3_bind_blob (statement_, param_index, data,
1976- static_cast <int >(typedArray.ByteLength ()),
1977- SQLITE_TRANSIENT);
1978- } else {
1979- sqlite3_bind_null (statement_, param_index);
1980- }
1981- } else {
1982- // Handle DataView using Buffer.from(dataView.buffer, offset, length)
1983- // conversion
1984- try {
1985- Napi::Env env = param.Env ();
1986- Napi::Object dataViewObj = param.As <Napi::Object>();
1987-
1988- // Get DataView properties via JavaScript
1989- Napi::Value bufferValue = dataViewObj.Get (" buffer" );
1990- Napi::Value byteOffsetValue = dataViewObj.Get (" byteOffset" );
1991- Napi::Value byteLengthValue = dataViewObj.Get (" byteLength" );
1992-
1993- if (bufferValue.IsArrayBuffer () && byteOffsetValue.IsNumber () &&
1994- byteLengthValue.IsNumber ()) {
1995- // Create Buffer from underlying ArrayBuffer with proper offset and
1996- // length
1997- Napi::Function bufferFrom = env.Global ()
1998- .Get (" Buffer" )
1999- .As <Napi::Object>()
2000- .Get (" from" )
2001- .As <Napi::Function>();
2002- Napi::Value buffer = bufferFrom.Call (
2003- {bufferValue, byteOffsetValue, byteLengthValue});
2004-
2005- if (buffer.IsBuffer ()) {
2006- Napi::Buffer<uint8_t > buf = buffer.As <Napi::Buffer<uint8_t >>();
2007- if (buf.Length () > 0 ) {
2008- sqlite3_bind_blob (statement_, param_index, buf.Data (),
2009- static_cast <int >(buf.Length ()),
2010- SQLITE_TRANSIENT);
2011- } else {
2012- sqlite3_bind_null (statement_, param_index);
2013- }
2014- } else {
2015- sqlite3_bind_null (statement_, param_index);
2016- }
2017- } else {
2018- sqlite3_bind_null (statement_, param_index);
2019- }
2020- } catch (const Napi::Error &e) {
2021- // Re-throw Napi errors (preserves error message)
2022- throw ;
2023- } catch (const std::exception &e) {
2024- // If conversion fails, bind as NULL
2025- sqlite3_bind_null (statement_, param_index);
2026- }
2027- }
20281989 } else if (param.IsObject ()) {
2029- // Handle any other object that might be a DataView that wasn't caught
2030- // This is a fallback for objects that aren't explicitly handled above
2031- try {
2032- // Try to see if this is a DataView by checking for DataView properties
2033- Napi::Object obj = param.As <Napi::Object>();
2034- Napi::Value constructorName =
2035- obj.Get (" constructor" ).As <Napi::Object>().Get (" name" );
2036-
2037- if (constructorName.IsString () &&
2038- constructorName.As <Napi::String>().Utf8Value () == " DataView" ) {
2039- // TEMPORARY: Just bind DataView as NULL to test basic branching
2040- sqlite3_bind_null (statement_, param_index);
2041- } else {
2042- // Objects and arrays should throw error like Node.js does
2043- throw Napi::Error::New (
2044- Env (), " Provided value cannot be bound to SQLite parameter " +
2045- std::to_string (param_index) + " ." );
2046- }
2047- } catch (const Napi::Error &e) {
2048- // Re-throw Napi errors (preserves error message)
2049- throw ;
2050- } catch (const std::exception &e) {
2051- // If any property access fails, treat as non-bindable object
2052- throw Napi::Error::New (
2053- Env (), " Provided value cannot be bound to SQLite parameter " +
2054- std::to_string (param_index) + " ." );
2055- }
1990+ // Objects and arrays cannot be bound to SQLite parameters (same as
1991+ // Node.js behavior). Note: DataView, Buffer, TypedArray, and ArrayBuffer
1992+ // are handled above and don't reach this branch.
1993+ throw Napi::Error::New (
1994+ Env (), " Provided value cannot be bound to SQLite parameter " +
1995+ std::to_string (param_index) + " ." );
20561996 } else {
20571997 // For any other type, throw error like Node.js does
20581998 throw Napi::Error::New (
0 commit comments