Skip to content

Commit

Permalink
support json_set/json_insert/json_replace(#2.0-dev) (#20592)
Browse files Browse the repository at this point in the history
support json_set/json_insert/json_replace

Approved by: @heni02, @m-schen, @XuPeng-SH, @aressu1985, @sukki37
  • Loading branch information
YANGGMM authored Dec 11, 2024
1 parent 9b4d020 commit 6b1e4ea
Show file tree
Hide file tree
Showing 15 changed files with 1,498 additions and 3 deletions.
245 changes: 245 additions & 0 deletions pkg/container/bytejson/bytejosn_modifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
// Copyright 2024 Matrix Origin
//
// 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 bytejson

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
)

func Test_Modify(t *testing.T) {
path, _ := ParseJsonPath("$.a")
type args struct {
pathList []*Path
valList []ByteJson
modifyType JsonModifyType
expectedErr bool
}
tests := []args{
// path is empty
{
pathList: []*Path{},
valList: []ByteJson{},
modifyType: JsonModifyReplace,
expectedErr: false,
},
// path length is not equal to val length
{
pathList: []*Path{},
valList: []ByteJson{Null},
modifyType: JsonModifyReplace,
expectedErr: true,
},
// modifyType is not valid
{

pathList: []*Path{&path},
valList: []ByteJson{Null},
modifyType: JsonModifyType(100),
expectedErr: true,
},
}

for _, test := range tests {
bj := Null
_, err := bj.Modify(test.pathList, test.valList, test.modifyType)
if test.expectedErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}

func TestAppendBinaryJSON(t *testing.T) {
tests := []struct {
name string
input any
wantType TpCode
wantErr bool
}{
{
name: "nil value",
input: nil,
wantType: TpCodeLiteral,
wantErr: false,
},
{
name: "bool true",
input: true,
wantType: TpCodeLiteral,
wantErr: false,
},
{
name: "bool false",
input: false,
wantType: TpCodeLiteral,
wantErr: false,
},
{
name: "int64",
input: int64(123),
wantType: TpCodeInt64,
wantErr: false,
},
{
name: "uint64",
input: uint64(123),
wantType: TpCodeUint64,
wantErr: false,
},
{
name: "float64",
input: float64(123.45),
wantType: TpCodeFloat64,
wantErr: false,
},
{
name: "string",
input: "test",
wantType: TpCodeString,
wantErr: false,
},
{
name: "array",
input: []any{int64(1), int64(2), true},
wantType: TpCodeArray,
wantErr: false,
},
{
name: "object",
input: map[string]any{"key": "value"},
wantType: TpCodeObject,
wantErr: false,
},
{
name: "invalid type",
input: struct{}{},
wantType: 0,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotType, gotBuf, err := appendBinaryJSON(nil, tt.input)

if tt.wantErr {
require.Error(t, err)
return
}

require.NoError(t, err)
require.Equal(t, tt.wantType, gotType)

require.NotEmpty(t, gotBuf)

switch tt.input.(type) {
case nil:
require.Equal(t, []byte{LiteralNull}, gotBuf)
case bool:
if tt.input.(bool) {
require.Equal(t, []byte{LiteralTrue}, gotBuf)
} else {
require.Equal(t, []byte{LiteralFalse}, gotBuf)
}
}
})
}
}

func TestAppendBinaryNumber(t *testing.T) {
tests := []struct {
name string
input json.Number
wantType TpCode
wantErr bool
}{
{
name: "int64",
input: json.Number("123"),
wantType: TpCodeInt64,
wantErr: false,
},
{
name: "uint64",
input: json.Number("18446744073709551615"),
wantType: TpCodeUint64,
wantErr: false,
},
{
name: "float64",
input: json.Number("123.45"),
wantType: TpCodeFloat64,
wantErr: false,
},
{
name: "invalid number",
input: json.Number("invalid"),
wantType: 0,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotType, gotBuf, err := appendBinaryNumber(nil, tt.input)

if tt.wantErr {
require.Error(t, err)
return
}

require.NoError(t, err)
require.Equal(t, tt.wantType, gotType)
require.NotEmpty(t, gotBuf)
})
}
}

func TestAppendBinaryString(t *testing.T) {
tests := []struct {
name string
input string
}{
{
name: "empty string",
input: "",
},
{
name: "simple string",
input: "test",
},
{
name: "unicode string",
input: "测试",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := appendBinaryString(nil, tt.input)
require.NotEmpty(t, got)

strLen, lenLen := calStrLen(got)
require.Equal(t, len(tt.input), strLen)
require.True(t, lenLen > 0)

require.Equal(t, tt.input, string(got[lenLen:]))
})
}
}
37 changes: 37 additions & 0 deletions pkg/container/bytejson/bytejson.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,43 @@ func (bj ByteJson) QuerySimple(paths []*Path) ByteJson {
}
}

func (bj ByteJson) Modify(pathList []*Path, valList []ByteJson, modifyType JsonModifyType) (ByteJson, error) {
var (
err error
)

if len(pathList) != len(valList) {
return Null, moerr.NewInvalidInputNoCtx("pathList and valList should have the same length")
}

if len(pathList) == 0 {
return bj, nil
}

for i := 0; i < len(pathList); i++ {
path := pathList[i]
val := valList[i]

modifier := &bytejsonModifier{bj: bj}

switch modifyType {
case JsonModifySet:
bj, err = modifier.set(path, val)
case JsonModifyInsert:
bj, err = modifier.insert(path, val)
case JsonModifyReplace:
bj, err = modifier.replace(path, val)
default:
return Null, moerr.NewInvalidInputNoCtx("invalid modify type")
}

if err != nil {
return Null, err
}
}
return bj, nil
}

func (bj ByteJson) canUnnest() bool {
return bj.Type == TpCodeArray || bj.Type == TpCodeObject
}
Expand Down
Loading

0 comments on commit 6b1e4ea

Please sign in to comment.