1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use super::key::ParamPosition;
/// Parameterized query processor
///
/// Handling the parsing and binding of parameterized queries
pub struct ParameterizedQueryHandler;
impl ParameterizedQueryHandler {
/// Create a new parametric query processor.
pub fn new() -> Self {
Self
}
/// Extract the parameter positions from the query.
///
/// # Parameters
/// - `query`: query text
///
/// # Returns
/// Parameter Location List
pub fn extract_params(&self, query: &str) -> Vec<ParamPosition> {
self.extract_param_matches(query)
.into_iter()
.map(|(position, _)| position)
.collect()
}
/// Extract the parameter matches from the query together with their end
/// offsets. Assignment left-hand sides (`$var = ...`) are excluded, and
/// `@name` / `$N` occurrences inside string literals (`'...'` / `"..."`,
/// backslash escapes respected) are ignored so that literal content such
/// as an e-mail address does not look like a parameter.
fn extract_param_matches(&self, query: &str) -> Vec<(ParamPosition, usize)> {
let mut positions = Vec::new();
let chars: Vec<char> = query.chars().collect();
let mut i = 0usize;
let mut idx = 0usize;
let mut in_string: Option<char> = None;
while i < chars.len() {
let c = chars[i];
if let Some(quote) = in_string {
if c == '\\' {
i = (i + 2).min(chars.len());
continue;
}
if c == quote {
in_string = None;
}
i += 1;
continue;
}
match c {
'\'' | '"' => {
in_string = Some(c);
i += 1;
}
'@' => {
let mut j = i + 1;
if j < chars.len() && (chars[j].is_ascii_alphabetic() || chars[j] == '_') {
j += 1;
while j < chars.len()
&& (chars[j].is_ascii_alphanumeric() || chars[j] == '_')
{
j += 1;
}
let name: String = chars[i + 1..j].iter().collect();
// Skip the left-hand side of a variable assignment,
// e.g. `$result = GO ...` defines a session variable
// instead of declaring a named query parameter.
let after_match = &query[j..];
if !after_match.trim_start().starts_with('=') {
positions.push((
ParamPosition {
index: idx,
name: Some(name),
position: i,
expected_type: None,
},
j,
));
idx += 1;
}
i = j;
} else {
i += 1;
}
}
'$' => {
let mut j = i + 1;
let start = j;
while j < chars.len() && chars[j].is_ascii_digit() {
j += 1;
}
if j > start {
let digits: String = chars[start..j].iter().collect();
let after_match = &query[j..];
if !after_match.trim_start().starts_with('=') {
let parsed = digits.parse::<usize>().unwrap_or(idx);
positions.push((
ParamPosition {
index: parsed,
name: None,
position: i,
expected_type: None,
},
j,
));
idx += 1;
}
i = j;
} else {
i += 1;
}
}
_ => i += 1,
}
}
positions
}
/// Parameterize the query (replace parameters with placeholders)
///
/// # Parameters
/// - `query`: query text
///
/// # Returns
/// (parameterized query, parameter list)
pub fn parameterize(&self, query: &str) -> (String, Vec<ParamPosition>) {
let matches = self.extract_param_matches(query);
let positions = matches
.iter()
.map(|(position, _)| position.clone())
.collect::<Vec<_>>();
// Replace only the matches that were accepted as parameters so that
// assignment left-hand sides ($var = ...) stay intact in the template.
let mut parameterized = String::with_capacity(query.len());
let mut last_end = 0;
for (position, end) in matches {
parameterized.push_str(&query[last_end..position.position]);
parameterized.push('?');
last_end = end;
}
parameterized.push_str(&query[last_end..]);
(parameterized, positions)
}
}
impl Default for ParameterizedQueryHandler {
fn default() -> Self {
Self::new()
}
}