1use uqa_core::{
10 json::{decode_json_string_with_control, JsonReadError},
11 memory::{Produced, ProductionControl, ProductionString, ProductionVec},
12 Value,
13};
14
15use crate::error::{Result, SQLError};
16
17use super::validate_named_argument_order_with_control;
18
19const PARAMETER_NAMES: [&str; 2] = ["target", "strip_in_arrays"];
20
21pub fn argument_positions(
23 name: &str,
24 argument_names: &[Option<&str>],
25) -> Result<Option<Vec<usize>>> {
26 argument_positions_with_control(name, argument_names, &ProductionControl::uncontrolled()).map(
27 |positions| {
28 positions.map(|positions| {
29 positions
30 .into_uncontrolled()
31 .expect("ordinary JSON null-stripping argument positions")
32 })
33 },
34 )
35}
36
37pub fn argument_positions_with_control(
38 name: &str,
39 argument_names: &[Option<&str>],
40 control: &ProductionControl<'_>,
41) -> Result<Option<Produced<Vec<usize>>>> {
42 control.check()?;
43 validate_named_argument_order_with_control(argument_names.iter().copied(), control)?;
44 let function = name
45 .get(..11)
46 .filter(|prefix| prefix.eq_ignore_ascii_case("pg_catalog."))
47 .map_or(name, |_| &name[11..]);
48 if !(function.eq_ignore_ascii_case("json_strip_nulls")
49 || function.eq_ignore_ascii_case("jsonb_strip_nulls"))
50 || !(1..=2).contains(&argument_names.len())
51 {
52 return Ok(None);
53 }
54 let mut occupied = [false; PARAMETER_NAMES.len()];
55 let mut positions = ProductionVec::new(*control);
56 positions.reserve(argument_names.len())?;
57 let mut positional = 0usize;
58 for argument_name in argument_names {
59 let position = if let Some(argument_name) = argument_name {
60 PARAMETER_NAMES
61 .iter()
62 .position(|candidate| candidate == argument_name)
63 } else {
64 let position = positional;
65 positional += 1;
66 Some(position)
67 };
68 let Some(position) = position.filter(|position| *position < occupied.len()) else {
69 return Ok(None);
70 };
71 if occupied[position] {
72 return Ok(None);
73 }
74 occupied[position] = true;
75 positions.push_copy(position)?;
76 }
77 Ok(occupied[0].then(|| positions.finish()).transpose()?)
78}
79
80pub(super) fn reorder_named_values_with_control(
81 function: &str,
82 call_args: &[(Option<String>, Value)],
83 control: &ProductionControl<'_>,
84) -> Result<Option<Produced<Vec<Value>>>> {
85 let names = super::call_arguments::evaluated_argument_names_with_control(call_args, control)?;
86 let positions = match argument_positions_with_control(function, &names, control) {
87 Ok(Some(positions)) => positions,
88 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => return Err(error),
89 Ok(None) | Err(_) => return Ok(None),
90 };
91 let mut values = [None; PARAMETER_NAMES.len()];
92 for ((_, value), position) in call_args.iter().zip(positions.iter().copied()) {
93 values[position] = Some(value);
94 }
95 let default = Value::Bool(false);
96 values[1].get_or_insert(&default);
97 let mut output = ProductionVec::new(*control);
98 output.reserve(values.len())?;
99 for value in values {
100 let Some(value) = value else { return Ok(None) };
101 output.push_produced(control.copy_value(value)?)?;
102 }
103 Ok(Some(output.finish()?))
104}
105
106pub(super) fn invalid_json_input(input: &str) -> SQLError {
107 SQLError::Routine {
108 sqlstate: "22P02".into(),
109 message: format!("invalid input syntax for type json: \"{input}\""),
110 }
111}
112
113#[cfg(test)]
115pub(super) fn strip_json_nulls_text(input: &str, strip_in_arrays: bool) -> Result<String> {
116 Ok(strip_json_nulls_text_with_control(
117 input,
118 strip_in_arrays,
119 &ProductionControl::uncontrolled(),
120 )?
121 .into_uncontrolled()
122 .expect("ordinary JSON stripping has no lease"))
123}
124
125pub(super) fn strip_json_nulls_text_with_control(
126 input: &str,
127 strip_in_arrays: bool,
128 control: &ProductionControl<'_>,
129) -> Result<Produced<String>> {
130 let mut parser = JsonStripParser {
131 input,
132 position: 0,
133 strip_in_arrays,
134 control: *control,
135 };
136 let rendered = parser.parse_value(0)?;
137 parser.skip_whitespace()?;
138 if parser.position != input.len() {
139 return Err(invalid_json_input(input));
140 }
141 Ok(rendered.text)
142}
143
144struct RenderedJson {
145 text: Produced<String>,
146 is_null: bool,
147}
148
149struct JsonStripParser<'a, 'c> {
150 input: &'a str,
151 position: usize,
152 strip_in_arrays: bool,
153 control: ProductionControl<'c>,
154}
155
156impl JsonStripParser<'_, '_> {
157 const MAX_DEPTH: usize = 128;
158
159 fn parse_value(&mut self, depth: usize) -> Result<RenderedJson> {
160 self.control.check()?;
161 if depth > Self::MAX_DEPTH {
162 return Err(invalid_json_input(self.input));
163 }
164 self.skip_whitespace()?;
165 match self.peek() {
166 Some(b'{') => self.parse_object(depth),
167 Some(b'[') => self.parse_array(depth),
168 Some(b'"') => self.parse_string().map(|text| RenderedJson {
169 text,
170 is_null: false,
171 }),
172 Some(b't') => self.parse_literal("true", false),
173 Some(b'f') => self.parse_literal("false", false),
174 Some(b'n') => self.parse_literal("null", true),
175 Some(b'-' | b'0'..=b'9') => self.parse_number(),
176 _ => Err(invalid_json_input(self.input)),
177 }
178 }
179
180 fn parse_object(&mut self, depth: usize) -> Result<RenderedJson> {
181 self.position += 1;
182 self.skip_whitespace()?;
183 let mut fields = ProductionString::new(self.control);
184 fields.push('{')?;
185 let mut emitted = false;
186 if self.consume(b'}') {
187 fields.push('}')?;
188 return Ok(RenderedJson {
189 text: fields.finish()?,
190 is_null: false,
191 });
192 }
193 loop {
194 self.skip_whitespace()?;
195 if self.peek() != Some(b'"') {
196 return Err(invalid_json_input(self.input));
197 }
198 let key = self.parse_string()?;
199 self.skip_whitespace()?;
200 if !self.consume(b':') {
201 return Err(invalid_json_input(self.input));
202 }
203 let value = self.parse_value(depth + 1)?;
204 if !value.is_null {
205 if emitted {
206 fields.push(',')?;
207 }
208 fields.push_str(&key)?;
209 fields.push(':')?;
210 fields.push_str(&value.text)?;
211 emitted = true;
212 }
213 self.skip_whitespace()?;
214 if self.consume(b'}') {
215 break;
216 }
217 if !self.consume(b',') {
218 return Err(invalid_json_input(self.input));
219 }
220 }
221 fields.push('}')?;
222 Ok(RenderedJson {
223 text: fields.finish()?,
224 is_null: false,
225 })
226 }
227
228 fn parse_array(&mut self, depth: usize) -> Result<RenderedJson> {
229 self.position += 1;
230 self.skip_whitespace()?;
231 let mut elements = ProductionString::new(self.control);
232 elements.push('[')?;
233 let mut emitted = false;
234 if self.consume(b']') {
235 elements.push(']')?;
236 return Ok(RenderedJson {
237 text: elements.finish()?,
238 is_null: false,
239 });
240 }
241 loop {
242 let value = self.parse_value(depth + 1)?;
243 if !self.strip_in_arrays || !value.is_null {
244 if emitted {
245 elements.push(',')?;
246 }
247 elements.push_str(&value.text)?;
248 emitted = true;
249 }
250 self.skip_whitespace()?;
251 if self.consume(b']') {
252 break;
253 }
254 if !self.consume(b',') {
255 return Err(invalid_json_input(self.input));
256 }
257 }
258 elements.push(']')?;
259 Ok(RenderedJson {
260 text: elements.finish()?,
261 is_null: false,
262 })
263 }
264
265 fn parse_string(&mut self) -> Result<Produced<String>> {
266 let start = self.position;
267 self.position += 1;
268 while let Some(byte) = self.peek() {
269 self.control.check()?;
270 match byte {
271 b'"' => {
272 self.position += 1;
273 let source = &self.input[start..self.position];
274 let decoded = decode_json_string_with_control(source.as_bytes(), &self.control)
275 .map_err(|error| match error {
276 JsonReadError::InvalidJson => invalid_json_input(self.input),
277 JsonReadError::Memory(error) => error.into(),
278 JsonReadError::Cancelled(error) => error.into(),
279 })?;
280 return super::json::quote_with_control(&decoded, &self.control);
281 }
282 b'\\' => {
283 self.position += 1;
284 if self.peek().is_none() {
285 return Err(invalid_json_input(self.input));
286 }
287 self.position += 1;
288 }
289 _ => self.position += 1,
290 }
291 }
292 Err(invalid_json_input(self.input))
293 }
294
295 fn parse_literal(&mut self, literal: &str, is_null: bool) -> Result<RenderedJson> {
296 if !self.input[self.position..].starts_with(literal) {
297 return Err(invalid_json_input(self.input));
298 }
299 self.position += literal.len();
300 Ok(RenderedJson {
301 text: self.control.copy_text(literal)?,
302 is_null,
303 })
304 }
305
306 fn parse_number(&mut self) -> Result<RenderedJson> {
307 let start = self.position;
308 self.consume(b'-');
309 match self.peek() {
310 Some(b'0') => self.position += 1,
311 Some(b'1'..=b'9') => {
312 self.position += 1;
313 self.consume_digits()?;
314 }
315 _ => return Err(invalid_json_input(self.input)),
316 }
317 if self.consume(b'.') {
318 let digits = self.position;
319 self.consume_digits()?;
320 if digits == self.position {
321 return Err(invalid_json_input(self.input));
322 }
323 }
324 if matches!(self.peek(), Some(b'e' | b'E')) {
325 self.position += 1;
326 if matches!(self.peek(), Some(b'+' | b'-')) {
327 self.position += 1;
328 }
329 let digits = self.position;
330 self.consume_digits()?;
331 if digits == self.position {
332 return Err(invalid_json_input(self.input));
333 }
334 }
335 Ok(RenderedJson {
336 text: self.control.copy_text(&self.input[start..self.position])?,
337 is_null: false,
338 })
339 }
340
341 fn consume_digits(&mut self) -> Result<()> {
342 while matches!(self.peek(), Some(b'0'..=b'9')) {
343 self.control.check()?;
344 self.position += 1;
345 }
346 Ok(())
347 }
348
349 fn skip_whitespace(&mut self) -> Result<()> {
350 while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
351 self.control.check()?;
352 self.position += 1;
353 }
354 Ok(())
355 }
356
357 fn consume(&mut self, expected: u8) -> bool {
358 if self.peek() == Some(expected) {
359 self.position += 1;
360 true
361 } else {
362 false
363 }
364 }
365
366 fn peek(&self) -> Option<u8> {
367 self.input.as_bytes().get(self.position).copied()
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::{argument_positions, strip_json_nulls_text};
374
375 #[test]
376 fn json_strip_positions_accept_the_default_and_declaration_order_names() {
377 assert_eq!(
378 argument_positions("json_strip_nulls", &[None]).unwrap(),
379 Some(vec![0])
380 );
381 assert_eq!(
382 argument_positions(
383 "jsonb_strip_nulls",
384 &[Some("strip_in_arrays"), Some("target")]
385 )
386 .unwrap(),
387 Some(vec![1, 0])
388 );
389 assert_eq!(
390 argument_positions("json_strip_nulls", &[Some("strip_in_arrays")]).unwrap(),
391 None
392 );
393 assert_eq!(
394 argument_positions("json_strip_nulls", &[Some("unknown"), Some("target")]).unwrap(),
395 None
396 );
397 }
398
399 #[test]
400 fn textual_json_null_stripping_preserves_order_duplicates_and_number_lexemes() {
401 let input = r#" { "z" : 1.2300e+02, "a" : null, "z" : 2, "s" : "\u0061", "nested" : [null,{"drop":null,"keep":3}] } "#;
402 assert_eq!(
403 strip_json_nulls_text(input, false).unwrap(),
404 r#"{"z":1.2300e+02,"z":2,"s":"a","nested":[null,{"keep":3}]}"#
405 );
406 assert_eq!(
407 strip_json_nulls_text(input, true).unwrap(),
408 r#"{"z":1.2300e+02,"z":2,"s":"a","nested":[{"keep":3}]}"#
409 );
410 assert_eq!(strip_json_nulls_text("null", true).unwrap(), "null");
411 }
412
413 #[test]
414 fn textual_json_null_stripping_rejects_malformed_input_with_json_sqlstate() {
415 for input in [r#"{"a":}"#, r#"{"a":01}"#, r"[1,]", r#""\uD800""#] {
416 assert_eq!(
417 strip_json_nulls_text(input, false).unwrap_err().sqlstate(),
418 Some("22P02")
419 );
420 }
421 }
422}