github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/json_diff.go (about)

     1  // Copyright 2023 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tree
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"reflect"
    22  	"strings"
    23  
    24  	"github.com/dolthub/go-mysql-server/sql/types"
    25  )
    26  
    27  type JsonDiff struct {
    28  	Key      string
    29  	From, To *types.JSONDocument
    30  	Type     DiffType
    31  }
    32  
    33  type jsonKeyPair struct {
    34  	key   string
    35  	value interface{}
    36  }
    37  
    38  // JsonDiffer computes the diff between two JSON objects.
    39  type JsonDiffer struct {
    40  	root                           string
    41  	currentFromPair, currentToPair *jsonKeyPair
    42  	from, to                       types.JSONIter
    43  	subDiffer                      *JsonDiffer
    44  }
    45  
    46  func NewJsonDiffer(root string, from, to types.JsonObject) JsonDiffer {
    47  	fromIter := types.NewJSONIter(from)
    48  	toIter := types.NewJSONIter(to)
    49  	return JsonDiffer{
    50  		root: root,
    51  		from: fromIter,
    52  		to:   toIter,
    53  	}
    54  }
    55  
    56  func (differ *JsonDiffer) appendKey(key string) string {
    57  	escapedKey := strings.Replace(key, "\"", "\\\"", -1)
    58  	return fmt.Sprintf("%s.\"%s\"", differ.root, escapedKey)
    59  }
    60  
    61  func (differ *JsonDiffer) Next() (diff JsonDiff, err error) {
    62  	for {
    63  		if differ.subDiffer != nil {
    64  			diff, err := differ.subDiffer.Next()
    65  			if err == io.EOF {
    66  				differ.subDiffer = nil
    67  				differ.currentFromPair = nil
    68  				differ.currentToPair = nil
    69  				continue
    70  			} else if err != nil {
    71  				return JsonDiff{}, err
    72  			}
    73  			return diff, nil
    74  		}
    75  		if differ.currentFromPair == nil && differ.from.HasNext() {
    76  			key, value, err := differ.from.Next()
    77  			if err != nil {
    78  				return JsonDiff{}, err
    79  			}
    80  			differ.currentFromPair = &jsonKeyPair{key, value}
    81  		}
    82  
    83  		if differ.currentToPair == nil && differ.to.HasNext() {
    84  			key, value, err := differ.to.Next()
    85  			if err != nil {
    86  				return JsonDiff{}, err
    87  			}
    88  			differ.currentToPair = &jsonKeyPair{key, value}
    89  		}
    90  
    91  		if differ.currentFromPair == nil && differ.currentToPair == nil {
    92  			return JsonDiff{}, io.EOF
    93  		}
    94  
    95  		var diffType DiffType
    96  
    97  		if differ.currentFromPair == nil && differ.currentToPair != nil {
    98  			diffType = AddedDiff
    99  		} else if differ.currentFromPair != nil && differ.currentToPair == nil {
   100  			diffType = RemovedDiff
   101  		} else { // differ.currentFromPair != nil && differ.currentToPair != nil
   102  			keyCmp := bytes.Compare([]byte(differ.currentFromPair.key), []byte(differ.currentToPair.key))
   103  			if keyCmp > 0 {
   104  				// `to` key comes before `from` key. Right key must have been inserted.
   105  				diffType = AddedDiff
   106  			} else if keyCmp < 0 {
   107  				// `to` key comes after `from` key. Right key must have been deleted.
   108  				diffType = RemovedDiff
   109  			} else {
   110  				key := differ.currentFromPair.key
   111  				fromValue := differ.currentFromPair.value
   112  				toValue := differ.currentToPair.value
   113  				if reflect.TypeOf(fromValue) != reflect.TypeOf(toValue) {
   114  					diffType = ModifiedDiff
   115  				} else {
   116  					switch from := fromValue.(type) {
   117  					case types.JsonObject:
   118  						// Recursively compare the objects to generate diffs.
   119  						subDiffer := NewJsonDiffer(differ.appendKey(key), from, toValue.(types.JsonObject))
   120  						differ.subDiffer = &subDiffer
   121  						continue
   122  					case types.JsonArray:
   123  						if reflect.DeepEqual(fromValue, toValue) {
   124  							differ.currentFromPair = nil
   125  							differ.currentToPair = nil
   126  							continue
   127  						} else {
   128  							diffType = ModifiedDiff
   129  						}
   130  					default:
   131  						if fromValue == toValue {
   132  							differ.currentFromPair = nil
   133  							differ.currentToPair = nil
   134  							continue
   135  						}
   136  						diffType = ModifiedDiff
   137  					}
   138  				}
   139  			}
   140  		}
   141  
   142  		switch diffType {
   143  		case AddedDiff:
   144  			key, value := differ.currentToPair.key, differ.currentToPair.value
   145  			result := JsonDiff{
   146  				Key:  differ.appendKey(key),
   147  				From: nil,
   148  				To:   &types.JSONDocument{Val: value},
   149  				Type: AddedDiff,
   150  			}
   151  			differ.currentToPair = nil
   152  			return result, nil
   153  		case RemovedDiff:
   154  			key, value := differ.currentFromPair.key, differ.currentFromPair.value
   155  			result := JsonDiff{
   156  				Key:  differ.appendKey(key),
   157  				From: &types.JSONDocument{Val: value},
   158  				To:   nil,
   159  				Type: RemovedDiff,
   160  			}
   161  			differ.currentFromPair = nil
   162  			return result, nil
   163  		case ModifiedDiff:
   164  			key := differ.currentFromPair.key
   165  			from := differ.currentFromPair.value
   166  			to := differ.currentToPair.value
   167  			result := JsonDiff{
   168  				Key:  differ.appendKey(key),
   169  				From: &types.JSONDocument{Val: from},
   170  				To:   &types.JSONDocument{Val: to},
   171  				Type: ModifiedDiff,
   172  			}
   173  			differ.currentFromPair = nil
   174  			differ.currentToPair = nil
   175  			return result, nil
   176  		default:
   177  			panic("unreachable")
   178  		}
   179  	}
   180  }