1414package io .trino .operator .scalar ;
1515
1616import com .fasterxml .jackson .core .JsonFactory ;
17+ import com .fasterxml .jackson .core .JsonGenerator ;
1718import com .fasterxml .jackson .core .JsonParser ;
1819import com .fasterxml .jackson .core .JsonToken ;
20+ import com .fasterxml .jackson .core .util .DefaultIndenter ;
21+ import com .fasterxml .jackson .core .util .DefaultPrettyPrinter ;
1922import com .fasterxml .jackson .databind .MappingJsonFactory ;
23+ import com .fasterxml .jackson .databind .ObjectMapper ;
2024import com .google .common .primitives .Doubles ;
25+ import io .airlift .json .ObjectMapperProvider ;
26+ import io .airlift .slice .DynamicSliceOutput ;
2127import io .airlift .slice .Slice ;
28+ import io .airlift .slice .SliceOutput ;
2229import io .trino .plugin .base .util .JsonTypeUtil ;
2330import io .trino .spi .TrinoException ;
2431import io .trino .spi .function .LiteralParameter ;
3239import io .trino .type .JsonPathType ;
3340
3441import java .io .IOException ;
42+ import java .io .OutputStream ;
3543import java .util .LinkedList ;
3644import java .util .List ;
3745
4553import static com .fasterxml .jackson .core .JsonToken .VALUE_NUMBER_INT ;
4654import static com .fasterxml .jackson .core .JsonToken .VALUE_STRING ;
4755import static com .fasterxml .jackson .core .JsonToken .VALUE_TRUE ;
56+ import static com .fasterxml .jackson .databind .SerializationFeature .ORDER_MAP_ENTRIES_BY_KEYS ;
4857import static io .airlift .slice .Slices .utf8Slice ;
4958import static io .trino .plugin .base .util .JsonUtils .jsonFactoryBuilder ;
5059import static io .trino .spi .StandardErrorCode .INVALID_FUNCTION_ARGUMENT ;
@@ -61,6 +70,9 @@ public final class JsonFunctions
6170 private static final JsonFactory MAPPING_JSON_FACTORY = new MappingJsonFactory ()
6271 .disable (CANONICALIZE_FIELD_NAMES );
6372
73+ private static final ObjectMapper SORTED_MAPPER = new ObjectMapperProvider ().get ()
74+ .configure (ORDER_MAP_ENTRIES_BY_KEYS , true );
75+
6476 private JsonFunctions () {}
6577
6678 @ ScalarOperator (OperatorType .CAST )
@@ -124,6 +136,41 @@ public static Slice jsonFormat(@SqlType(StandardTypes.JSON) Slice slice)
124136 return slice ;
125137 }
126138
139+ @ ScalarFunction
140+ @ SqlType (StandardTypes .VARCHAR )
141+ public static Slice jsonFormat (@ SqlType (StandardTypes .JSON ) Slice slice , @ SqlType (StandardTypes .BIGINT ) long indentSpaces )
142+ {
143+ if (indentSpaces < 0 ) {
144+ throw new TrinoException (INVALID_FUNCTION_ARGUMENT , "Indentation spaces must be non-negative, got: " + indentSpaces );
145+ }
146+ if (indentSpaces == 0 ) {
147+ return slice ;
148+ }
149+
150+ try {
151+ Object jsonValue ;
152+ try (JsonParser parser = createJsonParser (JSON_FACTORY , slice )) {
153+ jsonValue = SORTED_MAPPER .readValue (parser , Object .class );
154+ }
155+
156+ DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter ();
157+ String indentString = " " .repeat ((int ) indentSpaces );
158+ prettyPrinter .indentObjectsWith (DefaultIndenter .SYSTEM_LINEFEED_INSTANCE .withIndent (indentString ));
159+ prettyPrinter .indentArraysWith (DefaultIndenter .SYSTEM_LINEFEED_INSTANCE .withIndent (indentString ));
160+
161+ SliceOutput output = new DynamicSliceOutput ((int ) (slice .length () * 1.5 ));
162+ try (JsonGenerator generator = JSON_FACTORY .createGenerator ((OutputStream ) output )) {
163+ generator .setPrettyPrinter (prettyPrinter );
164+ SORTED_MAPPER .writeValue (generator , jsonValue );
165+ }
166+
167+ return output .slice ();
168+ }
169+ catch (IOException e ) {
170+ throw new TrinoException (INVALID_FUNCTION_ARGUMENT , "Failed to format JSON: " + truncateIfNecessaryForErrorMessage (slice ), e );
171+ }
172+ }
173+
127174 @ ScalarFunction
128175 @ LiteralParameters ("x" )
129176 @ SqlType (StandardTypes .JSON )
0 commit comments