perf: eliminate Vec<char> allocation in split_sql_statements

- Replace sql.chars().collect::<Vec<char>>() with direct byte-level
  iteration over sql.as_bytes()
- Saves ~140 KB heap allocation for 35 KB schema (4× input size)
- Remove unused _start variable
- SQL is ASCII-safe so byte comparison is sufficient for all delimiters
This commit is contained in:
Dionisio
2026-03-06 22:44:04 +01:00
parent 55936699ce
commit fc78dcd35d
+38 -33
View File
@@ -204,49 +204,55 @@ async fn apply_schema(pool: &PgPool) -> Result<()> {
/// - Single-quoted strings (`'...'`) /// - Single-quoted strings (`'...'`)
/// - Line comments (`-- ...`) /// - Line comments (`-- ...`)
/// - Block comments (`/* ... */`) /// - Block comments (`/* ... */`)
///
/// Uses byte-level iteration over the `&str` directly — no intermediate
/// `Vec<char>` allocation (saves ~4× the input size in heap memory).
/// SQL is ASCII-safe, so byte comparison is sufficient for all delimiters.
fn split_sql_statements(sql: &str) -> Vec<String> { fn split_sql_statements(sql: &str) -> Vec<String> {
let mut statements = Vec::new(); let mut statements = Vec::new();
let mut current = String::new(); let mut current = String::new();
let chars: Vec<char> = sql.chars().collect(); let bytes = sql.as_bytes();
let len = chars.len(); let len = bytes.len();
let mut i = 0; let mut i = 0;
while i < len { while i < len {
let b = bytes[i];
// Line comment // Line comment
if i + 1 < len && chars[i] == '-' && chars[i + 1] == '-' { if b == b'-' && i + 1 < len && bytes[i + 1] == b'-' {
while i < len && chars[i] != '\n' { while i < len && bytes[i] != b'\n' {
current.push(chars[i]); current.push(bytes[i] as char);
i += 1; i += 1;
} }
continue; continue;
} }
// Block comment // Block comment
if i + 1 < len && chars[i] == '/' && chars[i + 1] == '*' { if b == b'/' && i + 1 < len && bytes[i + 1] == b'*' {
current.push(chars[i]); current.push('/');
current.push(chars[i + 1]); current.push('*');
i += 2; i += 2;
while i + 1 < len && !(chars[i] == '*' && chars[i + 1] == '/') { while i + 1 < len && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {
current.push(chars[i]); current.push(bytes[i] as char);
i += 1; i += 1;
} }
if i + 1 < len { if i + 1 < len {
current.push(chars[i]); current.push('*');
current.push(chars[i + 1]); current.push('/');
i += 2; i += 2;
} }
continue; continue;
} }
// Single-quoted string // Single-quoted string
if chars[i] == '\'' { if b == b'\'' {
current.push(chars[i]); current.push('\'');
i += 1; i += 1;
while i < len { while i < len {
current.push(chars[i]); current.push(bytes[i] as char);
if chars[i] == '\'' { if bytes[i] == b'\'' {
if i + 1 < len && chars[i + 1] == '\'' { if i + 1 < len && bytes[i + 1] == b'\'' {
current.push(chars[i + 1]); current.push('\'');
i += 2; i += 2;
} else { } else {
i += 1; i += 1;
@@ -260,32 +266,31 @@ fn split_sql_statements(sql: &str) -> Vec<String> {
} }
// Dollar-quoted string ($tag$...$tag$ or $$...$$) // Dollar-quoted string ($tag$...$tag$ or $$...$$)
if chars[i] == '$' { if b == b'$' {
let _start = i;
i += 1; i += 1;
let mut tag = String::from("$"); let mut tag = String::from("$");
while i < len && (chars[i].is_alphanumeric() || chars[i] == '_') { while i < len && (bytes[i].is_ascii_alphanumeric() || bytes[i] == b'_') {
tag.push(chars[i]); tag.push(bytes[i] as char);
i += 1; i += 1;
} }
if i < len && chars[i] == '$' { if i < len && bytes[i] == b'$' {
tag.push('$'); tag.push('$');
i += 1; i += 1;
// We have a dollar-quote tag, find the closing tag // We have a dollar-quote tag, find the closing tag
current.push_str(&tag); current.push_str(&tag);
let tag_bytes = tag.as_bytes();
loop { loop {
if i >= len { if i >= len {
break; break;
} }
if chars[i] == '$' { if bytes[i] == b'$' && i + tag_bytes.len() <= len
let remaining = &sql[i..]; && &bytes[i..i + tag_bytes.len()] == tag_bytes
if remaining.starts_with(&tag) { {
current.push_str(&tag); current.push_str(&tag);
i += tag.len(); i += tag_bytes.len();
break; break;
}
} }
current.push(chars[i]); current.push(bytes[i] as char);
i += 1; i += 1;
} }
} else { } else {
@@ -296,7 +301,7 @@ fn split_sql_statements(sql: &str) -> Vec<String> {
} }
// Statement separator // Statement separator
if chars[i] == ';' { if b == b';' {
current.push(';'); current.push(';');
let trimmed = current.trim().to_string(); let trimmed = current.trim().to_string();
if !trimmed.is_empty() && trimmed != ";" { if !trimmed.is_empty() && trimmed != ";" {
@@ -307,7 +312,7 @@ fn split_sql_statements(sql: &str) -> Vec<String> {
continue; continue;
} }
current.push(chars[i]); current.push(b as char);
i += 1; i += 1;
} }