Add native RPC calls for arbitrary stored procedures - #7
Open
Tawxyn wants to merge 4 commits into
Open
Conversation
RPC was previously reachable only for the built-in system procedures
(sp_prepare, sp_cursor*, ...), so invoking a user-defined procedure meant
falling back to a generated EXEC batch and losing typed output
parameters, return status, and server messages.
Add Client::call_procedure, built on the existing RPC send path and
response collectors rather than a parallel one: RpcParam gains an
optional type_info, and send_rpc now accepts a procedure name as well as
a numeric proc id.
Public API:
- ProcedureParameter carries an explicit TypeInfo independent of its
value, so a NULL output placeholder can declare a nullable wire type.
Binds positionally by default; .named() binds by name.
- ParameterDirection::{Input, Output, InputOutput}
- ProcedureResult exposes result sets, output values, return status and
informational messages.
- OutputValue::type_info() exposes the server's wire type.
The response is fully buffered, so the connection is reusable as soon as
the call returns, and cancellation works via cancellation_token() as it
does for query/execute.
RETURNSTATUS is surfaced as i32 rather than u32 so negative return codes
round-trip correctly.
Also fix two server-module bugs, both triggered by messages spanning more
than one TDS packet:
- TdsConnection::poll_next returned Poll::Pending after a read that did
not complete a message, with no waker registered, so any multi-packet
request hung forever.
- Multi-packet responses were framed into one accumulating buffer, but
Packet::encode back-patches the length at absolute offsets 2..4,
zeroing the second packet's length and corrupting the first.
BREAKING CHANGE: RpcOption, RpcProcId and RpcStatus move from the crate
root to tiberius::server, alongside the types that expose them
(RpcMessage, DecodedRpcParam). They are no longer part of the
client-facing surface.
Tests: 23 in-process (no DB) and 15 live covering parameter directions,
positional binding, bounded and MAX strings/binary, NULL outputs, numeric
precision/scale, multi-packet payloads (112 KB CLOB / 100 KB BLOB),
multiple result sets, errors, cancellation and connection reuse. Each
in-process server now runs on its own thread; smol's global executor
defaults to a single thread shared by the whole test binary, which
deadlocked once a test moved bulk data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
playlend
reviewed
Aug 26, 2026
playlend
reviewed
Aug 26, 2026
playlend
reviewed
Aug 26, 2026
| /// low 16 bits are `0xFFFF` emits the MAX sentinel. It is declared larger | ||
| /// than `0xFFFF` here because the encoder also treats it as a byte ceiling, | ||
| /// which would otherwise reject MAX values over 64 KiB. | ||
| const MAX_LEN: usize = 0xFFFF_FFFF; |
Collaborator
There was a problem hiding this comment.
this seems like a workaround the existing issue, perhaps we can address the issue in column_data.rs instead of relying on this const.
playlend
reviewed
Aug 26, 2026
| let packet = Packet::new(header, split_payload); | ||
| // `Packet::encode` back-patches the length at absolute offsets | ||
| // 2..4, so it must always be handed an empty buffer. | ||
| let mut framed = BytesMut::new(); |
Collaborator
There was a problem hiding this comment.
we need to fix the root cause in Packet::encode patching
playlend
reviewed
Aug 26, 2026
| let mut dst_fi = BytesMutWithTypeInfo::new(dst); | ||
| self.value.encode(&mut dst_fi)?; | ||
| match self.type_info { | ||
| Some(ty) => { |
Collaborator
There was a problem hiding this comment.
parameter names over 255 utf16 code units overflow a u8, we can do error::protocol to address it
Add call_procedure_without_rows for callers that need RPC outputs and metadata without retaining result rows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds Client::call_procedure for invoking named stored procedures through native TDS RPC with positional or named input, output, and input/output parameters.
Preserves declared wire types independently from values so typed NULL outputs, bounded and MAX strings/binary values, and numeric precision/scale round-trip correctly.
Exposes buffered result sets, output values, signed return status, and informational messages while keeping connections reusable after completion, errors, or cancellation.
Fixes multi-packet RPC reads and response framing to prevent hangs and corrupted payloads.