// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
// The four RPCs that make a statement a server-side object. `sp_prepare` compiles a text
// with a declaration and hands back a handle through an OUTPUT parameter; `sp_execute`
// runs that handle with new values; `sp_unprepare` releases it. The ids are the server's
// own — 11, 12 and 15 — so no name lookup is needed.
///|
/// Compile `sql` with `declaration` and return the handle the server assigned.
///
/// `@handle` is an `OUTPUT` parameter, which is why its status byte is set and why the
/// answer carries a `RETURNVALUE` token: an RPC with a by-reference parameter cannot report
/// through its result set.
async fn SqlServerConn::prepare_plan(
self : SqlServerConn,
sql : String,
declaration : String,
) -> Int32 raise @moondb.DbError {
// The text is compiled with `@p1`, `@p2`… in it, exactly as an `sp_executesql` call would
// carry it: the declaration names those slots, and a compiled plan has no `?` in it.
let call : Array[RpcParam] = [
{ name: "@handle", value: @moondb.Int(0), by_ref: true, },
{ name: "@params", value: @moondb.Text(declaration), by_ref: false, },
{ name: "@stmt", value: @moondb.Text(rewrite(sql)), by_ref: false, },
]
let response = self.exchange(build_rpc_message(11, call, self.txn))
match response.return_value {
Some(handle) =>
if handle == 0 {
raise @moondb.QueryError("tds: sp_prepare returned handle 0")
} else {
handle
}
None =>
raise @moondb.QueryError(
"tds: sp_prepare answered without a RETURNVALUE token",
)
}
}
///|
/// Run a compiled handle with `params`. The statement text is not sent again — that is the
/// whole point of the handle — so the values are positional and unnamed, matched to the
/// declaration the handle was compiled with.
async fn SqlServerConn::run_plan(
self : SqlServerConn,
handle : Int32,
params : Array[@moondb.Value],
) -> Response raise @moondb.DbError {
let call : Array[RpcParam] = [
{ name: "", value: @moondb.Int(handle), by_ref: false, },
]
for i = 0; i < params.length(); i = i + 1 {
call.push({ name: "", value: params[i], by_ref: false, })
}
self.exchange(build_rpc_message(12, call, self.txn))
}
///|
/// Release a compiled handle. The server frees the plan; a driver that forgot this would
/// leak one plan per statement for the life of the session, so `close` always calls it.
async fn SqlServerConn::unprepare(
self : SqlServerConn,
handle : Int32,
) -> Unit raise @moondb.DbError {
let call : Array[RpcParam] = [
{ name: "@handle", value: @moondb.Int(handle), by_ref: false, },
]
self.exchange(build_rpc_message(15, call, self.txn)) |> ignore
}