Skip to content

Commit 2d9a54c

Browse files
committed
Merge 'implement json_pretty' from Pedro Muniz
This PR implements json_pretty. At the moment, support for jsonb is being added, so this function suffers from the same limitations as in json(x). Also, I have not found a way to implement the same conversion of Blob -> String that SQLite does. From my own experimentation, I believe SQLite converts blobs to a lossy ascii representation, but I would appreciate some help on this. Closes #860
2 parents 0050f4a + 2e115d9 commit 2d9a54c

File tree

7 files changed

+237
-18
lines changed

7 files changed

+237
-18
lines changed

COMPAT.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This document describes the compatibility of Limbo with SQLite.
2323
- [Extensions](#extensions)
2424
- [UUID](#uuid)
2525
- [regexp](#regexp)
26+
- [Vector](#vector)
2627

2728
## Features
2829

@@ -349,7 +350,7 @@ Modifiers:
349350
#### JSON functions
350351

351352
| Function | Status | Comment |
352-
|------------------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------|
353+
| ---------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
353354
| json(json) | Partial | |
354355
| jsonb(json) | | |
355356
| json_array(value1,value2,...) | Yes | |
@@ -367,7 +368,7 @@ Modifiers:
367368
| jsonb_object(label1,value1,...) | | |
368369
| json_patch(json1,json2) | Yes | |
369370
| jsonb_patch(json1,json2) | | |
370-
| json_pretty(json) | | |
371+
| json_pretty(json) | Partial | Shares same json(val) limitations. Also, when passing blobs for indentation, conversion is not exactly the same as in SQLite |
371372
| json_remove(json,path,...) | Partial | Uses same json path parser as json_extract so shares same limitations. |
372373
| jsonb_remove(json,path,...) | | |
373374
| json_replace(json,path,value,...) | | |

core/function.rs

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub enum JsonFunc {
8282
JsonValid,
8383
JsonPatch,
8484
JsonRemove,
85+
JsonPretty,
8586
}
8687

8788
#[cfg(feature = "json")]
@@ -103,6 +104,7 @@ impl Display for JsonFunc {
103104
Self::JsonValid => "json_valid".to_string(),
104105
Self::JsonPatch => "json_patch".to_string(),
105106
Self::JsonRemove => "json_remove".to_string(),
107+
Self::JsonPretty => "json_pretty".to_string(),
106108
}
107109
)
108110
}
@@ -534,6 +536,8 @@ impl Func {
534536
"json_patch" => Ok(Self::Json(JsonFunc::JsonPatch)),
535537
#[cfg(feature = "json")]
536538
"json_remove" => Ok(Self::Json(JsonFunc::JsonRemove)),
539+
#[cfg(feature = "json")]
540+
"json_pretty" => Ok(Self::Json(JsonFunc::JsonPretty)),
537541
"unixepoch" => Ok(Self::Scalar(ScalarFunc::UnixEpoch)),
538542
"julianday" => Ok(Self::Scalar(ScalarFunc::JulianDay)),
539543
"hex" => Ok(Self::Scalar(ScalarFunc::Hex)),

core/json/mod.rs

+22-15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub use crate::json::ser::to_string;
1515
use crate::types::{LimboText, OwnedValue, TextSubtype};
1616
use indexmap::IndexMap;
1717
use jsonb::Error as JsonbError;
18+
use ser::to_string_pretty;
1819
use serde::{Deserialize, Serialize};
1920

2021
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
@@ -31,7 +32,7 @@ pub enum Val {
3132
Object(Vec<(String, Val)>),
3233
}
3334

34-
pub fn get_json(json_value: &OwnedValue) -> crate::Result<OwnedValue> {
35+
pub fn get_json(json_value: &OwnedValue, indent: Option<&str>) -> crate::Result<OwnedValue> {
3536
match json_value {
3637
OwnedValue::Text(ref t) => {
3738
// optimization: once we know the subtype is a valid JSON, we do not have
@@ -41,7 +42,10 @@ pub fn get_json(json_value: &OwnedValue) -> crate::Result<OwnedValue> {
4142
}
4243

4344
let json_val = get_json_value(json_value)?;
44-
let json = to_string(&json_val).unwrap();
45+
let json = match indent {
46+
Some(indent) => to_string_pretty(&json_val, indent).unwrap(),
47+
None => to_string(&json_val).unwrap(),
48+
};
4549

4650
Ok(OwnedValue::Text(LimboText::json(Rc::new(json))))
4751
}
@@ -57,7 +61,10 @@ pub fn get_json(json_value: &OwnedValue) -> crate::Result<OwnedValue> {
5761
OwnedValue::Null => Ok(OwnedValue::Null),
5862
_ => {
5963
let json_val = get_json_value(json_value)?;
60-
let json = to_string(&json_val).unwrap();
64+
let json = match indent {
65+
Some(indent) => to_string_pretty(&json_val, indent).unwrap(),
66+
None => to_string(&json_val).unwrap(),
67+
};
6168

6269
Ok(OwnedValue::Text(LimboText::json(Rc::new(json))))
6370
}
@@ -536,7 +543,7 @@ mod tests {
536543
#[test]
537544
fn test_get_json_valid_json5() {
538545
let input = OwnedValue::build_text(Rc::new("{ key: 'value' }".to_string()));
539-
let result = get_json(&input).unwrap();
546+
let result = get_json(&input, None).unwrap();
540547
if let OwnedValue::Text(result_str) = result {
541548
assert!(result_str.value.contains("\"key\":\"value\""));
542549
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -548,7 +555,7 @@ mod tests {
548555
#[test]
549556
fn test_get_json_valid_json5_double_single_quotes() {
550557
let input = OwnedValue::build_text(Rc::new("{ key: ''value'' }".to_string()));
551-
let result = get_json(&input).unwrap();
558+
let result = get_json(&input, None).unwrap();
552559
if let OwnedValue::Text(result_str) = result {
553560
assert!(result_str.value.contains("\"key\":\"value\""));
554561
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -560,7 +567,7 @@ mod tests {
560567
#[test]
561568
fn test_get_json_valid_json5_infinity() {
562569
let input = OwnedValue::build_text(Rc::new("{ \"key\": Infinity }".to_string()));
563-
let result = get_json(&input).unwrap();
570+
let result = get_json(&input, None).unwrap();
564571
if let OwnedValue::Text(result_str) = result {
565572
assert!(result_str.value.contains("{\"key\":9e999}"));
566573
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -572,7 +579,7 @@ mod tests {
572579
#[test]
573580
fn test_get_json_valid_json5_negative_infinity() {
574581
let input = OwnedValue::build_text(Rc::new("{ \"key\": -Infinity }".to_string()));
575-
let result = get_json(&input).unwrap();
582+
let result = get_json(&input, None).unwrap();
576583
if let OwnedValue::Text(result_str) = result {
577584
assert!(result_str.value.contains("{\"key\":-9e999}"));
578585
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -584,7 +591,7 @@ mod tests {
584591
#[test]
585592
fn test_get_json_valid_json5_nan() {
586593
let input = OwnedValue::build_text(Rc::new("{ \"key\": NaN }".to_string()));
587-
let result = get_json(&input).unwrap();
594+
let result = get_json(&input, None).unwrap();
588595
if let OwnedValue::Text(result_str) = result {
589596
assert!(result_str.value.contains("{\"key\":null}"));
590597
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -596,7 +603,7 @@ mod tests {
596603
#[test]
597604
fn test_get_json_invalid_json5() {
598605
let input = OwnedValue::build_text(Rc::new("{ key: value }".to_string()));
599-
let result = get_json(&input);
606+
let result = get_json(&input, None);
600607
match result {
601608
Ok(_) => panic!("Expected error for malformed JSON"),
602609
Err(e) => assert!(e.to_string().contains("malformed JSON")),
@@ -606,7 +613,7 @@ mod tests {
606613
#[test]
607614
fn test_get_json_valid_jsonb() {
608615
let input = OwnedValue::build_text(Rc::new("{\"key\":\"value\"}".to_string()));
609-
let result = get_json(&input).unwrap();
616+
let result = get_json(&input, None).unwrap();
610617
if let OwnedValue::Text(result_str) = result {
611618
assert!(result_str.value.contains("\"key\":\"value\""));
612619
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -618,7 +625,7 @@ mod tests {
618625
#[test]
619626
fn test_get_json_invalid_jsonb() {
620627
let input = OwnedValue::build_text(Rc::new("{key:\"value\"".to_string()));
621-
let result = get_json(&input);
628+
let result = get_json(&input, None);
622629
match result {
623630
Ok(_) => panic!("Expected error for malformed JSON"),
624631
Err(e) => assert!(e.to_string().contains("malformed JSON")),
@@ -629,7 +636,7 @@ mod tests {
629636
fn test_get_json_blob_valid_jsonb() {
630637
let binary_json = b"\x40\0\0\x01\x10\0\0\x03\x10\0\0\x03\x61\x73\x64\x61\x64\x66".to_vec();
631638
let input = OwnedValue::Blob(Rc::new(binary_json));
632-
let result = get_json(&input).unwrap();
639+
let result = get_json(&input, None).unwrap();
633640
if let OwnedValue::Text(result_str) = result {
634641
assert!(result_str.value.contains("\"asd\":\"adf\""));
635642
assert_eq!(result_str.subtype, TextSubtype::Json);
@@ -642,7 +649,7 @@ mod tests {
642649
fn test_get_json_blob_invalid_jsonb() {
643650
let binary_json: Vec<u8> = vec![0xA2, 0x62, 0x6B, 0x31, 0x62, 0x76]; // Incomplete binary JSON
644651
let input = OwnedValue::Blob(Rc::new(binary_json));
645-
let result = get_json(&input);
652+
let result = get_json(&input, None);
646653
match result {
647654
Ok(_) => panic!("Expected error for malformed JSON"),
648655
Err(e) => assert!(e.to_string().contains("malformed JSON")),
@@ -652,7 +659,7 @@ mod tests {
652659
#[test]
653660
fn test_get_json_non_text() {
654661
let input = OwnedValue::Null;
655-
let result = get_json(&input).unwrap();
662+
let result = get_json(&input, None).unwrap();
656663
if let OwnedValue::Null = result {
657664
// Test passed
658665
} else {
@@ -809,7 +816,7 @@ mod tests {
809816
#[test]
810817
fn test_json_array_length_simple_json_subtype() {
811818
let input = OwnedValue::build_text(Rc::new("[1,2,3]".to_string()));
812-
let wrapped = get_json(&input).unwrap();
819+
let wrapped = get_json(&input, None).unwrap();
813820
let result = json_array_length(&wrapped, None).unwrap();
814821

815822
if let OwnedValue::Integer(res) = result {

core/json/ser.rs

+39
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ where
2525
Ok(string)
2626
}
2727

28+
/// Attempts to serialize the input as a JSON5 string (actually a JSON string).
29+
pub fn to_string_pretty<T>(value: &T, indent: &str) -> Result<String>
30+
where
31+
T: Serialize,
32+
{
33+
let vec = to_vec_pretty(value, indent)?;
34+
let string = String::from_utf8(vec).map_err(|err| Error::from(err.utf8_error()))?;
35+
Ok(string)
36+
}
37+
2838
struct Serializer<W, F = CompactFormatter> {
2939
writer: W,
3040
formatter: F,
@@ -39,6 +49,17 @@ where
3949
}
4050
}
4151

52+
impl<'a, W> Serializer<W, PrettyFormatter<'a>>
53+
where
54+
W: io::Write,
55+
{
56+
/// Creates a new JSON pretty print serializer.
57+
#[inline]
58+
pub fn pretty(writer: W, indent: &'a str) -> Self {
59+
Serializer::with_formatter(writer, PrettyFormatter::with_indent(indent.as_bytes()))
60+
}
61+
}
62+
4263
impl<W, F> Serializer<W, F>
4364
where
4465
W: io::Write,
@@ -553,6 +574,24 @@ where
553574
Ok(writer)
554575
}
555576

577+
pub fn to_writer_pretty<W, T>(writer: W, value: &T, indent: &str) -> Result<()>
578+
where
579+
W: io::Write,
580+
T: ?Sized + Serialize,
581+
{
582+
let mut ser = Serializer::pretty(writer, indent);
583+
value.serialize(&mut ser)
584+
}
585+
586+
pub fn to_vec_pretty<T>(value: &T, indent: &str) -> Result<Vec<u8>>
587+
where
588+
T: ?Sized + Serialize,
589+
{
590+
let mut writer = Vec::with_capacity(128);
591+
to_writer_pretty(&mut writer, value, indent)?;
592+
Ok(writer)
593+
}
594+
556595
/// Represents a character escape code in a type-safe manner.
557596
pub enum CharEscape {
558597
/// An escaped quote `"`

core/translate/expr.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,18 @@ pub fn translate_expr(
10151015
});
10161016
Ok(target_register)
10171017
}
1018+
JsonFunc::JsonPretty => {
1019+
let args = expect_arguments_max!(args, 2, j);
1020+
1021+
translate_function(
1022+
program,
1023+
args,
1024+
referenced_tables,
1025+
resolver,
1026+
target_register,
1027+
func_ctx,
1028+
)
1029+
}
10181030
},
10191031
Func::Scalar(srf) => {
10201032
match srf {

core/vdbe/mod.rs

+35-1
Original file line numberDiff line numberDiff line change
@@ -1699,7 +1699,7 @@ impl Program {
16991699
crate::function::Func::Json(json_func) => match json_func {
17001700
JsonFunc::Json => {
17011701
let json_value = &state.registers[*start_reg];
1702-
let json_str = get_json(json_value);
1702+
let json_str = get_json(json_value, None);
17031703
match json_str {
17041704
Ok(json) => state.registers[*dest] = json,
17051705
Err(e) => return Err(e),
@@ -1796,6 +1796,40 @@ impl Program {
17961796
&state.registers[*start_reg..*start_reg + arg_count],
17971797
)?;
17981798
}
1799+
JsonFunc::JsonPretty => {
1800+
let json_value = &state.registers[*start_reg];
1801+
let indent = if arg_count > 1 {
1802+
Some(&state.registers[*start_reg + 1])
1803+
} else {
1804+
None
1805+
};
1806+
1807+
// Blob should be converted to Ascii in a lossy way
1808+
// However, Rust strings uses utf-8
1809+
// so the behavior at the moment is slightly different
1810+
// To the way blobs are parsed here in SQLite.
1811+
let indent = match indent {
1812+
Some(value) => match value {
1813+
OwnedValue::Text(text) => text.value.as_str(),
1814+
OwnedValue::Integer(val) => &val.to_string(),
1815+
OwnedValue::Float(val) => &val.to_string(),
1816+
OwnedValue::Blob(val) => &String::from_utf8_lossy(val),
1817+
OwnedValue::Agg(ctx) => match ctx.final_value() {
1818+
OwnedValue::Text(text) => text.value.as_str(),
1819+
OwnedValue::Integer(val) => &val.to_string(),
1820+
OwnedValue::Float(val) => &val.to_string(),
1821+
OwnedValue::Blob(val) => &String::from_utf8_lossy(val),
1822+
_ => " ",
1823+
},
1824+
_ => " ",
1825+
},
1826+
// If the second argument is omitted or is NULL, then indentation is four spaces per level
1827+
None => " ",
1828+
};
1829+
1830+
let json_str = get_json(json_value, Some(indent))?;
1831+
state.registers[*dest] = json_str;
1832+
}
17991833
},
18001834
crate::function::Func::Scalar(scalar_func) => match scalar_func {
18011835
ScalarFunc::Cast => {

0 commit comments

Comments
 (0)