// Helpers that encode client requests into PostgreSQL frontend messages.
///|
/// Encode a simple-query protocol request.
fn simple_query_bytes(sql : String) -> Bytes raise {
let buf = Buffer()
@frontend.query(@proto.utf8_encode(sql)[:], buf)
buf.to_bytes()
}
///|
/// Encode a standalone `Sync`.
fn sync_bytes() -> Bytes {
let buf = Buffer()
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Encode a graceful connection termination request.
fn terminate_bytes() -> Bytes {
let buf = Buffer()
@frontend.terminate(buf)
buf.to_bytes()
}
///|
/// Encode `Close` followed by `Sync` for a statement or portal.
fn close_bytes(kind : Byte, name : BytesView) -> Bytes raise {
let buf = Buffer()
@frontend.close(kind, name, buf)
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Encode Parse + Describe + Sync for a prepared statement.
fn prepare_bytes(
name : BytesView,
sql : String,
types : Array[Type],
) -> Bytes raise {
let buf = Buffer()
@frontend.parse(
name,
@proto.utf8_encode(sql)[:],
types.map(t => t.oid).iter(),
buf,
)
@frontend.describe(b'S', name, buf)
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Encode Bind + Execute + Sync for a prepared statement execution.
fn execute_statement_bytes(
statement_name : BytesView,
param_types : Array[Type],
params : Array[&ToSql],
) -> Bytes raise {
let buf = Buffer()
write_bind(buf, b""[:], statement_name, param_types, params, true)
@frontend.execute(b""[:], 0, buf)
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Encode Bind + Sync for portal creation.
fn bind_portal_bytes(
portal_name : BytesView,
statement_name : BytesView,
param_types : Array[Type],
params : Array[&ToSql],
) -> Bytes raise {
let buf = Buffer()
write_bind(buf, portal_name, statement_name, param_types, params, true)
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Encode Execute + Sync for portal execution.
fn execute_portal_bytes(portal_name : BytesView, max_rows : Int) -> Bytes raise {
let buf = Buffer()
@frontend.execute(portal_name, max_rows, buf)
@frontend.sync(buf)
buf.to_bytes()
}
///|
/// Serialized parameter payload plus its chosen wire format.
priv struct EncodedParam {
format : WireFormat
value : Bytes?
}
///|
/// Serialize one query parameter after validating type compatibility.
fn encode_param(param : &ToSql, type_ : Type) -> EncodedParam raise {
guard param.accepts(type_) else {
raise wrong_type_error(param.moonbit_type_name(), type_)
}
let payload = Buffer()
let value = match param.to_sql(type_, payload) {
Yes => None
No => Some(payload.to_bytes())
}
{ format: param.format(type_), value, }
}
///|
/// Write one encoded parameter into the bind payload.
fn write_encoded_param(
param : EncodedParam,
payload : @buffer.Buffer,
) -> @proto.IsNull {
match param.value {
None => Yes
Some(bytes) => {
payload.write_bytes(bytes)
No
}
}
}
///|
/// Encode a PostgreSQL `Bind` message and optionally describe the portal.
///
/// Each parameter is pre-serialized so codec failures happen before any bytes
/// are written to the request buffer.
fn write_bind(
buf : @buffer.Buffer,
portal : BytesView,
statement : BytesView,
param_types : Array[Type],
params : Array[&ToSql],
describe_portal : Bool,
) -> Unit raise {
let encoded_params : Array[EncodedParam] = []
for index, param in params {
encoded_params.push(encode_param(param, param_types[index]))
}
let formats = encoded_params.map(param => encode_wire_format(param.format))
@frontend.bind(
portal,
statement,
formats.iter(),
encoded_params.iter(),
(param, payload) => write_encoded_param(param, payload),
[1].iter(),
buf,
)
if describe_portal {
@frontend.describe(b'P', portal, buf)
}
}