Skip to content

NVarChar/VarBinary max values sent without a PLP terminator when length is omitted (TVP) or non-numeric — regression in 20.3.0 #1810

Closed

Description

@dhensby

Software versions

  • Tedious: 20.3.0 – 20.3.4 (20.2.3 is fine)
  • SQL Server: 2019, 2022 and 2025 (node-mssql CI); Azure SQL Edge (local)
  • Node.js: 22.x, 24.x

Additional Libraries Used and Versions
Found through node-mssql (tediousjs/node-mssql#1907), but reproducible with tedious alone. See below.

Table schema

create type dbo.probe as table (v nvarchar(50))

Connection configuration
Nothing special: default authentication, trustServerCertificate: true.

Problem description
Since 20.3.0, an NVarChar or VarBinary value whose length isn't comparable to a number is written with a max TYPE_INFO and PLP header but no PLP terminator. The plainest trigger is a TVP column with no length:

const { Request, TYPES } = require('tedious')
const req = new Request('select v from @t', console.log)
req.addParameter('t', TYPES.TVP, {
  name: 'probe',
  columns: [{ name: 'v', type: TYPES.NVarChar }], // no length
  rows: [['JP1016']]
})
connection.execSql(req)
20.2.3 20.3.0 – 20.3.4
TVP NVarChar / VarBinary column, length omitted ✅ ❌ stream ended unexpectedly
TVP NVarChar column, length: 50 ✅ ✅
addParameter NVarChar, length: NaN ✅ ❌ stream ended unexpectedly
VarChar, length omitted (TVP) or 'max' ✅ ✅

Cause: NVarChar.writeValue chooses the PLP header with parameter.length <= this.maximumLength (nvarchar.ts#L96) but writes the terminator under a separate parameter.length > this.maximumLength (#L113). For undefined/NaN, both comparisons are false. varbinary.ts has the same pattern (L84/L101). VarChar.writeValue uses if/else + writePlpValue and is fine. This looks like it came in with the write-contract refactor in #1774; the 20.2.3 generateParameterData was a single if/else.

Expected behavior
As in 20.2.3: the value is sent as a terminated max (PLP) value, or the length is rejected with a clear error.

Actual behavior
The PLP chunk is written without the trailing 00000000 terminator (the value bytes end ...68006900 instead of ...68006900 00000000), and the server rejects the request.

Error message/stack trace

RequestError: The incoming tabular data stream (TDS) protocol stream is incorrect. The stream ended unexpectedly.
    at RequestTokenHandler.onErrorMessage (node_modules/tedious/lib/token/handler.js:315:21)
    at Parser.run (node_modules/tedious/lib/token/token-stream-parser.js:52:35)

Any other details that can be helpful

  • Context: node-mssql passed length: 'max', which hits the same path. That's outside tedious's typed/documented contract (Infinity is the documented sentinel), and node-mssql now converts it on its side. The TVP and NaN cases above don't involve node-mssql at all.
  • Suggested fix: make the terminator part of the existing else branch (or reuse writePlpValue as VarChar does) in NVarChar and VarBinary. That restores the 20.2.3 behaviour and changes nothing for valid numeric lengths. Rejecting non-numeric lengths with an InputError might be nicer long term, but it would break callers that worked on 20.2.3.
  • feat: compile one writer per parameter or column, and read a row's max cells from a source #1780 looks like it would fix this as a side effect. Would a 20.3.x patch be possible in the meantime? Happy to send a PR.

Activity

  1. github-actions commented on Oct 6, 2026

    @github-actions
    Contributor

    🎉 This issue has been resolved in version 20.3.5 🎉

    The release is available on:

    Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions