// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Render one bound value as a MySQL SQL literal (UTF-8 bytes). Strings are
/// single-quoted with every metacharacter backslash-escaped; blobs use the
/// `x'…'` hex form; NULL/booleans/numbers are their plain literals.
fn render_value(v : @moondb.Value) -> Bytes {
match v {
Null => b"NULL"
Bool(true) => b"1"
Bool(false) => b"0"
Int(i) => string_to_bytes(i.to_string())
Int64(i) => string_to_bytes(i.to_string())
Double(d) => string_to_bytes(d.to_string())
Text(s) => {
let buf = Buffer()
buf.write_byte(b'\'')
escape_into(buf, string_to_bytes(s))
buf.write_byte(b'\'')
buf.to_bytes()
}
Blob(bytes) => {
let buf = Buffer()
buf.write_byte(b'x')
buf.write_byte(b'\'')
let digits = "0123456789abcdef"
for i in 0..> 4].to_int().to_byte())
buf.write_byte(digits[c & 0xF].to_int().to_byte())
}
buf.write_byte(b'\'')
buf.to_bytes()
}
}
}
///|
/// Append `bytes`, backslash-escaping the characters that would otherwise break
/// out of a single-quoted string literal. Because escaping happens on UTF-8
/// bytes and every escaped character is ASCII, multibyte text passes through
/// untouched.
fn escape_into(buf : Buffer, bytes : Bytes) -> Unit {
for i in 0.. Bytes raise MysqlError {
let src = string_to_bytes(sql)
let n = src.length()
let out = Buffer()
let mut i = 0
let mut pi = 0
let mut in_single = false
let mut in_double = false
let mut in_back = false
while i < n {
let b = src[i].to_int()
if in_single {
out.write_byte(src[i])
if b == 0x5C && i + 1 < n {
i += 1
out.write_byte(src[i])
} else if b == 0x27 {
in_single = false
}
i += 1
} else if in_double {
out.write_byte(src[i])
if b == 0x5C && i + 1 < n {
i += 1
out.write_byte(src[i])
} else if b == 0x22 {
in_double = false
}
i += 1
} else if in_back {
out.write_byte(src[i])
if b == 0x60 {
in_back = false
}
i += 1
} else if b == 0x27 {
in_single = true
out.write_byte(src[i])
i += 1
} else if b == 0x22 {
in_double = true
out.write_byte(src[i])
i += 1
} else if b == 0x60 {
in_back = true
out.write_byte(src[i])
i += 1
} else if b == 0x3F {
if pi >= params.length() {
raise ProtocolError(
"too many ? placeholders for the " +
params.length().to_string() +
" parameter(s) supplied",
)
}
out.write_bytes(render_value(params[pi])[:])
pi += 1
i += 1
} else {
out.write_byte(src[i])
i += 1
}
}
if pi != params.length() {
raise ProtocolError(
"parameter count mismatch: bound " +
pi.to_string() +
" placeholder(s) but " +
params.length().to_string() +
" parameter(s) were supplied",
)
}
out.to_bytes()
}