Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions enginetest/queries/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,42 @@ Select * from (
{`[{"z": 456, "aa": 123}, {"Y": 654, "BB": 321}]`},
},
},
{
Query: `select json_pretty(c3) from jsontable`,
Expected: []sql.Row{
{
`{
"a": 2
}`,
},
{
`{
"b": 2
}`,
},
{
`{
"c": 2
}`,
},
{
`{
"d": 2
}`,
},
},
},
{
Query: `select json_pretty(json_object("id", 1, "name", "test"));`,
Expected: []sql.Row{
{
`{
"id": 1,
"name": "test"
}`,
},
},
},
{
Query: `SELECT column_0, sum(column_1) FROM
(values row(1,1), row(1,3), row(2,2), row(2,5), row(3,9)) a
Expand Down
100 changes: 100 additions & 0 deletions sql/expression/function/json/json_pretty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package json

import (
"encoding/json"
"fmt"

"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/types"
)

// JSONPretty (json_val)
//
// JSONPretty Provides pretty-printing of JSON values similar to that implemented in PHP and by other languages and
// database systems. The value supplied must be a JSON value or a valid string representation of a JSON value.
// Extraneous whitespaces and newlines present in this value have no effect on the output. For a NULL value, the
// function returns NULL. If the value is not a JSON document, or if it cannot be parsed as one, the function fails
// with an error. Formatting of the output from this function adheres to the following rules:
// - Each array element or object member appears on a separate line, indented by one additional level as compared to
// its parent.
// - Each level of indentation adds two leading spaces.
// - A comma separating individual array elements or object members is printed before the newline that separates the
// two elements or members.
// - The key and the value of an object member are separated by a colon followed by a space (': ').
// - An empty object or array is printed on a single line. No space is printed between the opening and closing brace.
// - Special characters in string scalars and key names are escaped employing the same rules used by JSONQuote.
//
// https://dev.mysql.com/doc/refman/8.0/en/json-utility-functions.html#function_json-pretty
type JSONPretty struct {
expression.UnaryExpression
}

var _ sql.FunctionExpression = &JSONPretty{}

// NewJSONPretty creates a new JSONPretty function.
func NewJSONPretty(arg sql.Expression) sql.Expression {
return &JSONPretty{expression.UnaryExpression{Child: arg}}
}

// FunctionName implements sql.FunctionExpression
func (j *JSONPretty) FunctionName() string {
return "json_pretty"
}

// Description implements sql.FunctionExpression
func (j *JSONPretty) Description() string {
return "prints a JSON document in human-readable format."
}

// String implements sql.Expression
func (j *JSONPretty) String() string {
return fmt.Sprintf("%s(%s)", j.FunctionName(), j.Child.String())
}

// Type implements sql.Expression
func (j *JSONPretty) Type() sql.Type {
return types.LongText
}

// Eval implements sql.Expression
func (j *JSONPretty) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
span, ctx := ctx.Span(fmt.Sprintf("function.%s", j.FunctionName()))
defer span.End()

doc, err := getJSONDocumentFromRow(ctx, row, j.Child)
if err != nil {
return nil, err
}
if doc == nil {
return nil, nil
}
res, err := json.MarshalIndent(doc.Val, "", " ")
if err != nil {
return nil, err
}

return string(res), nil
}

// WithChildren implements sql.Expression
func (j *JSONPretty) WithChildren(children ...sql.Expression) (sql.Expression, error) {
if len(children) != 1 {
return nil, sql.ErrInvalidChildrenNumber.New(j, len(children), 1)
}
return NewJSONPretty(children[0]), nil
}
123 changes: 123 additions & 0 deletions sql/expression/function/json/json_pretty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package json

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/go-mysql-server/sql/types"
)

func TestJSONPretty(t *testing.T) {
testCases := []struct {
arg sql.Expression
exp interface{}
err bool
}{
{
arg: expression.NewLiteral(``, types.Text),
err: true,
},
{
arg: expression.NewLiteral(`badjson`, types.Text),
err: true,
},

{
arg: expression.NewLiteral(nil, types.Null),
exp: nil,
},
{
arg: expression.NewLiteral(`null`, types.Text),
exp: `null`,
},
{
arg: expression.NewLiteral(`true`, types.Text),
exp: `true`,
},
{
arg: expression.NewLiteral(`false`, types.Text),
exp: `false`,
},
{
arg: expression.NewLiteral(`123`, types.Text),
exp: `123`,
},
{
arg: expression.NewLiteral(`123.456`, types.Text),
exp: `123.456`,
},
{
arg: expression.NewLiteral(`"hello"`, types.Text),
exp: `"hello"`,
},

{
arg: expression.NewLiteral(`[]`, types.Text),
exp: `[]`,
},
{
arg: expression.NewLiteral(`{}`, types.Text),
exp: `{}`,
},
{
arg: expression.NewLiteral(`[1,3,5]`, types.Text),
exp: `[
1,
3,
5
]`,
},
{
arg: expression.NewLiteral(`["a",1,{"key1": "value1"},"5", "77" , {"key2":["value3","valueX", "valueY"]},"j", "2" ]`, types.Text),
exp: `[
"a",
1,
{
"key1": "value1"
},
"5",
"77",
{
"key2": [
"value3",
"valueX",
"valueY"
]
},
"j",
"2"
]`,
},
}

for _, tt := range testCases {
t.Run(tt.arg.String(), func(t *testing.T) {
require := require.New(t)
f := NewJSONPretty(tt.arg)
res, err := f.Eval(sql.NewEmptyContext(), nil)
if tt.err {
require.Error(err)
return
}
require.NoError(err)
require.Equal(tt.exp, res)
})
}
}
43 changes: 0 additions & 43 deletions sql/expression/function/json/json_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,49 +208,6 @@ func (j JSONSchemaValidationReport) IsUnsupported() bool {
// JSON utility functions //
////////////////////////////

// JSON_PRETTY(json_val)
//
// JSONPretty Provides pretty-printing of JSON values similar to that implemented in PHP and by other languages and
// database systems. The value supplied must be a JSON value or a valid string representation of a JSON value.
// Extraneous whitespaces and newlines present in this value have no effect on the output. For a NULL value, the
// function returns NULL. If the value is not a JSON document, or if it cannot be parsed as one, the function fails
// with an error. Formatting of the output from this function adheres to the following rules:
// - Each array element or object member appears on a separate line, indented by one additional level as compared to
// its parent.
// - Each level of indentation adds two leading spaces.
// - A comma separating individual array elements or object members is printed before the newline that separates the
// two elements or members.
// - The key and the value of an object member are separated by a colon followed by a space (': ').
// - An empty object or array is printed on a single line. No space is printed between the opening and closing brace.
// - Special characters in string scalars and key names are escaped employing the same rules used by JSONQuote.
//
// https://dev.mysql.com/doc/refman/8.0/en/json-utility-functions.html#function_json-pretty
type JSONPretty struct {
sql.Expression
}

var _ sql.FunctionExpression = JSONPretty{}

// NewJSONPretty creates a new JSONPretty function.
func NewJSONPretty(args ...sql.Expression) (sql.Expression, error) {
return nil, ErrUnsupportedJSONFunction.New(JSONPretty{}.FunctionName())
}

// FunctionName implements sql.FunctionExpression
func (j JSONPretty) FunctionName() string {
return "json_pretty"
}

// Description implements sql.FunctionExpression
func (j JSONPretty) Description() string {
return "prints a JSON document in human-readable format."
}

// IsUnsupported implements sql.UnsupportedFunctionStub
func (j JSONPretty) IsUnsupported() bool {
return true
}

// JSON_STORAGE_FREE(json_val)
//
// JSONStorageFree For a JSON column value, this function shows how much storage space was freed in its binary
Expand Down
2 changes: 1 addition & 1 deletion sql/expression/function/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ var BuiltIns = []sql.Function{
sql.FunctionN{Name: "json_merge_preserve", Fn: json.NewJSONMergePreserve},
sql.FunctionN{Name: "json_object", Fn: json.NewJSONObject},
sql.FunctionN{Name: "json_overlaps", Fn: json.NewJSONOverlaps},
sql.FunctionN{Name: "json_pretty", Fn: json.NewJSONPretty},
sql.Function1{Name: "json_pretty", Fn: json.NewJSONPretty},
sql.Function1{Name: "json_quote", Fn: json.NewJSONQuote},
sql.FunctionN{Name: "json_remove", Fn: json.NewJSONRemove},
sql.FunctionN{Name: "json_replace", Fn: json.NewJSONReplace},
Expand Down