diff --git a/CHANGELOG.md b/CHANGELOG.md index 16bd687ac..426cc7cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ and Yorkie adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) - Add `--auth-webhook-url`, `--name` flag to updateProject command: #376 +### Changed + +- Rename package names to match JS SDK: #395 + ## [0.2.15] - 2022-08-08 ### Added diff --git a/api/converter/converter_test.go b/api/converter/converter_test.go index c6b41b6ca..b69138fff 100644 --- a/api/converter/converter_test.go +++ b/api/converter/converter_test.go @@ -27,7 +27,7 @@ import ( "github.com/yorkie-team/yorkie/api/types" api "github.com/yorkie-team/yorkie/api/yorkie/v1" "github.com/yorkie-team/yorkie/pkg/document" - "github.com/yorkie-team/yorkie/pkg/document/proxy" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -39,14 +39,14 @@ func TestConverter(t *testing.T) { doc := document.New("d1") - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewText("k1").Edit(0, 0, "A") return nil }) assert.NoError(t, err) assert.Equal(t, `{"k1":"A"}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewText("k1").Edit(0, 0, "B") return nil }) @@ -64,7 +64,7 @@ func TestConverter(t *testing.T) { t.Run("snapshot test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { // an object and primitive types root.SetNewObject("k1"). SetNull("k1.0"). @@ -126,7 +126,7 @@ func TestConverter(t *testing.T) { t.Run("change pack test", func(t *testing.T) { d1 := document.New("d1") - err := d1.Update(func(root *proxy.ObjectProxy) error { + err := d1.Update(func(root *json.Object) error { // an object and primitive types root.SetNewObject("k1"). SetBool("k1.1", true). diff --git a/api/converter/from_bytes.go b/api/converter/from_bytes.go index b8dc09bd1..1d0c4d6eb 100644 --- a/api/converter/from_bytes.go +++ b/api/converter/from_bytes.go @@ -22,14 +22,14 @@ import ( "github.com/gogo/protobuf/proto" api "github.com/yorkie-team/yorkie/api/yorkie/v1" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) // BytesToObject creates an Object from the given byte array. -func BytesToObject(snapshot []byte) (*json.Object, error) { +func BytesToObject(snapshot []byte) (*crdt.Object, error) { if snapshot == nil { - return json.NewObject(json.NewRHTPriorityQueueMap(), time.InitialTicket), nil + return crdt.NewObject(crdt.NewRHTPriorityQueueMap(), time.InitialTicket), nil } pbElem := &api.JSONElement{} @@ -45,7 +45,7 @@ func BytesToObject(snapshot []byte) (*json.Object, error) { return obj, nil } -func fromJSONElement(pbElem *api.JSONElement) (json.Element, error) { +func fromJSONElement(pbElem *api.JSONElement) (crdt.Element, error) { switch decoded := pbElem.Body.(type) { case *api.JSONElement_JsonObject: return fromJSONObject(decoded.JsonObject) @@ -64,8 +64,8 @@ func fromJSONElement(pbElem *api.JSONElement) (json.Element, error) { } } -func fromJSONObject(pbObj *api.JSONElement_JSONObject) (*json.Object, error) { - members := json.NewRHTPriorityQueueMap() +func fromJSONObject(pbObj *api.JSONElement_JSONObject) (*crdt.Object, error) { + members := crdt.NewRHTPriorityQueueMap() for _, pbNode := range pbObj.Nodes { elem, err := fromJSONElement(pbNode.Element) if err != nil { @@ -89,7 +89,7 @@ func fromJSONObject(pbObj *api.JSONElement_JSONObject) (*json.Object, error) { return nil, err } - obj := json.NewObject( + obj := crdt.NewObject( members, createdAt, ) @@ -99,8 +99,8 @@ func fromJSONObject(pbObj *api.JSONElement_JSONObject) (*json.Object, error) { return obj, nil } -func fromJSONArray(pbArr *api.JSONElement_JSONArray) (*json.Array, error) { - elements := json.NewRGATreeList() +func fromJSONArray(pbArr *api.JSONElement_JSONArray) (*crdt.Array, error) { + elements := crdt.NewRGATreeList() for _, pbNode := range pbArr.Nodes { elem, err := fromJSONElement(pbNode.Element) if err != nil { @@ -122,7 +122,7 @@ func fromJSONArray(pbArr *api.JSONElement_JSONArray) (*json.Array, error) { return nil, err } - arr := json.NewArray( + arr := crdt.NewArray( elements, createdAt, ) @@ -133,7 +133,7 @@ func fromJSONArray(pbArr *api.JSONElement_JSONArray) (*json.Array, error) { func fromJSONPrimitive( pbPrim *api.JSONElement_Primitive, -) (*json.Primitive, error) { +) (*crdt.Primitive, error) { createdAt, err := fromTimeTicket(pbPrim.CreatedAt) if err != nil { return nil, err @@ -151,8 +151,8 @@ func fromJSONPrimitive( return nil, err } - primitive := json.NewPrimitive( - json.ValueFromBytes(valueType, pbPrim.Value), + primitive := crdt.NewPrimitive( + crdt.ValueFromBytes(valueType, pbPrim.Value), createdAt, ) primitive.SetMovedAt(movedAt) @@ -160,7 +160,7 @@ func fromJSONPrimitive( return primitive, nil } -func fromJSONText(pbText *api.JSONElement_Text) (*json.Text, error) { +func fromJSONText(pbText *api.JSONElement_Text) (*crdt.Text, error) { createdAt, err := fromTimeTicket(pbText.CreatedAt) if err != nil { return nil, err @@ -174,7 +174,7 @@ func fromJSONText(pbText *api.JSONElement_Text) (*json.Text, error) { return nil, err } - rgaTreeSplit := json.NewRGATreeSplit(json.InitialTextNode()) + rgaTreeSplit := crdt.NewRGATreeSplit(crdt.InitialTextNode()) current := rgaTreeSplit.InitialHead() for _, pbNode := range pbText.Nodes { @@ -196,7 +196,7 @@ func fromJSONText(pbText *api.JSONElement_Text) (*json.Text, error) { } } - text := json.NewText( + text := crdt.NewText( rgaTreeSplit, createdAt, ) @@ -208,7 +208,7 @@ func fromJSONText(pbText *api.JSONElement_Text) (*json.Text, error) { func fromJSONRichText( pbText *api.JSONElement_RichText, -) (*json.RichText, error) { +) (*crdt.RichText, error) { createdAt, err := fromTimeTicket(pbText.CreatedAt) if err != nil { return nil, err @@ -222,7 +222,7 @@ func fromJSONRichText( return nil, err } - rgaTreeSplit := json.NewRGATreeSplit(json.InitialRichTextNode()) + rgaTreeSplit := crdt.NewRGATreeSplit(crdt.InitialRichTextNode()) current := rgaTreeSplit.InitialHead() for _, pbNode := range pbText.Nodes { @@ -244,7 +244,7 @@ func fromJSONRichText( } } - text := json.NewRichText( + text := crdt.NewRichText( rgaTreeSplit, createdAt, ) @@ -254,7 +254,7 @@ func fromJSONRichText( return text, nil } -func fromJSONCounter(pbCnt *api.JSONElement_Counter) (*json.Counter, error) { +func fromJSONCounter(pbCnt *api.JSONElement_Counter) (*crdt.Counter, error) { createdAt, err := fromTimeTicket(pbCnt.CreatedAt) if err != nil { return nil, err @@ -272,8 +272,8 @@ func fromJSONCounter(pbCnt *api.JSONElement_Counter) (*json.Counter, error) { return nil, err } - counter := json.NewCounter( - json.CounterValueFromBytes(counterType, pbCnt.Value), + counter := crdt.NewCounter( + crdt.CounterValueFromBytes(counterType, pbCnt.Value), createdAt, ) counter.SetMovedAt(movedAt) @@ -282,14 +282,14 @@ func fromJSONCounter(pbCnt *api.JSONElement_Counter) (*json.Counter, error) { return counter, nil } -func fromTextNode(pbTextNode *api.TextNode) (*json.RGATreeSplitNode[*json.TextValue], error) { +func fromTextNode(pbTextNode *api.TextNode) (*crdt.RGATreeSplitNode[*crdt.TextValue], error) { id, err := fromTextNodeID(pbTextNode.Id) if err != nil { return nil, err } - textNode := json.NewRGATreeSplitNode( + textNode := crdt.NewRGATreeSplitNode( id, - json.NewTextValue(pbTextNode.Value), + crdt.NewTextValue(pbTextNode.Value), ) if pbTextNode.RemovedAt != nil { removedAt, err := fromTimeTicket(pbTextNode.RemovedAt) @@ -303,13 +303,13 @@ func fromTextNode(pbTextNode *api.TextNode) (*json.RGATreeSplitNode[*json.TextVa func fromRichTextNode( pbNode *api.RichTextNode, -) (*json.RGATreeSplitNode[*json.RichTextValue], error) { +) (*crdt.RGATreeSplitNode[*crdt.RichTextValue], error) { id, err := fromTextNodeID(pbNode.Id) if err != nil { return nil, err } - attrs := json.NewRHT() + attrs := crdt.NewRHT() for _, pbAttr := range pbNode.Attributes { updatedAt, err := fromTimeTicket(pbAttr.UpdatedAt) if err != nil { @@ -318,9 +318,9 @@ func fromRichTextNode( attrs.Set(pbAttr.Key, pbAttr.Value, updatedAt) } - textNode := json.NewRGATreeSplitNode( + textNode := crdt.NewRGATreeSplitNode( id, - json.NewRichTextValue(attrs, pbNode.Value), + crdt.NewRichTextValue(attrs, pbNode.Value), ) if pbNode.RemovedAt != nil { removedAt, err := fromTimeTicket(pbNode.RemovedAt) @@ -334,7 +334,7 @@ func fromRichTextNode( func fromTextNodeID( pbTextNodeID *api.TextNodeID, -) (*json.RGATreeSplitNodeID, error) { +) (*crdt.RGATreeSplitNodeID, error) { if pbTextNodeID == nil { return nil, nil } @@ -344,7 +344,7 @@ func fromTextNodeID( return nil, err } - return json.NewRGATreeSplitNodeID( + return crdt.NewRGATreeSplitNodeID( createdAt, int(pbTextNodeID.Offset), ), nil diff --git a/api/converter/from_pb.go b/api/converter/from_pb.go index 12e835f86..ebeb98acf 100644 --- a/api/converter/from_pb.go +++ b/api/converter/from_pb.go @@ -24,7 +24,7 @@ import ( "github.com/yorkie-team/yorkie/api/types" api "github.com/yorkie-team/yorkie/api/yorkie/v1" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/key" "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" @@ -548,13 +548,13 @@ func fromCreatedAtMapByActor( func fromTextNodePos( pbPos *api.TextNodePos, -) (*json.RGATreeSplitNodePos, error) { +) (*crdt.RGATreeSplitNodePos, error) { createdAt, err := fromTimeTicket(pbPos.CreatedAt) if err != nil { return nil, err } - return json.NewRGATreeSplitNodePos( - json.NewRGATreeSplitNodeID(createdAt, int(pbPos.Offset)), + return crdt.NewRGATreeSplitNodePos( + crdt.NewRGATreeSplitNodeID(createdAt, int(pbPos.Offset)), int(pbPos.RelativeOffset), ), nil } @@ -575,15 +575,15 @@ func fromTimeTicket(pbTicket *api.TimeTicket) (*time.Ticket, error) { ), nil } -func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { +func fromElement(pbElement *api.JSONElementSimple) (crdt.Element, error) { switch pbType := pbElement.Type; pbType { case api.ValueType_VALUE_TYPE_JSON_OBJECT: createdAt, err := fromTimeTicket(pbElement.CreatedAt) if err != nil { return nil, err } - return json.NewObject( - json.NewRHTPriorityQueueMap(), + return crdt.NewObject( + crdt.NewRHTPriorityQueueMap(), createdAt, ), nil case api.ValueType_VALUE_TYPE_JSON_ARRAY: @@ -591,8 +591,8 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { if err != nil { return nil, err } - return json.NewArray( - json.NewRGATreeList(), + return crdt.NewArray( + crdt.NewRGATreeList(), createdAt, ), nil case api.ValueType_VALUE_TYPE_NULL: @@ -618,8 +618,8 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { if err != nil { return nil, err } - return json.NewPrimitive( - json.ValueFromBytes(valueType, pbElement.Value), + return crdt.NewPrimitive( + crdt.ValueFromBytes(valueType, pbElement.Value), createdAt, ), nil case api.ValueType_VALUE_TYPE_TEXT: @@ -627,8 +627,8 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { if err != nil { return nil, err } - return json.NewText( - json.NewRGATreeSplit(json.InitialTextNode()), + return crdt.NewText( + crdt.NewRGATreeSplit(crdt.InitialTextNode()), createdAt, ), nil case api.ValueType_VALUE_TYPE_RICH_TEXT: @@ -636,8 +636,8 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { if err != nil { return nil, err } - return json.NewInitialRichText( - json.NewRGATreeSplit(json.InitialRichTextNode()), + return crdt.NewInitialRichText( + crdt.NewRGATreeSplit(crdt.InitialRichTextNode()), createdAt, ), nil case api.ValueType_VALUE_TYPE_INTEGER_CNT: @@ -653,8 +653,8 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { if err != nil { return nil, err } - return json.NewCounter( - json.CounterValueFromBytes(counterType, pbElement.Value), + return crdt.NewCounter( + crdt.CounterValueFromBytes(counterType, pbElement.Value), createdAt, ), nil } @@ -662,37 +662,37 @@ func fromElement(pbElement *api.JSONElementSimple) (json.Element, error) { return nil, fmt.Errorf("%d, %w", pbElement.Type, ErrUnsupportedElement) } -func fromPrimitiveValueType(valueType api.ValueType) (json.ValueType, error) { +func fromPrimitiveValueType(valueType api.ValueType) (crdt.ValueType, error) { switch valueType { case api.ValueType_VALUE_TYPE_NULL: - return json.Null, nil + return crdt.Null, nil case api.ValueType_VALUE_TYPE_BOOLEAN: - return json.Boolean, nil + return crdt.Boolean, nil case api.ValueType_VALUE_TYPE_INTEGER: - return json.Integer, nil + return crdt.Integer, nil case api.ValueType_VALUE_TYPE_LONG: - return json.Long, nil + return crdt.Long, nil case api.ValueType_VALUE_TYPE_DOUBLE: - return json.Double, nil + return crdt.Double, nil case api.ValueType_VALUE_TYPE_STRING: - return json.String, nil + return crdt.String, nil case api.ValueType_VALUE_TYPE_BYTES: - return json.Bytes, nil + return crdt.Bytes, nil case api.ValueType_VALUE_TYPE_DATE: - return json.Date, nil + return crdt.Date, nil } return 0, fmt.Errorf("%d, %w", valueType, ErrUnsupportedValueType) } -func fromCounterType(valueType api.ValueType) (json.CounterType, error) { +func fromCounterType(valueType api.ValueType) (crdt.CounterType, error) { switch valueType { case api.ValueType_VALUE_TYPE_INTEGER_CNT: - return json.IntegerCnt, nil + return crdt.IntegerCnt, nil case api.ValueType_VALUE_TYPE_LONG_CNT: - return json.LongCnt, nil + return crdt.LongCnt, nil case api.ValueType_VALUE_TYPE_DOUBLE_CNT: - return json.DoubleCnt, nil + return crdt.DoubleCnt, nil } return 0, fmt.Errorf("%d, %w", valueType, ErrUnsupportedCounterType) diff --git a/api/converter/to_bytes.go b/api/converter/to_bytes.go index d82415e18..cf1e3d205 100644 --- a/api/converter/to_bytes.go +++ b/api/converter/to_bytes.go @@ -23,11 +23,11 @@ import ( "github.com/gogo/protobuf/proto" api "github.com/yorkie-team/yorkie/api/yorkie/v1" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" ) // ObjectToBytes converts the given object to byte array. -func ObjectToBytes(obj *json.Object) ([]byte, error) { +func ObjectToBytes(obj *crdt.Object) ([]byte, error) { pbElem, err := toJSONElement(obj) if err != nil { return nil, err @@ -40,26 +40,26 @@ func ObjectToBytes(obj *json.Object) ([]byte, error) { return bytes, nil } -func toJSONElement(elem json.Element) (*api.JSONElement, error) { +func toJSONElement(elem crdt.Element) (*api.JSONElement, error) { switch elem := elem.(type) { - case *json.Object: + case *crdt.Object: return toJSONObject(elem) - case *json.Array: + case *crdt.Array: return toJSONArray(elem) - case *json.Primitive: + case *crdt.Primitive: return toPrimitive(elem) - case *json.Text: + case *crdt.Text: return toText(elem), nil - case *json.RichText: + case *crdt.RichText: return toRichText(elem), nil - case *json.Counter: + case *crdt.Counter: return toCounter(elem) default: return nil, fmt.Errorf("%v: %w", reflect.TypeOf(elem), ErrUnsupportedElement) } } -func toJSONObject(obj *json.Object) (*api.JSONElement, error) { +func toJSONObject(obj *crdt.Object) (*api.JSONElement, error) { pbRHTNodes, err := toRHTNodes(obj.RHTNodes()) if err != nil { return nil, err @@ -76,7 +76,7 @@ func toJSONObject(obj *json.Object) (*api.JSONElement, error) { return pbElem, nil } -func toJSONArray(arr *json.Array) (*api.JSONElement, error) { +func toJSONArray(arr *crdt.Array) (*api.JSONElement, error) { pbRGANodes, err := toRGANodes(arr.RGANodes()) if err != nil { return nil, err @@ -93,7 +93,7 @@ func toJSONArray(arr *json.Array) (*api.JSONElement, error) { return pbElem, nil } -func toPrimitive(primitive *json.Primitive) (*api.JSONElement, error) { +func toPrimitive(primitive *crdt.Primitive) (*api.JSONElement, error) { pbValueType, err := toValueType(primitive.ValueType()) if err != nil { return nil, err @@ -110,7 +110,7 @@ func toPrimitive(primitive *json.Primitive) (*api.JSONElement, error) { }, nil } -func toText(text *json.Text) *api.JSONElement { +func toText(text *crdt.Text) *api.JSONElement { return &api.JSONElement{ Body: &api.JSONElement_Text_{Text: &api.JSONElement_Text{ Nodes: toTextNodes(text.Nodes()), @@ -121,7 +121,7 @@ func toText(text *json.Text) *api.JSONElement { } } -func toRichText(text *json.RichText) *api.JSONElement { +func toRichText(text *crdt.RichText) *api.JSONElement { return &api.JSONElement{ Body: &api.JSONElement_RichText_{RichText: &api.JSONElement_RichText{ Nodes: toRichTextNodes(text.Nodes()), @@ -132,7 +132,7 @@ func toRichText(text *json.RichText) *api.JSONElement { } } -func toCounter(counter *json.Counter) (*api.JSONElement, error) { +func toCounter(counter *crdt.Counter) (*api.JSONElement, error) { pbCounterType, err := toCounterType(counter.ValueType()) if err != nil { return nil, err @@ -149,7 +149,7 @@ func toCounter(counter *json.Counter) (*api.JSONElement, error) { }, nil } -func toRHTNodes(rhtNodes []*json.RHTPQMapNode) ([]*api.RHTNode, error) { +func toRHTNodes(rhtNodes []*crdt.RHTPQMapNode) ([]*api.RHTNode, error) { var pbRHTNodes []*api.RHTNode for _, rhtNode := range rhtNodes { pbElem, err := toJSONElement(rhtNode.Element()) @@ -165,7 +165,7 @@ func toRHTNodes(rhtNodes []*json.RHTPQMapNode) ([]*api.RHTNode, error) { return pbRHTNodes, nil } -func toRGANodes(rgaNodes []*json.RGATreeListNode) ([]*api.RGANode, error) { +func toRGANodes(rgaNodes []*crdt.RGATreeListNode) ([]*api.RGANode, error) { var pbRGANodes []*api.RGANode for _, rgaNode := range rgaNodes { pbElem, err := toJSONElement(rgaNode.Element()) @@ -180,7 +180,7 @@ func toRGANodes(rgaNodes []*json.RGATreeListNode) ([]*api.RGANode, error) { return pbRGANodes, nil } -func toTextNodes(textNodes []*json.RGATreeSplitNode[*json.TextValue]) []*api.TextNode { +func toTextNodes(textNodes []*crdt.RGATreeSplitNode[*crdt.TextValue]) []*api.TextNode { var pbTextNodes []*api.TextNode for _, textNode := range textNodes { pbTextNode := &api.TextNode{ @@ -198,7 +198,7 @@ func toTextNodes(textNodes []*json.RGATreeSplitNode[*json.TextValue]) []*api.Tex return pbTextNodes } -func toRichTextNodes(textNodes []*json.RGATreeSplitNode[*json.RichTextValue]) []*api.RichTextNode { +func toRichTextNodes(textNodes []*crdt.RGATreeSplitNode[*crdt.RichTextValue]) []*api.RichTextNode { var pbTextNodes []*api.RichTextNode for _, textNode := range textNodes { value := textNode.Value() @@ -228,7 +228,7 @@ func toRichTextNodes(textNodes []*json.RGATreeSplitNode[*json.RichTextValue]) [] return pbTextNodes } -func toTextNodeID(id *json.RGATreeSplitNodeID) *api.TextNodeID { +func toTextNodeID(id *crdt.RGATreeSplitNodeID) *api.TextNodeID { return &api.TextNodeID{ CreatedAt: ToTimeTicket(id.CreatedAt()), Offset: int32(id.Offset()), diff --git a/api/converter/to_pb.go b/api/converter/to_pb.go index 78a00b355..9b6f4e7e9 100644 --- a/api/converter/to_pb.go +++ b/api/converter/to_pb.go @@ -25,7 +25,7 @@ import ( "github.com/yorkie-team/yorkie/api/types" api "github.com/yorkie-team/yorkie/api/yorkie/v1" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/key" "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" @@ -417,19 +417,19 @@ func toIncrease(increase *operations.Increase) (*api.Operation_Increase_, error) }, nil } -func toJSONElementSimple(elem json.Element) (*api.JSONElementSimple, error) { +func toJSONElementSimple(elem crdt.Element) (*api.JSONElementSimple, error) { switch elem := elem.(type) { - case *json.Object: + case *crdt.Object: return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_JSON_OBJECT, CreatedAt: ToTimeTicket(elem.CreatedAt()), }, nil - case *json.Array: + case *crdt.Array: return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_JSON_ARRAY, CreatedAt: ToTimeTicket(elem.CreatedAt()), }, nil - case *json.Primitive: + case *crdt.Primitive: pbValueType, err := toValueType(elem.ValueType()) if err != nil { return nil, err @@ -440,17 +440,17 @@ func toJSONElementSimple(elem json.Element) (*api.JSONElementSimple, error) { CreatedAt: ToTimeTicket(elem.CreatedAt()), Value: elem.Bytes(), }, nil - case *json.Text: + case *crdt.Text: return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_TEXT, CreatedAt: ToTimeTicket(elem.CreatedAt()), }, nil - case *json.RichText: + case *crdt.RichText: return &api.JSONElementSimple{ Type: api.ValueType_VALUE_TYPE_RICH_TEXT, CreatedAt: ToTimeTicket(elem.CreatedAt()), }, nil - case *json.Counter: + case *crdt.Counter: pbCounterType, err := toCounterType(elem.ValueType()) if err != nil { return nil, err @@ -466,7 +466,7 @@ func toJSONElementSimple(elem json.Element) (*api.JSONElementSimple, error) { return nil, fmt.Errorf("%v, %w", reflect.TypeOf(elem), ErrUnsupportedElement) } -func toTextNodePos(pos *json.RGATreeSplitNodePos) *api.TextNodePos { +func toTextNodePos(pos *crdt.RGATreeSplitNodePos) *api.TextNodePos { return &api.TextNodePos{ CreatedAt: ToTimeTicket(pos.ID().CreatedAt()), Offset: int32(pos.ID().Offset()), @@ -484,36 +484,36 @@ func toCreatedAtMapByActor( return pbCreatedAtMapByActor } -func toValueType(valueType json.ValueType) (api.ValueType, error) { +func toValueType(valueType crdt.ValueType) (api.ValueType, error) { switch valueType { - case json.Null: + case crdt.Null: return api.ValueType_VALUE_TYPE_NULL, nil - case json.Boolean: + case crdt.Boolean: return api.ValueType_VALUE_TYPE_BOOLEAN, nil - case json.Integer: + case crdt.Integer: return api.ValueType_VALUE_TYPE_INTEGER, nil - case json.Long: + case crdt.Long: return api.ValueType_VALUE_TYPE_LONG, nil - case json.Double: + case crdt.Double: return api.ValueType_VALUE_TYPE_DOUBLE, nil - case json.String: + case crdt.String: return api.ValueType_VALUE_TYPE_STRING, nil - case json.Bytes: + case crdt.Bytes: return api.ValueType_VALUE_TYPE_BYTES, nil - case json.Date: + case crdt.Date: return api.ValueType_VALUE_TYPE_DATE, nil } return 0, fmt.Errorf("%d, %w", valueType, ErrUnsupportedValueType) } -func toCounterType(valueType json.CounterType) (api.ValueType, error) { +func toCounterType(valueType crdt.CounterType) (api.ValueType, error) { switch valueType { - case json.IntegerCnt: + case crdt.IntegerCnt: return api.ValueType_VALUE_TYPE_INTEGER_CNT, nil - case json.LongCnt: + case crdt.LongCnt: return api.ValueType_VALUE_TYPE_LONG_CNT, nil - case json.DoubleCnt: + case crdt.DoubleCnt: return api.ValueType_VALUE_TYPE_DOUBLE_CNT, nil } diff --git a/pkg/document/change/change.go b/pkg/document/change/change.go index d156303bb..11259489c 100644 --- a/pkg/document/change/change.go +++ b/pkg/document/change/change.go @@ -17,7 +17,7 @@ package change import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -44,7 +44,7 @@ func New(id ID, message string, operations []operations.Operation) *Change { } // Execute applies this change to the given JSON root. -func (c *Change) Execute(root *json.Root) error { +func (c *Change) Execute(root *crdt.Root) error { for _, op := range c.operations { if err := op.Execute(root); err != nil { return err diff --git a/pkg/document/change/context.go b/pkg/document/change/context.go index af73cfb5d..03e32fd75 100644 --- a/pkg/document/change/context.go +++ b/pkg/document/change/context.go @@ -17,7 +17,7 @@ package change import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -30,11 +30,11 @@ type Context struct { message string operations []operations.Operation delimiter uint32 - root *json.Root + root *crdt.Root } // NewContext creates a new instance of Context. -func NewContext(id ID, message string, root *json.Root) *Context { +func NewContext(id ID, message string, root *crdt.Root) *Context { return &Context{ id: id, message: message, @@ -69,16 +69,16 @@ func (c *Context) Push(op operations.Operation) { } // RegisterElement registers the given element to the root. -func (c *Context) RegisterElement(elem json.Element) { +func (c *Context) RegisterElement(elem crdt.Element) { c.root.RegisterElement(elem) } // RegisterRemovedElementPair registers the given element pair to hash table. -func (c *Context) RegisterRemovedElementPair(parent json.Container, deleted json.Element) { +func (c *Context) RegisterRemovedElementPair(parent crdt.Container, deleted crdt.Element) { c.root.RegisterRemovedElementPair(parent, deleted) } // RegisterTextElementWithGarbage register the given text element with garbage to hash table. -func (c *Context) RegisterTextElementWithGarbage(textType json.TextElement) { +func (c *Context) RegisterTextElementWithGarbage(textType crdt.TextElement) { c.root.RegisterTextElementWithGarbage(textType) } diff --git a/pkg/document/crdt/array.go b/pkg/document/crdt/array.go new file mode 100644 index 000000000..44805e052 --- /dev/null +++ b/pkg/document/crdt/array.go @@ -0,0 +1,184 @@ +/* + * Copyright 2020 The Yorkie Authors. All rights reserved. + * + * 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 crdt + +import ( + "github.com/yorkie-team/yorkie/pkg/document/time" +) + +// Array represents JSON array data structure including logical clock. +// Array implements Element interface. +type Array struct { + elements *RGATreeList + createdAt *time.Ticket + movedAt *time.Ticket + removedAt *time.Ticket +} + +// NewArray creates a new instance of Array. +func NewArray(elements *RGATreeList, createdAt *time.Ticket) *Array { + return &Array{ + elements: elements, + createdAt: createdAt, + } +} + +// Purge physically purge child element. +func (a *Array) Purge(elem Element) { + a.elements.purge(elem) +} + +// Add adds the given element at the last. +func (a *Array) Add(elem Element) *Array { + a.elements.Add(elem) + return a +} + +// Get returns the element of the given index. +func (a *Array) Get(idx int) Element { + return a.elements.Get(idx).elem +} + +// FindPrevCreatedAt returns the creation time of the previous element of the +// given element. +func (a *Array) FindPrevCreatedAt(createdAt *time.Ticket) *time.Ticket { + return a.elements.FindPrevCreatedAt(createdAt) +} + +// Delete deletes the element of the given index. +func (a *Array) Delete(idx int, deletedAt *time.Ticket) Element { + return a.elements.Delete(idx, deletedAt).elem +} + +// MoveAfter moves the given `createdAt` element after the `prevCreatedAt` +// element. +func (a *Array) MoveAfter(prevCreatedAt, createdAt, executedAt *time.Ticket) { + a.elements.MoveAfter(prevCreatedAt, createdAt, executedAt) +} + +// Elements returns an array of elements contained in this RGATreeList. +func (a *Array) Elements() []Element { + var elements []Element + for _, node := range a.elements.Nodes() { + if node.isRemoved() { + continue + } + elements = append(elements, node.elem) + } + + return elements +} + +// Marshal returns the JSON encoding of this Array. +func (a *Array) Marshal() string { + return a.elements.Marshal() +} + +// AnnotatedString returns a String containing the metadata of the elements +// for debugging purpose. +func (a *Array) AnnotatedString() string { + return a.elements.AnnotatedString() +} + +// DeepCopy copies itself deeply. +func (a *Array) DeepCopy() Element { + elements := NewRGATreeList() + + for _, node := range a.elements.Nodes() { + elements.Add(node.elem.DeepCopy()) + } + + array := NewArray(elements, a.createdAt) + array.removedAt = a.removedAt + return array +} + +// CreatedAt returns the creation time of this array. +func (a *Array) CreatedAt() *time.Ticket { + return a.createdAt +} + +// MovedAt returns the move time of this array. +func (a *Array) MovedAt() *time.Ticket { + return a.movedAt +} + +// SetMovedAt sets the move time of this array. +func (a *Array) SetMovedAt(movedAt *time.Ticket) { + a.movedAt = movedAt +} + +// RemovedAt returns the removal time of this array. +func (a *Array) RemovedAt() *time.Ticket { + return a.removedAt +} + +// SetRemovedAt sets the removal time of this array. +func (a *Array) SetRemovedAt(removedAt *time.Ticket) { + a.removedAt = removedAt +} + +// Remove removes this array. +func (a *Array) Remove(removedAt *time.Ticket) bool { + if (removedAt != nil && removedAt.After(a.createdAt)) && + (a.removedAt == nil || removedAt.After(a.removedAt)) { + a.removedAt = removedAt + return true + } + return false +} + +// LastCreatedAt returns the creation time of the last element. +func (a *Array) LastCreatedAt() *time.Ticket { + return a.elements.LastCreatedAt() +} + +// InsertAfter inserts the given element after the given previous element. +func (a *Array) InsertAfter(prevCreatedAt *time.Ticket, element Element) { + a.elements.InsertAfter(prevCreatedAt, element) +} + +// DeleteByCreatedAt deletes the given element. +func (a *Array) DeleteByCreatedAt(createdAt *time.Ticket, deletedAt *time.Ticket) Element { + return a.elements.DeleteByCreatedAt(createdAt, deletedAt).elem +} + +// Len returns length of this Array. +func (a *Array) Len() int { + return a.elements.Len() +} + +// Descendants traverse the descendants of this array. +func (a *Array) Descendants(callback func(elem Element, parent Container) bool) { + for _, node := range a.elements.Nodes() { + if callback(node.elem, a) { + return + } + + switch elem := node.elem.(type) { + case *Object: + elem.Descendants(callback) + case *Array: + elem.Descendants(callback) + } + } +} + +// RGANodes returns the slices of RGATreeListNode. +func (a *Array) RGANodes() []*RGATreeListNode { + return a.elements.Nodes() +} diff --git a/pkg/document/json/array_test.go b/pkg/document/crdt/array_test.go similarity index 77% rename from pkg/document/json/array_test.go rename to pkg/document/crdt/array_test.go index e64606bc8..805708e08 100644 --- a/pkg/document/json/array_test.go +++ b/pkg/document/crdt/array_test.go @@ -14,14 +14,14 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/test/helper" ) @@ -30,13 +30,13 @@ func TestArray(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - a := json.NewArray(json.NewRGATreeList(), ctx.IssueTimeTicket()) + a := crdt.NewArray(crdt.NewRGATreeList(), ctx.IssueTimeTicket()) - a.Add(json.NewPrimitive("1", ctx.IssueTimeTicket())) + a.Add(crdt.NewPrimitive("1", ctx.IssueTimeTicket())) assert.Equal(t, `["1"]`, a.Marshal()) - a.Add(json.NewPrimitive("2", ctx.IssueTimeTicket())) + a.Add(crdt.NewPrimitive("2", ctx.IssueTimeTicket())) assert.Equal(t, `["1","2"]`, a.Marshal()) - a.Add(json.NewPrimitive("3", ctx.IssueTimeTicket())) + a.Add(crdt.NewPrimitive("3", ctx.IssueTimeTicket())) assert.Equal(t, `["1","2","3"]`, a.Marshal()) }) } diff --git a/pkg/document/crdt/counter.go b/pkg/document/crdt/counter.go new file mode 100644 index 000000000..eb9d7e7ea --- /dev/null +++ b/pkg/document/crdt/counter.go @@ -0,0 +1,225 @@ +/* + * Copyright 2020 The Yorkie Authors. All rights reserved. + * + * 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 crdt + +import ( + "encoding/binary" + "fmt" + "math" + + "github.com/yorkie-team/yorkie/pkg/document/time" +) + +// CounterType represents any type that can be used as a counter. +type CounterType int + +// The values below are the types that can be used as counters. +const ( + IntegerCnt CounterType = iota + LongCnt + DoubleCnt +) + +// CounterValueFromBytes parses the given bytes into value. +func CounterValueFromBytes(counterType CounterType, value []byte) interface{} { + switch counterType { + case IntegerCnt: + val := int32(binary.LittleEndian.Uint32(value)) + return int(val) + case LongCnt: + return int64(binary.LittleEndian.Uint64(value)) + case DoubleCnt: + return math.Float64frombits(binary.LittleEndian.Uint64(value)) + } + + panic("unsupported type") +} + +// Counter represents changeable number data type. +type Counter struct { + valueType CounterType + value interface{} + createdAt *time.Ticket + movedAt *time.Ticket + removedAt *time.Ticket +} + +// NewCounter creates a new instance of Counter. +func NewCounter(value interface{}, createdAt *time.Ticket) *Counter { + switch val := value.(type) { + case int: + if math.MaxInt32 < val || math.MinInt32 > val { + return &Counter{ + valueType: LongCnt, + value: int64(val), + createdAt: createdAt, + } + } + return &Counter{ + valueType: IntegerCnt, + value: val, + createdAt: createdAt, + } + case int64: + return &Counter{ + valueType: LongCnt, + value: val, + createdAt: createdAt, + } + case float64: + return &Counter{ + valueType: DoubleCnt, + value: val, + createdAt: createdAt, + } + } + + panic("unsupported type") +} + +// Bytes creates an array representing the value. +func (p *Counter) Bytes() []byte { + switch val := p.value.(type) { + case int: + bytes := [4]byte{} + binary.LittleEndian.PutUint32(bytes[:], uint32(val)) + return bytes[:] + case int64: + bytes := [8]byte{} + binary.LittleEndian.PutUint64(bytes[:], uint64(val)) + return bytes[:] + case float64: + bytes := [8]byte{} + binary.LittleEndian.PutUint64(bytes[:], math.Float64bits(val)) + return bytes[:] + } + + panic("unsupported type") +} + +// Marshal returns the JSON encoding of the value. +func (p *Counter) Marshal() string { + switch p.valueType { + case IntegerCnt: + return fmt.Sprintf("%d", p.value) + case LongCnt: + return fmt.Sprintf("%d", p.value) + case DoubleCnt: + return fmt.Sprintf("%f", p.value) + } + + panic("unsupported type") +} + +// DeepCopy copies itself deeply. +func (p *Counter) DeepCopy() Element { + counter := *p + return &counter +} + +// CreatedAt returns the creation time. +func (p *Counter) CreatedAt() *time.Ticket { + return p.createdAt +} + +// MovedAt returns the move time of this element. +func (p *Counter) MovedAt() *time.Ticket { + return p.movedAt +} + +// SetMovedAt sets the move time of this element. +func (p *Counter) SetMovedAt(movedAt *time.Ticket) { + p.movedAt = movedAt +} + +// RemovedAt returns the removal time of this element. +func (p *Counter) RemovedAt() *time.Ticket { + return p.removedAt +} + +// SetRemovedAt sets the removal time of this array. +func (p *Counter) SetRemovedAt(removedAt *time.Ticket) { + p.removedAt = removedAt +} + +// Remove removes this element. +func (p *Counter) Remove(removedAt *time.Ticket) bool { + if (removedAt != nil && removedAt.After(p.createdAt)) && + (p.removedAt == nil || removedAt.After(p.removedAt)) { + p.removedAt = removedAt + return true + } + return false +} + +// ValueType returns the type of the value. +func (p *Counter) ValueType() CounterType { + return p.valueType +} + +// Increase increases integer, long or double. +// If the result of the operation is greater than MaxInt32 or less +// than MinInt32, Counter's value type can be changed Integer to Long. +// Because in golang, int can be either int32 or int64. +// So we need to assert int to int32. +func (p *Counter) Increase(v *Primitive) *Counter { + if !p.IsNumericType() || !v.IsNumericType() { + panic("unsupported type") + } + switch p.valueType { + case IntegerCnt: + switch v.valueType { + case Long: + p.value = p.value.(int) + int(v.value.(int64)) + case Double: + p.value = p.value.(int) + int(v.value.(float64)) + default: + p.value = p.value.(int) + v.value.(int) + } + + if p.value.(int) > math.MaxInt32 || p.value.(int) < math.MinInt32 { + p.value = int64(p.value.(int)) + p.valueType = LongCnt + } + case LongCnt: + switch v.valueType { + case Integer: + p.value = p.value.(int64) + int64(v.value.(int)) + case Double: + p.value = p.value.(int64) + int64(v.value.(float64)) + default: + p.value = p.value.(int64) + v.value.(int64) + } + case DoubleCnt: + switch v.valueType { + case Integer: + p.value = p.value.(float64) + float64(v.value.(int)) + case Long: + p.value = p.value.(float64) + float64(v.value.(int64)) + default: + p.value = p.value.(float64) + v.value.(float64) + } + } + + return p +} + +// IsNumericType checks for numeric types. +func (p *Counter) IsNumericType() bool { + t := p.valueType + return t == IntegerCnt || t == LongCnt || t == DoubleCnt +} diff --git a/pkg/document/json/counter_test.go b/pkg/document/crdt/counter_test.go similarity index 67% rename from pkg/document/json/counter_test.go rename to pkg/document/crdt/counter_test.go index 72df7b1b3..6f8e218f7 100644 --- a/pkg/document/json/counter_test.go +++ b/pkg/document/crdt/counter_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "math" @@ -23,33 +23,33 @@ import ( "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) func TestCounter(t *testing.T) { t.Run("new counter test", func(t *testing.T) { - integer := json.NewCounter(math.MaxInt32, time.InitialTicket) - assert.Equal(t, json.IntegerCnt, integer.ValueType()) + integer := crdt.NewCounter(math.MaxInt32, time.InitialTicket) + assert.Equal(t, crdt.IntegerCnt, integer.ValueType()) - long := json.NewCounter(math.MaxInt32+1, time.InitialTicket) - assert.Equal(t, json.LongCnt, long.ValueType()) + long := crdt.NewCounter(math.MaxInt32+1, time.InitialTicket) + assert.Equal(t, crdt.LongCnt, long.ValueType()) - double := json.NewCounter(0.5, time.InitialTicket) - assert.Equal(t, json.DoubleCnt, double.ValueType()) + double := crdt.NewCounter(0.5, time.InitialTicket) + assert.Equal(t, crdt.DoubleCnt, double.ValueType()) }) t.Run("increase test", func(t *testing.T) { var x = 5 var y int64 = 10 var z = 3.14 - integer := json.NewCounter(x, time.InitialTicket) - long := json.NewCounter(y, time.InitialTicket) - double := json.NewCounter(z, time.InitialTicket) + integer := crdt.NewCounter(x, time.InitialTicket) + long := crdt.NewCounter(y, time.InitialTicket) + double := crdt.NewCounter(z, time.InitialTicket) - integerOperand := json.NewPrimitive(x, time.InitialTicket) - longOperand := json.NewPrimitive(y, time.InitialTicket) - doubleOperand := json.NewPrimitive(z, time.InitialTicket) + integerOperand := crdt.NewPrimitive(x, time.InitialTicket) + longOperand := crdt.NewPrimitive(y, time.InitialTicket) + doubleOperand := crdt.NewPrimitive(z, time.InitialTicket) // normal process test integer.Increase(integerOperand) @@ -77,7 +77,7 @@ func TestCounter(t *testing.T) { } unsupportedTest := func(v interface{}) { defer unsupportedTypePanicTest() - json.NewCounter(v, time.InitialTicket) + crdt.NewCounter(v, time.InitialTicket) } unsupportedTest("str") unsupportedTest(true) @@ -90,11 +90,11 @@ func TestCounter(t *testing.T) { }) t.Run("Counter's value type changed Integer to Long test", func(t *testing.T) { - integer := json.NewCounter(math.MaxInt32, time.InitialTicket) - assert.Equal(t, integer.ValueType(), json.IntegerCnt) + integer := crdt.NewCounter(math.MaxInt32, time.InitialTicket) + assert.Equal(t, integer.ValueType(), crdt.IntegerCnt) - operand := json.NewPrimitive(1, time.InitialTicket) + operand := crdt.NewPrimitive(1, time.InitialTicket) integer.Increase(operand) - assert.Equal(t, integer.ValueType(), json.LongCnt) + assert.Equal(t, integer.ValueType(), crdt.LongCnt) }) } diff --git a/pkg/document/json/element.go b/pkg/document/crdt/element.go similarity index 99% rename from pkg/document/json/element.go rename to pkg/document/crdt/element.go index 12bc29db9..f62f00399 100644 --- a/pkg/document/json/element.go +++ b/pkg/document/crdt/element.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "github.com/yorkie-team/yorkie/pkg/document/time" diff --git a/pkg/document/crdt/object.go b/pkg/document/crdt/object.go new file mode 100644 index 000000000..f4f5ca660 --- /dev/null +++ b/pkg/document/crdt/object.go @@ -0,0 +1,147 @@ +/* + * Copyright 2020 The Yorkie Authors. All rights reserved. + * + * 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 crdt + +import ( + "github.com/yorkie-team/yorkie/pkg/document/time" +) + +// Object represents a JSON object, but unlike regular JSON, it has time +// tickets which is created by logical clock. +type Object struct { + memberNodes *RHTPriorityQueueMap + createdAt *time.Ticket + movedAt *time.Ticket + removedAt *time.Ticket +} + +// NewObject creates a new instance of Object. +func NewObject(memberNodes *RHTPriorityQueueMap, createdAt *time.Ticket) *Object { + return &Object{ + memberNodes: memberNodes, + createdAt: createdAt, + } +} + +// Purge physically purge child element. +func (o *Object) Purge(elem Element) { + o.memberNodes.purge(elem) +} + +// Set sets the given element of the given key. +func (o *Object) Set(k string, v Element) Element { + return o.memberNodes.Set(k, v) +} + +// Members returns the member of this object as a map. +func (o *Object) Members() map[string]Element { + return o.memberNodes.Elements() +} + +// Get returns the value of the given key. +func (o *Object) Get(k string) Element { + return o.memberNodes.Get(k) +} + +// Has returns whether the element exists of the given key or not. +func (o *Object) Has(k string) bool { + return o.memberNodes.Has(k) +} + +// DeleteByCreatedAt deletes the element of the given creation time. +func (o *Object) DeleteByCreatedAt(createdAt *time.Ticket, deletedAt *time.Ticket) Element { + return o.memberNodes.DeleteByCreatedAt(createdAt, deletedAt) +} + +// Delete deletes the element of the given key. +func (o *Object) Delete(k string, deletedAt *time.Ticket) Element { + return o.memberNodes.Delete(k, deletedAt) +} + +// Descendants traverse the descendants of this object. +func (o *Object) Descendants(callback func(elem Element, parent Container) bool) { + for _, node := range o.memberNodes.Nodes() { + if callback(node.elem, o) { + return + } + + switch elem := node.elem.(type) { + case *Object: + elem.Descendants(callback) + case *Array: + elem.Descendants(callback) + } + } +} + +// Marshal returns the JSON encoding of this object. +func (o *Object) Marshal() string { + return o.memberNodes.Marshal() +} + +// DeepCopy copies itself deeply. +func (o *Object) DeepCopy() Element { + members := NewRHTPriorityQueueMap() + + for _, node := range o.memberNodes.Nodes() { + members.SetInternal(node.key, node.elem.DeepCopy()) + } + + obj := NewObject(members, o.createdAt) + obj.removedAt = o.removedAt + return obj +} + +// CreatedAt returns the creation time of this object. +func (o *Object) CreatedAt() *time.Ticket { + return o.createdAt +} + +// MovedAt returns the move time of this object. +func (o *Object) MovedAt() *time.Ticket { + return o.movedAt +} + +// SetMovedAt sets the move time of this object. +func (o *Object) SetMovedAt(movedAt *time.Ticket) { + o.movedAt = movedAt +} + +// RemovedAt returns the removal time of this object. +func (o *Object) RemovedAt() *time.Ticket { + return o.removedAt +} + +// SetRemovedAt sets the removal time of this array. +func (o *Object) SetRemovedAt(removedAt *time.Ticket) { + o.removedAt = removedAt +} + +// Remove removes this object. +func (o *Object) Remove(removedAt *time.Ticket) bool { + if (removedAt != nil && removedAt.After(o.createdAt)) && + (o.removedAt == nil || removedAt.After(o.removedAt)) { + o.removedAt = removedAt + return true + } + return false +} + +// RHTNodes returns the RHTPriorityQueueMap nodes. +func (o *Object) RHTNodes() []*RHTPQMapNode { + return o.memberNodes.Nodes() +} diff --git a/pkg/document/json/object_test.go b/pkg/document/crdt/object_test.go similarity index 80% rename from pkg/document/json/object_test.go rename to pkg/document/crdt/object_test.go index 545a0f4f8..4b2d7a79e 100644 --- a/pkg/document/json/object_test.go +++ b/pkg/document/crdt/object_test.go @@ -14,14 +14,14 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/test/helper" ) @@ -30,11 +30,11 @@ func TestObject(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - obj := json.NewObject(json.NewRHTPriorityQueueMap(), ctx.IssueTimeTicket()) + obj := crdt.NewObject(crdt.NewRHTPriorityQueueMap(), ctx.IssueTimeTicket()) - obj.Set("k1", json.NewPrimitive("v1", ctx.IssueTimeTicket())) + obj.Set("k1", crdt.NewPrimitive("v1", ctx.IssueTimeTicket())) assert.Equal(t, `{"k1":"v1"}`, obj.Marshal()) - obj.Set("k2", json.NewPrimitive("v2", ctx.IssueTimeTicket())) + obj.Set("k2", crdt.NewPrimitive("v2", ctx.IssueTimeTicket())) assert.Equal(t, `{"k1":"v1","k2":"v2"}`, obj.Marshal()) obj.Delete("k1", ctx.IssueTimeTicket()) assert.Equal(t, `{"k2":"v2"}`, obj.Marshal()) diff --git a/pkg/document/json/primitive.go b/pkg/document/crdt/primitive.go similarity index 99% rename from pkg/document/json/primitive.go rename to pkg/document/crdt/primitive.go index 10cb1efd2..2ba4f025e 100644 --- a/pkg/document/json/primitive.go +++ b/pkg/document/crdt/primitive.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "encoding/binary" diff --git a/pkg/document/json/primitive_test.go b/pkg/document/crdt/primitive_test.go similarity index 69% rename from pkg/document/json/primitive_test.go rename to pkg/document/crdt/primitive_test.go index b65acefcd..d43a187cc 100644 --- a/pkg/document/json/primitive_test.go +++ b/pkg/document/crdt/primitive_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "math" @@ -23,32 +23,32 @@ import ( "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) func TestPrimitive(t *testing.T) { tests := []struct { value interface{} - valueType json.ValueType + valueType crdt.ValueType marshal string }{ - {nil, json.Null, "null"}, - {false, json.Boolean, "false"}, - {true, json.Boolean, "true"}, - {0, json.Integer, "0"}, - {int64(0), json.Long, "0"}, - {float64(0), json.Double, "0.000000"}, - {"0", json.String, `"0"`}, - {[]byte{}, json.Bytes, `""`}, - {gotime.Unix(0, 0), json.Date, gotime.Unix(0, 0).Format(gotime.RFC3339)}, + {nil, crdt.Null, "null"}, + {false, crdt.Boolean, "false"}, + {true, crdt.Boolean, "true"}, + {0, crdt.Integer, "0"}, + {int64(0), crdt.Long, "0"}, + {float64(0), crdt.Double, "0.000000"}, + {"0", crdt.String, `"0"`}, + {[]byte{}, crdt.Bytes, `""`}, + {gotime.Unix(0, 0), crdt.Date, gotime.Unix(0, 0).Format(gotime.RFC3339)}, } t.Run("creation and deep copy test", func(t *testing.T) { for _, test := range tests { - prim := json.NewPrimitive(test.value, time.InitialTicket) + prim := crdt.NewPrimitive(test.value, time.InitialTicket) assert.Equal(t, prim.ValueType(), test.valueType) - assert.Equal(t, prim.Value(), json.ValueFromBytes(prim.ValueType(), prim.Bytes())) + assert.Equal(t, prim.Value(), crdt.ValueFromBytes(prim.ValueType(), prim.Bytes())) assert.Equal(t, prim.Marshal(), test.marshal) copied := prim.DeepCopy() @@ -60,7 +60,7 @@ func TestPrimitive(t *testing.T) { prim.SetMovedAt(time.NewTicket(0, 0, actorID)) assert.NotEqual(t, prim.MovedAt(), copied.MovedAt()) } - longPrim := json.NewPrimitive(math.MaxInt32+1, time.InitialTicket) - assert.Equal(t, longPrim.ValueType(), json.Long) + longPrim := crdt.NewPrimitive(math.MaxInt32+1, time.InitialTicket) + assert.Equal(t, longPrim.ValueType(), crdt.Long) }) } diff --git a/pkg/document/json/rga_tree_list.go b/pkg/document/crdt/rga_tree_list.go similarity index 99% rename from pkg/document/json/rga_tree_list.go rename to pkg/document/crdt/rga_tree_list.go index 30b0ff602..6270f9e59 100644 --- a/pkg/document/json/rga_tree_list.go +++ b/pkg/document/crdt/rga_tree_list.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "strings" diff --git a/pkg/document/json/rga_tree_split.go b/pkg/document/crdt/rga_tree_split.go similarity index 99% rename from pkg/document/json/rga_tree_split.go rename to pkg/document/crdt/rga_tree_split.go index 25b486175..7c245b6a5 100644 --- a/pkg/document/json/rga_tree_split.go +++ b/pkg/document/crdt/rga_tree_split.go @@ -1,4 +1,4 @@ -package json +package crdt import ( "fmt" diff --git a/pkg/document/json/rga_tree_split_test.go b/pkg/document/crdt/rga_tree_split_test.go similarity index 69% rename from pkg/document/json/rga_tree_split_test.go rename to pkg/document/crdt/rga_tree_split_test.go index 0ce62d978..a4832f5bd 100644 --- a/pkg/document/json/rga_tree_split_test.go +++ b/pkg/document/crdt/rga_tree_split_test.go @@ -1,17 +1,17 @@ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) func TestRGATreeSplit(t *testing.T) { t.Run("compare nil id panic test", func(t *testing.T) { - id := json.NewRGATreeSplitNodeID(time.InitialTicket, 0) + id := crdt.NewRGATreeSplitNodeID(time.InitialTicket, 0) assert.Panics(t, func() { id.Compare(nil) }, "ID cannot be null") }) } diff --git a/pkg/document/json/rht.go b/pkg/document/crdt/rht.go similarity index 99% rename from pkg/document/json/rht.go rename to pkg/document/crdt/rht.go index db66913e5..f7d729efb 100644 --- a/pkg/document/json/rht.go +++ b/pkg/document/crdt/rht.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "fmt" diff --git a/pkg/document/json/rht_pq_map.go b/pkg/document/crdt/rht_pq_map.go similarity index 99% rename from pkg/document/json/rht_pq_map.go rename to pkg/document/crdt/rht_pq_map.go index 0e60da832..cf023f45d 100644 --- a/pkg/document/json/rht_pq_map.go +++ b/pkg/document/crdt/rht_pq_map.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "fmt" diff --git a/pkg/document/crdt/rich_text.go b/pkg/document/crdt/rich_text.go new file mode 100644 index 000000000..7877255aa --- /dev/null +++ b/pkg/document/crdt/rich_text.go @@ -0,0 +1,287 @@ +/* + * Copyright 2020 The Yorkie Authors. All rights reserved. + * + * 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 crdt + +import ( + "fmt" + "strings" + "unicode/utf16" + + "github.com/yorkie-team/yorkie/pkg/document/time" +) + +// RichTextValue is a value of RichText which has an attributes that represent +// the text style. +type RichTextValue struct { + attrs *RHT + value string +} + +// NewRichTextValue creates a value of RichText. +func NewRichTextValue(attrs *RHT, value string) *RichTextValue { + return &RichTextValue{ + attrs: attrs, + value: value, + } +} + +// Attrs returns the attributes of this value. +func (t *RichTextValue) Attrs() *RHT { + return t.attrs +} + +// Value returns the value of this rich text value. +func (t *RichTextValue) Value() string { + return t.value +} + +// Len returns the length of this value. +// It is calculated in UTF-16 code units. +func (t *RichTextValue) Len() int { + encoded := utf16.Encode([]rune(t.value)) + return len(encoded) +} + +// String returns the string representation of this value. +func (t *RichTextValue) String() string { + return t.value +} + +// Marshal returns the JSON encoding of this text. +func (t *RichTextValue) Marshal() string { + return fmt.Sprintf( + `{"attrs":%s,"val":"%s"}`, + t.attrs.Marshal(), + EscapeString(t.value), + ) +} + +// AnnotatedString returns a String containing the metadata of this value +// for debugging purpose. +func (t *RichTextValue) AnnotatedString() string { + return fmt.Sprintf( + `%s "%s"`, + t.attrs.Marshal(), + EscapeString(t.value), + ) +} + +// Split splits this value by the given offset. +func (t *RichTextValue) Split(offset int) RGATreeSplitValue { + value := t.value + encoded := utf16.Encode([]rune(value)) + t.value = string(utf16.Decode(encoded[0:offset])) + return NewRichTextValue(t.attrs.DeepCopy(), string(utf16.Decode(encoded[offset:]))) +} + +// DeepCopy copies itself deeply. +func (t *RichTextValue) DeepCopy() RGATreeSplitValue { + return &RichTextValue{ + attrs: t.attrs.DeepCopy(), + value: t.value, + } +} + +// InitialRichTextNode creates an initial node of RichText. The text is edited +// as this node is split into multiple nodes. +func InitialRichTextNode() *RGATreeSplitNode[*RichTextValue] { + return NewRGATreeSplitNode(initialNodeID, &RichTextValue{ + attrs: NewRHT(), + value: "", + }) +} + +// RichText is an extended data type for the contents of a text editor. +type RichText struct { + rgaTreeSplit *RGATreeSplit[*RichTextValue] + selectionMap map[string]*Selection + createdAt *time.Ticket + movedAt *time.Ticket + removedAt *time.Ticket +} + +// NewRichText creates a new instance of RichText. +func NewRichText(elements *RGATreeSplit[*RichTextValue], createdAt *time.Ticket) *RichText { + return &RichText{ + rgaTreeSplit: elements, + selectionMap: make(map[string]*Selection), + createdAt: createdAt, + } +} + +// NewInitialRichText creates a new instance of RichText. +func NewInitialRichText(elements *RGATreeSplit[*RichTextValue], createdAt *time.Ticket) *RichText { + text := NewRichText(elements, createdAt) + fromPos, toPos := text.CreateRange(0, 0) + text.Edit(fromPos, toPos, nil, "\n", nil, createdAt) + return text +} + +// Marshal returns the JSON encoding of this rich text. +func (t *RichText) Marshal() string { + var values []string + + node := t.rgaTreeSplit.initialHead.next + for node != nil { + if node.createdAt().Compare(t.createdAt) == 0 { + // last line + } else if node.removedAt == nil { + values = append(values, node.Marshal()) + } + node = node.next + } + + return fmt.Sprintf("[%s]", strings.Join(values, ",")) +} + +// DeepCopy copies itself deeply. +func (t *RichText) DeepCopy() Element { + rgaTreeSplit := NewRGATreeSplit(InitialRichTextNode()) + + current := rgaTreeSplit.InitialHead() + for _, node := range t.Nodes() { + current = rgaTreeSplit.InsertAfter(current, node.DeepCopy()) + insPrevID := node.InsPrevID() + if insPrevID != nil { + insPrevNode := rgaTreeSplit.FindNode(insPrevID) + if insPrevNode == nil { + panic("insPrevNode should be presence") + } + current.SetInsPrev(insPrevNode) + } + } + + return NewRichText(rgaTreeSplit, t.createdAt) +} + +// CreatedAt returns the creation time of this Text. +func (t *RichText) CreatedAt() *time.Ticket { + return t.createdAt +} + +// RemovedAt returns the removal time of this Text. +func (t *RichText) RemovedAt() *time.Ticket { + return t.removedAt +} + +// MovedAt returns the move time of this Text. +func (t *RichText) MovedAt() *time.Ticket { + return t.movedAt +} + +// SetMovedAt sets the move time of this Text. +func (t *RichText) SetMovedAt(movedAt *time.Ticket) { + t.movedAt = movedAt +} + +// SetRemovedAt sets the removal time of this array. +func (t *RichText) SetRemovedAt(removedAt *time.Ticket) { + t.removedAt = removedAt +} + +// Remove removes this Text. +func (t *RichText) Remove(removedAt *time.Ticket) bool { + if (removedAt != nil && removedAt.After(t.createdAt)) && + (t.removedAt == nil || removedAt.After(t.removedAt)) { + t.removedAt = removedAt + return true + } + return false +} + +// CreateRange returns a pair of RGATreeSplitNodePos of the given integer offsets. +func (t *RichText) CreateRange(from, to int) (*RGATreeSplitNodePos, *RGATreeSplitNodePos) { + return t.rgaTreeSplit.createRange(from, to) +} + +// Edit edits the given range with the given content and attributes. +func (t *RichText) Edit( + from, + to *RGATreeSplitNodePos, + latestCreatedAtMapByActor map[string]*time.Ticket, + content string, + attributes map[string]string, + executedAt *time.Ticket, +) (*RGATreeSplitNodePos, map[string]*time.Ticket) { + val := NewRichTextValue(NewRHT(), content) + for key, value := range attributes { + val.attrs.Set(key, value, executedAt) + } + + cursorPos, latestCreatedAtMapByActor := t.rgaTreeSplit.edit( + from, + to, + latestCreatedAtMapByActor, + val, + executedAt, + ) + + return cursorPos, latestCreatedAtMapByActor +} + +// SetStyle applies the style of the given range. +func (t *RichText) SetStyle( + from, + to *RGATreeSplitNodePos, + attributes map[string]string, + executedAt *time.Ticket, +) { + // 01. Split nodes with from and to + _, toRight := t.rgaTreeSplit.findNodeWithSplit(to, executedAt) + _, fromRight := t.rgaTreeSplit.findNodeWithSplit(from, executedAt) + + // 02. style nodes between from and to + nodes := t.rgaTreeSplit.findBetween(fromRight, toRight) + for _, node := range nodes { + val := node.value + for key, value := range attributes { + val.attrs.Set(key, value, executedAt) + } + } +} + +// Select stores that the given range has been selected. +func (t *RichText) Select( + from *RGATreeSplitNodePos, + to *RGATreeSplitNodePos, + executedAt *time.Ticket, +) { + if prev, ok := t.selectionMap[executedAt.ActorIDHex()]; !ok || executedAt.After(prev.updatedAt) { + t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) + } +} + +// Nodes returns the internal nodes of this rich text. +func (t *RichText) Nodes() []*RGATreeSplitNode[*RichTextValue] { + return t.rgaTreeSplit.nodes() +} + +// AnnotatedString returns a String containing the metadata of the text +// for debugging purpose. +func (t *RichText) AnnotatedString() string { + return t.rgaTreeSplit.AnnotatedString() +} + +// removedNodesLen returns length of removed nodes +func (t *RichText) removedNodesLen() int { + return t.rgaTreeSplit.removedNodesLen() +} + +// purgeTextNodesWithGarbage physically purges nodes that have been removed. +func (t *RichText) purgeTextNodesWithGarbage(ticket *time.Ticket) int { + return t.rgaTreeSplit.purgeTextNodesWithGarbage(ticket) +} diff --git a/pkg/document/json/rich_text_test.go b/pkg/document/crdt/rich_text_test.go similarity index 92% rename from pkg/document/json/rich_text_test.go rename to pkg/document/crdt/rich_text_test.go index 63c05bee6..b23dcd7f4 100644 --- a/pkg/document/json/rich_text_test.go +++ b/pkg/document/crdt/rich_text_test.go @@ -14,14 +14,14 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/test/helper" ) @@ -30,7 +30,7 @@ func TestRichText(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - text := json.NewInitialRichText(json.NewRGATreeSplit(json.InitialRichTextNode()), ctx.IssueTimeTicket()) + text := crdt.NewInitialRichText(crdt.NewRGATreeSplit(crdt.InitialRichTextNode()), ctx.IssueTimeTicket()) fromPos, toPos := text.CreateRange(0, 0) text.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket()) diff --git a/pkg/document/json/root.go b/pkg/document/crdt/root.go similarity index 99% rename from pkg/document/json/root.go rename to pkg/document/crdt/root.go index 598cfb01b..37d4432d0 100644 --- a/pkg/document/json/root.go +++ b/pkg/document/crdt/root.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "github.com/yorkie-team/yorkie/pkg/document/time" diff --git a/pkg/document/json/root_test.go b/pkg/document/crdt/root_test.go similarity index 86% rename from pkg/document/json/root_test.go rename to pkg/document/crdt/root_test.go index 55c3a2398..d105fa2d6 100644 --- a/pkg/document/json/root_test.go +++ b/pkg/document/crdt/root_test.go @@ -14,19 +14,19 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" "github.com/yorkie-team/yorkie/test/helper" ) -func registerTextElementWithGarbage(fromPos, toPos *json.RGATreeSplitNodePos, root *json.Root, text json.TextElement) { +func registerTextElementWithGarbage(fromPos, toPos *crdt.RGATreeSplitNodePos, root *crdt.Root, text crdt.TextElement) { if !fromPos.Equal(toPos) { root.RegisterTextElementWithGarbage(text) } @@ -36,11 +36,11 @@ func TestRoot(t *testing.T) { t.Run("garbage collection for array test", func(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - array := json.NewArray(json.NewRGATreeList(), ctx.IssueTimeTicket()) + array := crdt.NewArray(crdt.NewRGATreeList(), ctx.IssueTimeTicket()) - array.Add(json.NewPrimitive(0, ctx.IssueTimeTicket())) - array.Add(json.NewPrimitive(1, ctx.IssueTimeTicket())) - array.Add(json.NewPrimitive(2, ctx.IssueTimeTicket())) + array.Add(crdt.NewPrimitive(0, ctx.IssueTimeTicket())) + array.Add(crdt.NewPrimitive(1, ctx.IssueTimeTicket())) + array.Add(crdt.NewPrimitive(2, ctx.IssueTimeTicket())) assert.Equal(t, "[0,1,2]", array.Marshal()) targetElement := array.Get(1) @@ -56,7 +56,7 @@ func TestRoot(t *testing.T) { t.Run("garbage collection for text test", func(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - text := json.NewText(json.NewRGATreeSplit(json.InitialTextNode()), ctx.IssueTimeTicket()) + text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket()) fromPos, toPos := text.CreateRange(0, 0) text.Edit(fromPos, toPos, nil, "Hello World", ctx.IssueTimeTicket()) @@ -104,7 +104,7 @@ func TestRoot(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - text := json.NewText(json.NewRGATreeSplit(json.InitialTextNode()), ctx.IssueTimeTicket()) + text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket()) tests := []test{ {from: 0, to: 0, content: "Yorkie", want: `"Yorkie"`, garbage: 0}, @@ -128,7 +128,7 @@ func TestRoot(t *testing.T) { t.Run("garbage collection for rich text test", func(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - richText := json.NewRichText(json.NewRGATreeSplit(json.InitialRichTextNode()), ctx.IssueTimeTicket()) + richText := crdt.NewRichText(crdt.NewRGATreeSplit(crdt.InitialRichTextNode()), ctx.IssueTimeTicket()) fromPos, toPos := richText.CreateRange(0, 0) richText.Edit(fromPos, toPos, nil, "Hello World", nil, ctx.IssueTimeTicket()) @@ -165,13 +165,13 @@ func TestRoot(t *testing.T) { ctx := helper.TextChangeContext(root) obj := root.Object() - obj.Set("1", json.NewPrimitive(1, ctx.IssueTimeTicket())) - arr := json.NewArray(json.NewRGATreeList(), ctx.IssueTimeTicket()). - Add(json.NewPrimitive(1, ctx.IssueTimeTicket())). - Add(json.NewPrimitive(2, ctx.IssueTimeTicket())). - Add(json.NewPrimitive(3, ctx.IssueTimeTicket())) + obj.Set("1", crdt.NewPrimitive(1, ctx.IssueTimeTicket())) + arr := crdt.NewArray(crdt.NewRGATreeList(), ctx.IssueTimeTicket()). + Add(crdt.NewPrimitive(1, ctx.IssueTimeTicket())). + Add(crdt.NewPrimitive(2, ctx.IssueTimeTicket())). + Add(crdt.NewPrimitive(3, ctx.IssueTimeTicket())) obj.Set("2", arr) - obj.Set("3", json.NewPrimitive(3, ctx.IssueTimeTicket())) + obj.Set("3", crdt.NewPrimitive(3, ctx.IssueTimeTicket())) assert.Equal(t, `{"1":1,"2":[1,2,3],"3":3}`, root.Object().Marshal()) deleted := obj.Delete("2", ctx.IssueTimeTicket()) diff --git a/pkg/document/json/strings.go b/pkg/document/crdt/strings.go similarity index 99% rename from pkg/document/json/strings.go rename to pkg/document/crdt/strings.go index 5008a5d8f..4ba0ed017 100644 --- a/pkg/document/json/strings.go +++ b/pkg/document/crdt/strings.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import "bytes" diff --git a/pkg/document/json/strings_test.go b/pkg/document/crdt/strings_test.go similarity index 98% rename from pkg/document/json/strings_test.go rename to pkg/document/crdt/strings_test.go index 890984ea0..0ae55f888 100644 --- a/pkg/document/json/strings_test.go +++ b/pkg/document/crdt/strings_test.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package json +package crdt import ( "testing" diff --git a/pkg/document/crdt/text.go b/pkg/document/crdt/text.go new file mode 100644 index 000000000..51ddf7eea --- /dev/null +++ b/pkg/document/crdt/text.go @@ -0,0 +1,233 @@ +/* + * Copyright 2020 The Yorkie Authors. All rights reserved. + * + * 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 crdt + +import ( + "fmt" + "unicode/utf16" + + "github.com/yorkie-team/yorkie/pkg/document/time" +) + +// TextValue is a value of Text. +type TextValue struct { + value string +} + +// NewTextValue creates a value of Text. +func NewTextValue(value string) *TextValue { + return &TextValue{ + value: value, + } +} + +// Len returns the length of this value. +// It is calculated in UTF-16 code units. +func (t *TextValue) Len() int { + encoded := utf16.Encode([]rune(t.value)) + return len(encoded) +} + +// String returns the string representation of this value. +func (t *TextValue) String() string { + return t.value +} + +// Marshal returns the JSON encoding of this text. +func (t *TextValue) Marshal() string { + return EscapeString(t.value) +} + +// AnnotatedString returns a String containing the metadata of this value +// for debugging purpose. +func (t *TextValue) AnnotatedString() string { + return EscapeString(t.value) +} + +// Split splits this value by the given offset. +func (t *TextValue) Split(offset int) RGATreeSplitValue { + value := t.value + encoded := utf16.Encode([]rune(value)) + t.value = string(utf16.Decode(encoded[0:offset])) + return NewTextValue(string(utf16.Decode(encoded[offset:]))) +} + +// DeepCopy copies itself deeply. +func (t *TextValue) DeepCopy() RGATreeSplitValue { + return &TextValue{ + value: t.value, + } +} + +// InitialTextNode creates an initial node of Text. The text is edited +// as this node is split into multiple nodes. +func InitialTextNode() *RGATreeSplitNode[*TextValue] { + return NewRGATreeSplitNode(initialNodeID, &TextValue{ + value: "", + }) +} + +// Text is an extended data type for the contents of a text editor. +type Text struct { + rgaTreeSplit *RGATreeSplit[*TextValue] + selectionMap map[string]*Selection + createdAt *time.Ticket + movedAt *time.Ticket + removedAt *time.Ticket +} + +// NewText creates a new instance of Text. +func NewText(elements *RGATreeSplit[*TextValue], createdAt *time.Ticket) *Text { + return &Text{ + rgaTreeSplit: elements, + selectionMap: make(map[string]*Selection), + createdAt: createdAt, + } +} + +// Marshal returns the JSON encoding of this text. +func (t *Text) Marshal() string { + return fmt.Sprintf(`"%s"`, t.rgaTreeSplit.marshal()) +} + +// String returns a string representation of this text. +func (t *Text) String() string { + return t.rgaTreeSplit.string() +} + +// DeepCopy copies itself deeply. +func (t *Text) DeepCopy() Element { + rgaTreeSplit := NewRGATreeSplit(InitialTextNode()) + + current := rgaTreeSplit.InitialHead() + for _, node := range t.Nodes() { + current = rgaTreeSplit.InsertAfter(current, node.DeepCopy()) + insPrevID := node.InsPrevID() + if insPrevID != nil { + insPrevNode := rgaTreeSplit.FindNode(insPrevID) + if insPrevNode == nil { + panic("insPrevNode should be presence") + } + current.SetInsPrev(insPrevNode) + } + } + + return NewText(rgaTreeSplit, t.createdAt) +} + +// CreatedAt returns the creation time of this Text. +func (t *Text) CreatedAt() *time.Ticket { + return t.createdAt +} + +// RemovedAt returns the removal time of this Text. +func (t *Text) RemovedAt() *time.Ticket { + return t.removedAt +} + +// MovedAt returns the move time of this Text. +func (t *Text) MovedAt() *time.Ticket { + return t.movedAt +} + +// SetMovedAt sets the move time of this Text. +func (t *Text) SetMovedAt(movedAt *time.Ticket) { + t.movedAt = movedAt +} + +// SetRemovedAt sets the removal time of this array. +func (t *Text) SetRemovedAt(removedAt *time.Ticket) { + t.removedAt = removedAt +} + +// Remove removes this Text. +func (t *Text) Remove(removedAt *time.Ticket) bool { + if (removedAt != nil && removedAt.After(t.createdAt)) && + (t.removedAt == nil || removedAt.After(t.removedAt)) { + t.removedAt = removedAt + return true + } + return false +} + +// CreateRange returns a pair of RGATreeSplitNodePos of the given integer offsets. +func (t *Text) CreateRange(from, to int) (*RGATreeSplitNodePos, *RGATreeSplitNodePos) { + return t.rgaTreeSplit.createRange(from, to) +} + +// Edit edits the given range with the given content. +func (t *Text) Edit( + from, + to *RGATreeSplitNodePos, + latestCreatedAtMapByActor map[string]*time.Ticket, + content string, + executedAt *time.Ticket, +) (*RGATreeSplitNodePos, map[string]*time.Ticket) { + cursorPos, latestCreatedAtMapByActor := t.rgaTreeSplit.edit( + from, + to, + latestCreatedAtMapByActor, + NewTextValue(content), + executedAt, + ) + + return cursorPos, latestCreatedAtMapByActor +} + +// Select stores that the given range has been selected. +func (t *Text) Select( + from *RGATreeSplitNodePos, + to *RGATreeSplitNodePos, + executedAt *time.Ticket, +) { + if _, ok := t.selectionMap[executedAt.ActorIDHex()]; !ok { + t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) + return + } + + prevSelection := t.selectionMap[executedAt.ActorIDHex()] + if executedAt.After(prevSelection.updatedAt) { + t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) + } +} + +// Nodes returns the internal nodes of this text. +func (t *Text) Nodes() []*RGATreeSplitNode[*TextValue] { + return t.rgaTreeSplit.nodes() +} + +// AnnotatedString returns a String containing the metadata of the text +// for debugging purpose. +func (t *Text) AnnotatedString() string { + return t.rgaTreeSplit.AnnotatedString() +} + +// CheckWeight returns false when there is an incorrect weight node. +// for debugging purpose. +func (t *Text) CheckWeight() bool { + return t.rgaTreeSplit.CheckWeight() +} + +// removedNodesLen returns length of removed nodes +func (t *Text) removedNodesLen() int { + return t.rgaTreeSplit.removedNodesLen() +} + +// purgeTextNodesWithGarbage physically purges nodes that have been removed. +func (t *Text) purgeTextNodesWithGarbage(ticket *time.Ticket) int { + return t.rgaTreeSplit.purgeTextNodesWithGarbage(ticket) +} diff --git a/pkg/document/json/text_test.go b/pkg/document/crdt/text_test.go similarity index 88% rename from pkg/document/json/text_test.go rename to pkg/document/crdt/text_test.go index 7a95e5b9c..6fcc03129 100644 --- a/pkg/document/json/text_test.go +++ b/pkg/document/crdt/text_test.go @@ -14,14 +14,14 @@ * limitations under the License. */ -package json_test +package crdt_test import ( "testing" "github.com/stretchr/testify/assert" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/test/helper" ) @@ -29,7 +29,7 @@ func TestText(t *testing.T) { t.Run("marshal test", func(t *testing.T) { root := helper.TestRoot() ctx := helper.TextChangeContext(root) - text := json.NewText(json.NewRGATreeSplit(json.InitialTextNode()), ctx.IssueTimeTicket()) + text := crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ctx.IssueTimeTicket()) fromPos, toPos := text.CreateRange(0, 0) text.Edit(fromPos, toPos, nil, "Hello World", ctx.IssueTimeTicket()) @@ -52,11 +52,11 @@ func TestText(t *testing.T) { {10, "Ĺo͂řȩm̅"}, } for _, test := range tests { - val := json.NewTextValue(test.value) + val := crdt.NewTextValue(test.value) assert.Equal(t, test.length, val.Len()) assert.Equal(t, test.length-2, val.Split(2).Len()) - richVal := json.NewRichTextValue(json.NewRHT(), test.value) + richVal := crdt.NewRichTextValue(crdt.NewRHT(), test.value) assert.Equal(t, test.length, richVal.Len()) assert.Equal(t, test.length-2, richVal.Split(2).Len()) } diff --git a/pkg/document/document.go b/pkg/document/document.go index a6d54fd88..20d4129af 100644 --- a/pkg/document/document.go +++ b/pkg/document/document.go @@ -20,9 +20,9 @@ import ( "fmt" "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -39,7 +39,7 @@ type Document struct { // clone is a copy of `doc` to be exposed to the user and is used to // protect `doc`. - clone *json.Root + clone *crdt.Root } // New creates a new instance of Document. @@ -51,7 +51,7 @@ func New(key key.Key) *Document { // Update executes the given updater to update this document. func (d *Document) Update( - updater func(root *proxy.ObjectProxy) error, + updater func(root *json.Object) error, msgAndArgs ...interface{}, ) error { d.ensureClone() @@ -62,7 +62,7 @@ func (d *Document) Update( d.clone, ) - if err := updater(proxy.NewObjectProxy(ctx, d.clone.Object())); err != nil { + if err := updater(json.NewObject(ctx, d.clone.Object())); err != nil { // drop clone because it is contaminated. d.clone = nil return err @@ -172,17 +172,17 @@ func (d *Document) IsAttached() bool { return d.doc.IsAttached() } -// RootObject returns the root object. -func (d *Document) RootObject() *json.Object { +// RootObject returns the internal root object of this document. +func (d *Document) RootObject() *crdt.Object { return d.doc.RootObject() } -// Root returns the proxy of the root object. -func (d *Document) Root() *proxy.ObjectProxy { +// Root returns the root object of this document. +func (d *Document) Root() *json.Object { d.ensureClone() ctx := change.NewContext(d.doc.changeID.Next(), "", d.clone) - return proxy.NewObjectProxy(ctx, d.clone.Object()) + return json.NewObject(ctx, d.clone.Object()) } // GarbageCollect purge elements that were removed before the given time. diff --git a/pkg/document/document_test.go b/pkg/document/document_test.go index fe05f9f31..64d0432c3 100644 --- a/pkg/document/document_test.go +++ b/pkg/document/document_test.go @@ -25,7 +25,7 @@ import ( "github.com/yorkie-team/yorkie/pkg/document" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/proxy" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -53,7 +53,7 @@ func TestDocument(t *testing.T) { doc2 := document.New("d2") doc3 := document.New("d3") - err := doc1.Update(func(root *proxy.ObjectProxy) error { + err := doc1.Update(func(root *json.Object) error { root.SetString("k1", "v1") return nil }, "updates k1") @@ -70,7 +70,7 @@ func TestDocument(t *testing.T) { assert.Equal(t, "{}", doc.Marshal()) assert.False(t, doc.HasLocalChanges()) - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") root.SetNewObject("k2").SetString("k4", "v4") root.SetNewArray("k3").AddString("v5", "v6") @@ -89,7 +89,7 @@ func TestDocument(t *testing.T) { assert.False(t, doc.HasLocalChanges()) expected := `{"k1":"v1","k2":{"k4":"v4"},"k3":["v5","v6"]}` - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") root.SetNewObject("k2").SetString("k4", "v4") root.SetNewArray("k3").AddString("v5", "v6") @@ -100,7 +100,7 @@ func TestDocument(t *testing.T) { assert.Equal(t, expected, doc.Marshal()) expected = `{"k1":"v1","k3":["v5","v6"]}` - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.Delete("k2") assert.Equal(t, expected, root.Marshal()) return nil @@ -111,7 +111,7 @@ func TestDocument(t *testing.T) { t.Run("object test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") assert.Equal(t, `{"k1":"v1"}`, root.Marshal()) root.SetString("k1", "v2") @@ -125,7 +125,7 @@ func TestDocument(t *testing.T) { t.Run("array test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewArray("k1").AddInteger(1).AddInteger(2).AddInteger(3) assert.Equal(t, 3, root.GetArray("k1").Len()) assert.Equal(t, `{"k1":[1,2,3]}`, root.Marshal()) @@ -166,7 +166,7 @@ func TestDocument(t *testing.T) { // ---------- ins links -------- // | | | // [init] - [A] - [12] - [BC deleted] - [D] - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewText("k1"). Edit(0, 0, "ABCD"). Edit(1, 3, "12") @@ -176,7 +176,7 @@ func TestDocument(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"k1":"A12D"}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("k1") assert.Equal(t, "[0:0:00:0 ][1:2:00:0 A][1:3:00:0 12]{1:2:00:1 BC}[1:2:00:3 D]", @@ -205,7 +205,7 @@ func TestDocument(t *testing.T) { t.Run("text composition test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewText("k1"). Edit(0, 0, "ㅎ"). Edit(0, 1, "하"). @@ -223,7 +223,7 @@ func TestDocument(t *testing.T) { t.Run("rich text test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewRichText("k1") text.Edit(0, 0, "Hello world", nil) assert.Equal( @@ -236,7 +236,7 @@ func TestDocument(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"k1":[{"attrs":{},"val":"Hello world"}]}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 5, map[string]string{"b": "1"}) assert.Equal(t, @@ -252,7 +252,7 @@ func TestDocument(t *testing.T) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 5, map[string]string{"b": "1"}) assert.Equal( @@ -277,7 +277,7 @@ func TestDocument(t *testing.T) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.Edit(5, 11, " Yorkie", nil) assert.Equal( @@ -295,7 +295,7 @@ func TestDocument(t *testing.T) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.Edit(5, 5, "\n", map[string]string{"list": "true"}) assert.Equal( @@ -324,7 +324,7 @@ func TestDocument(t *testing.T) { var double = 5.66 // integer type test - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewCounter("age", 5) age := root.GetCounter("age") @@ -340,7 +340,7 @@ func TestDocument(t *testing.T) { assert.Equal(t, `{"age":128}`, doc.Marshal()) // long type test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewCounter("price", 9000000000000000000) price := root.GetCounter("price") price.Increase(long) @@ -355,7 +355,7 @@ func TestDocument(t *testing.T) { assert.Equal(t, `{"age":128,"price":9000000000000000123}`, doc.Marshal()) // double type test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewCounter("width", 10.5) width := root.GetCounter("width") width.Increase(long) @@ -370,7 +370,7 @@ func TestDocument(t *testing.T) { assert.Equal(t, `{"age":128,"price":9000000000000000123,"width":134.300000}`, doc.Marshal()) // negative operator test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { age := root.GetCounter("age") age.Increase(-5) age.Increase(-3.14) @@ -390,7 +390,7 @@ func TestDocument(t *testing.T) { // TODO: it should be modified to error check // when 'Remove panic from server code (#50)' is completed. - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { defer func() { r := recover() assert.NotNil(t, r) @@ -410,21 +410,21 @@ func TestDocument(t *testing.T) { t.Run("rollback test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewArray("k1").AddInteger(1, 2, 3) return nil }) assert.NoError(t, err) assert.Equal(t, `{"k1":[1,2,3]}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.GetArray("k1").AddInteger(4, 5) return errDummy }) assert.Equal(t, err, errDummy, "should returns the dummy error") assert.Equal(t, `{"k1":[1,2,3]}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.GetArray("k1").AddInteger(4, 5) return nil }) @@ -435,7 +435,7 @@ func TestDocument(t *testing.T) { t.Run("rollback test, primitive deepcopy", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewObject("k1"). SetInteger("k1.1", 1). SetInteger("k1.2", 2) @@ -444,7 +444,7 @@ func TestDocument(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"k1":{"k1.1":1,"k1.2":2}}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.GetObject("k1").Delete("k1.1") return errDummy }) @@ -455,7 +455,7 @@ func TestDocument(t *testing.T) { t.Run("text garbage collection test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewText("text") root.GetText("text").Edit(0, 0, "ABCD") root.GetText("text").Edit(0, 2, "12") @@ -477,7 +477,7 @@ func TestDocument(t *testing.T) { doc.Root().GetText("text").AnnotatedString(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.GetText("text").Edit(2, 4, "") return nil }) @@ -492,7 +492,7 @@ func TestDocument(t *testing.T) { t.Run("previously inserted elements in heap when running GC test", func(t *testing.T) { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetInteger("a", 1) root.SetInteger("a", 2) root.Delete("a") diff --git a/pkg/document/internal_document.go b/pkg/document/internal_document.go index d57ce9230..7bcc26454 100644 --- a/pkg/document/internal_document.go +++ b/pkg/document/internal_document.go @@ -19,7 +19,7 @@ package document import ( "github.com/yorkie-team/yorkie/api/converter" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/key" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -40,7 +40,7 @@ const ( type InternalDocument struct { key key.Key status statusType - root *json.Root + root *crdt.Root checkpoint change.Checkpoint changeID change.ID localChanges []*change.Change @@ -48,12 +48,12 @@ type InternalDocument struct { // NewInternalDocument creates a new instance of InternalDocument. func NewInternalDocument(k key.Key) *InternalDocument { - root := json.NewObject(json.NewRHTPriorityQueueMap(), time.InitialTicket) + root := crdt.NewObject(crdt.NewRHTPriorityQueueMap(), time.InitialTicket) return &InternalDocument{ key: k, status: Detached, - root: json.NewRoot(root), + root: crdt.NewRoot(root), checkpoint: change.InitialCheckpoint, changeID: change.InitialID, } @@ -74,7 +74,7 @@ func NewInternalDocumentFromSnapshot( return &InternalDocument{ key: k, status: Detached, - root: json.NewRoot(obj), + root: crdt.NewRoot(obj), checkpoint: change.InitialCheckpoint.NextServerSeq(serverSeq), changeID: change.InitialID.SyncLamport(lamport), }, nil @@ -180,12 +180,12 @@ func (d *InternalDocument) IsAttached() bool { } // Root returns the root of this document. -func (d *InternalDocument) Root() *json.Root { +func (d *InternalDocument) Root() *crdt.Root { return d.root } // RootObject returns the root object. -func (d *InternalDocument) RootObject() *json.Object { +func (d *InternalDocument) RootObject() *crdt.Object { return d.root.Object() } @@ -195,7 +195,7 @@ func (d *InternalDocument) applySnapshot(snapshot []byte, serverSeq int64) error return err } - d.root = json.NewRoot(rootObj) + d.root = crdt.NewRoot(rootObj) d.changeID = d.changeID.SyncLamport(serverSeq) return nil diff --git a/pkg/document/json/array.go b/pkg/document/json/array.go index 5c6812472..1c0ba8e03 100644 --- a/pkg/document/json/array.go +++ b/pkg/document/json/array.go @@ -17,168 +17,199 @@ package json import ( + gotime "time" + + "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" + "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" ) -// Array represents JSON array data structure including logical clock. -// Array implements Element interface. +// Array represents an array in the document. As a proxy for the CRDT array, +// it is used when the user manipulate the array from the outside. type Array struct { - elements *RGATreeList - createdAt *time.Ticket - movedAt *time.Ticket - removedAt *time.Ticket + *crdt.Array + context *change.Context } // NewArray creates a new instance of Array. -func NewArray(elements *RGATreeList, createdAt *time.Ticket) *Array { +func NewArray(ctx *change.Context, array *crdt.Array) *Array { return &Array{ - elements: elements, - createdAt: createdAt, + Array: array, + context: ctx, } } -// Purge physically purge child element. -func (a *Array) Purge(elem Element) { - a.elements.purge(elem) -} +// AddNull adds the null at the last. +func (p *Array) AddNull() *Array { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(nil, ticket) + }) -// Add adds the given element at the last. -func (a *Array) Add(elem Element) *Array { - a.elements.Add(elem) - return a + return p } -// Get returns the element of the given index. -func (a *Array) Get(idx int) Element { - return a.elements.Get(idx).elem -} +// AddBool adds the given boolean at the last. +func (p *Array) AddBool(values ...bool) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) + } -// FindPrevCreatedAt returns the creation time of the previous element of the -// given element. -func (a *Array) FindPrevCreatedAt(createdAt *time.Ticket) *time.Ticket { - return a.elements.FindPrevCreatedAt(createdAt) + return p } -// Delete deletes the element of the given index. -func (a *Array) Delete(idx int, deletedAt *time.Ticket) Element { - return a.elements.Delete(idx, deletedAt).elem +// AddInteger adds the given integer at the last. +func (p *Array) AddInteger(values ...int) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) + } + + return p } -// MoveAfter moves the given `createdAt` element after the `prevCreatedAt` -// element. -func (a *Array) MoveAfter(prevCreatedAt, createdAt, executedAt *time.Ticket) { - a.elements.MoveAfter(prevCreatedAt, createdAt, executedAt) -} - -// Elements returns an array of elements contained in this RGATreeList. -func (a *Array) Elements() []Element { - var elements []Element - for _, node := range a.elements.Nodes() { - if node.isRemoved() { - continue - } - elements = append(elements, node.elem) +// AddLong adds the given long at the last. +func (p *Array) AddLong(values ...int64) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) } - return elements + return p } -// Marshal returns the JSON encoding of this Array. -func (a *Array) Marshal() string { - return a.elements.Marshal() -} +// AddDouble adds the given double at the last. +func (p *Array) AddDouble(values ...float64) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) + } -// AnnotatedString returns a String containing the metadata of the elements -// for debugging purpose. -func (a *Array) AnnotatedString() string { - return a.elements.AnnotatedString() + return p } -// DeepCopy copies itself deeply. -func (a *Array) DeepCopy() Element { - elements := NewRGATreeList() - - for _, node := range a.elements.Nodes() { - elements.Add(node.elem.DeepCopy()) +// AddString adds the given string at the last. +func (p *Array) AddString(values ...string) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) } - array := NewArray(elements, a.createdAt) - array.removedAt = a.removedAt - return array + return p } -// CreatedAt returns the creation time of this array. -func (a *Array) CreatedAt() *time.Ticket { - return a.createdAt +// AddBytes adds the given bytes at the last. +func (p *Array) AddBytes(values ...[]byte) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) + } + + return p } -// MovedAt returns the move time of this array. -func (a *Array) MovedAt() *time.Ticket { - return a.movedAt +// AddDate adds the given date at the last. +func (p *Array) AddDate(values ...gotime.Time) *Array { + for _, value := range values { + p.addInternal(func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(value, ticket) + }) + } + + return p } -// SetMovedAt sets the move time of this array. -func (a *Array) SetMovedAt(movedAt *time.Ticket) { - a.movedAt = movedAt +// AddNewArray adds a new array at the last. +func (p *Array) AddNewArray() *Array { + v := p.addInternal(func(ticket *time.Ticket) crdt.Element { + return NewArray(p.context, crdt.NewArray(crdt.NewRGATreeList(), ticket)) + }) + + return v.(*Array) } -// RemovedAt returns the removal time of this array. -func (a *Array) RemovedAt() *time.Ticket { - return a.removedAt +// MoveBefore moves the given element to its new position before the given next element. +func (p *Array) MoveBefore(nextCreatedAt, createdAt *time.Ticket) { + p.moveBeforeInternal(nextCreatedAt, createdAt) } -// SetRemovedAt sets the removal time of this array. -func (a *Array) SetRemovedAt(removedAt *time.Ticket) { - a.removedAt = removedAt +// InsertIntegerAfter inserts the given integer after the given previous +// element. +func (p *Array) InsertIntegerAfter(index int, v int) *Array { + p.insertAfterInternal(p.Get(index).CreatedAt(), func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) + + return p } -// Remove removes this array. -func (a *Array) Remove(removedAt *time.Ticket) bool { - if (removedAt != nil && removedAt.After(a.createdAt)) && - (a.removedAt == nil || removedAt.After(a.removedAt)) { - a.removedAt = removedAt - return true +// Delete deletes the element of the given index. +func (p *Array) Delete(idx int) crdt.Element { + if p.Len() <= idx { + return nil } - return false -} -// LastCreatedAt returns the creation time of the last element. -func (a *Array) LastCreatedAt() *time.Ticket { - return a.elements.LastCreatedAt() + ticket := p.context.IssueTimeTicket() + deleted := p.Array.Delete(idx, ticket) + p.context.Push(operations.NewRemove( + p.CreatedAt(), + deleted.CreatedAt(), + ticket, + )) + p.context.RegisterRemovedElementPair(p, deleted) + return deleted } -// InsertAfter inserts the given element after the given previous element. -func (a *Array) InsertAfter(prevCreatedAt *time.Ticket, element Element) { - a.elements.InsertAfter(prevCreatedAt, element) +// Len returns length of this Array. +func (p *Array) Len() int { + return p.Array.Len() } -// DeleteByCreatedAt deletes the given element. -func (a *Array) DeleteByCreatedAt(createdAt *time.Ticket, deletedAt *time.Ticket) Element { - return a.elements.DeleteByCreatedAt(createdAt, deletedAt).elem +func (p *Array) addInternal( + creator func(ticket *time.Ticket) crdt.Element, +) crdt.Element { + return p.insertAfterInternal(p.Array.LastCreatedAt(), creator) } -// Len returns length of this Array. -func (a *Array) Len() int { - return a.elements.Len() -} - -// Descendants traverse the descendants of this array. -func (a *Array) Descendants(callback func(elem Element, parent Container) bool) { - for _, node := range a.elements.Nodes() { - if callback(node.elem, a) { - return - } - - switch elem := node.elem.(type) { - case *Object: - elem.Descendants(callback) - case *Array: - elem.Descendants(callback) - } - } +func (p *Array) insertAfterInternal( + prevCreatedAt *time.Ticket, + creator func(ticket *time.Ticket) crdt.Element, +) crdt.Element { + ticket := p.context.IssueTimeTicket() + elem := creator(ticket) + value := toOriginal(elem) + + p.context.Push(operations.NewAdd( + p.Array.CreatedAt(), + prevCreatedAt, + value.DeepCopy(), + ticket, + )) + + p.InsertAfter(prevCreatedAt, value) + p.context.RegisterElement(value) + + return elem } -// RGANodes returns the slices of RGATreeListNode. -func (a *Array) RGANodes() []*RGATreeListNode { - return a.elements.Nodes() +func (p *Array) moveBeforeInternal(nextCreatedAt, createdAt *time.Ticket) { + ticket := p.context.IssueTimeTicket() + + prevCreatedAt := p.FindPrevCreatedAt(nextCreatedAt) + + p.context.Push(operations.NewMove( + p.Array.CreatedAt(), + prevCreatedAt, + createdAt, + ticket, + )) + + p.MoveAfter(prevCreatedAt, createdAt, ticket) } diff --git a/pkg/document/json/counter.go b/pkg/document/json/counter.go index 66c10c85c..aaf02ad56 100644 --- a/pkg/document/json/counter.go +++ b/pkg/document/json/counter.go @@ -17,209 +17,112 @@ package json import ( - "encoding/binary" - "fmt" - "math" + "reflect" - "github.com/yorkie-team/yorkie/pkg/document/time" + "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" + "github.com/yorkie-team/yorkie/pkg/document/operations" ) -// CounterType represents any type that can be used as a counter. -type CounterType int - -// The values below are the types that can be used as counters. -const ( - IntegerCnt CounterType = iota - LongCnt - DoubleCnt -) - -// CounterValueFromBytes parses the given bytes into value. -func CounterValueFromBytes(counterType CounterType, value []byte) interface{} { - switch counterType { - case IntegerCnt: - val := int32(binary.LittleEndian.Uint32(value)) - return int(val) - case LongCnt: - return int64(binary.LittleEndian.Uint64(value)) - case DoubleCnt: - return math.Float64frombits(binary.LittleEndian.Uint64(value)) - } - - panic("unsupported type") -} - -// Counter represents changeable number data type. +// Counter represents a counter in the document. As a proxy for the CRDT counter, +// it is used when the user manipulates the counter from the outside. type Counter struct { - valueType CounterType - value interface{} - createdAt *time.Ticket - movedAt *time.Ticket - removedAt *time.Ticket + *crdt.Counter + context *change.Context } -// NewCounter creates a new instance of Counter. -func NewCounter(value interface{}, createdAt *time.Ticket) *Counter { - switch val := value.(type) { - case int: - if math.MaxInt32 < val || math.MinInt32 > val { - return &Counter{ - valueType: LongCnt, - value: int64(val), - createdAt: createdAt, - } - } - return &Counter{ - valueType: IntegerCnt, - value: val, - createdAt: createdAt, - } - case int64: - return &Counter{ - valueType: LongCnt, - value: val, - createdAt: createdAt, - } - case float64: - return &Counter{ - valueType: DoubleCnt, - value: val, - createdAt: createdAt, - } +// NewCounter create Counter instance. +func NewCounter(ctx *change.Context, counter *crdt.Counter) *Counter { + if !counter.IsNumericType() { + panic("unsupported type") } - - panic("unsupported type") -} - -// Bytes creates an array representing the value. -func (p *Counter) Bytes() []byte { - switch val := p.value.(type) { - case int: - bytes := [4]byte{} - binary.LittleEndian.PutUint32(bytes[:], uint32(val)) - return bytes[:] - case int64: - bytes := [8]byte{} - binary.LittleEndian.PutUint64(bytes[:], uint64(val)) - return bytes[:] - case float64: - bytes := [8]byte{} - binary.LittleEndian.PutUint64(bytes[:], math.Float64bits(val)) - return bytes[:] + return &Counter{ + Counter: counter, + context: ctx, } - - panic("unsupported type") } -// Marshal returns the JSON encoding of the value. -func (p *Counter) Marshal() string { - switch p.valueType { - case IntegerCnt: - return fmt.Sprintf("%d", p.value) - case LongCnt: - return fmt.Sprintf("%d", p.value) - case DoubleCnt: - return fmt.Sprintf("%f", p.value) +// Increase adds an increase operations. +// Only numeric types are allowed as operand values, excluding +// uint64 and uintptr. +func (p *Counter) Increase(v interface{}) *Counter { + if !isAllowedOperand(v) { + panic("unsupported type") + } + var primitive *crdt.Primitive + ticket := p.context.IssueTimeTicket() + + value, kind := convertAssertableOperand(v) + isInt := kind == reflect.Int + switch p.ValueType() { + case crdt.LongCnt: + if isInt { + primitive = crdt.NewPrimitive(int64(value.(int)), ticket) + } else { + primitive = crdt.NewPrimitive(int64(value.(float64)), ticket) + } + case crdt.IntegerCnt: + if isInt { + primitive = crdt.NewPrimitive(value, ticket) + } else { + primitive = crdt.NewPrimitive(int(value.(float64)), ticket) + } + case crdt.DoubleCnt: + if isInt { + primitive = crdt.NewPrimitive(float64(value.(int)), ticket) + } else { + primitive = crdt.NewPrimitive(value, ticket) + } + default: + panic("unsupported type") } - panic("unsupported type") -} - -// DeepCopy copies itself deeply. -func (p *Counter) DeepCopy() Element { - counter := *p - return &counter -} - -// CreatedAt returns the creation time. -func (p *Counter) CreatedAt() *time.Ticket { - return p.createdAt -} - -// MovedAt returns the move time of this element. -func (p *Counter) MovedAt() *time.Ticket { - return p.movedAt -} - -// SetMovedAt sets the move time of this element. -func (p *Counter) SetMovedAt(movedAt *time.Ticket) { - p.movedAt = movedAt -} - -// RemovedAt returns the removal time of this element. -func (p *Counter) RemovedAt() *time.Ticket { - return p.removedAt -} + p.context.Push(operations.NewIncrease( + p.CreatedAt(), + primitive, + ticket, + )) -// SetRemovedAt sets the removal time of this array. -func (p *Counter) SetRemovedAt(removedAt *time.Ticket) { - p.removedAt = removedAt + return p } -// Remove removes this element. -func (p *Counter) Remove(removedAt *time.Ticket) bool { - if (removedAt != nil && removedAt.After(p.createdAt)) && - (p.removedAt == nil || removedAt.After(p.removedAt)) { - p.removedAt = removedAt +// isAllowedOperand indicates whether +// the operand of increase is an allowable type. +func isAllowedOperand(v interface{}) bool { + vt := reflect.ValueOf(v).Kind() + if vt >= reflect.Int && vt <= reflect.Float64 && vt != reflect.Uint64 && vt != reflect.Uintptr { return true } - return false -} -// ValueType returns the type of the value. -func (p *Counter) ValueType() CounterType { - return p.valueType + return false } -// Increase increases integer, long or double. -// If the result of the operation is greater than MaxInt32 or less -// than MinInt32, Counter's value type can be changed Integer to Long. -// Because in golang, int can be either int32 or int64. -// So we need to assert int to int32. -func (p *Counter) Increase(v *Primitive) *Counter { - if !p.IsNumericType() || !v.IsNumericType() { - panic("unsupported type") - } - switch p.valueType { - case IntegerCnt: - switch v.valueType { - case Long: - p.value = p.value.(int) + int(v.value.(int64)) - case Double: - p.value = p.value.(int) + int(v.value.(float64)) - default: - p.value = p.value.(int) + v.value.(int) - } - - if p.value.(int) > math.MaxInt32 || p.value.(int) < math.MinInt32 { - p.value = int64(p.value.(int)) - p.valueType = LongCnt - } - case LongCnt: - switch v.valueType { - case Integer: - p.value = p.value.(int64) + int64(v.value.(int)) - case Double: - p.value = p.value.(int64) + int64(v.value.(float64)) - default: - p.value = p.value.(int64) + v.value.(int64) - } - case DoubleCnt: - switch v.valueType { - case Integer: - p.value = p.value.(float64) + float64(v.value.(int)) - case Long: - p.value = p.value.(float64) + float64(v.value.(int64)) - default: - p.value = p.value.(float64) + v.value.(float64) - } +// convertAssertableOperand converts the operand +// to be used in the increase function to assertable type. +func convertAssertableOperand(v interface{}) (interface{}, reflect.Kind) { + vt := reflect.ValueOf(v).Kind() + switch vt { + case reflect.Int: + return v, reflect.Int + case reflect.Int8: + return int(v.(int8)), reflect.Int + case reflect.Int16: + return int(v.(int16)), reflect.Int + case reflect.Int32: + return int(v.(int32)), reflect.Int + case reflect.Int64: + return int(v.(int64)), reflect.Int + case reflect.Uint: + return int(v.(uint)), reflect.Int + case reflect.Uint8: + return int(v.(uint8)), reflect.Int + case reflect.Uint16: + return int(v.(uint16)), reflect.Int + case reflect.Uint32: + return int(v.(uint32)), reflect.Int + case reflect.Float32: + return float64(v.(float32)), reflect.Float64 + default: + return v, reflect.Float64 } - - return p -} - -// IsNumericType checks for numeric types. -func (p *Counter) IsNumericType() bool { - t := p.valueType - return t == IntegerCnt || t == LongCnt || t == DoubleCnt } diff --git a/pkg/document/proxy/proxies.go b/pkg/document/json/json.go similarity index 76% rename from pkg/document/proxy/proxies.go rename to pkg/document/json/json.go index b8d72fd2b..7332a3d34 100644 --- a/pkg/document/proxy/proxies.go +++ b/pkg/document/json/json.go @@ -14,25 +14,23 @@ * limitations under the License. */ -package proxy +package json -import ( - "github.com/yorkie-team/yorkie/pkg/document/json" -) +import "github.com/yorkie-team/yorkie/pkg/document/crdt" -func toOriginal(elem json.Element) json.Element { +func toOriginal(elem crdt.Element) crdt.Element { switch elem := elem.(type) { - case *ObjectProxy: + case *Object: return elem.Object - case *ArrayProxy: + case *Array: return elem.Array - case *TextProxy: + case *Text: return elem.Text - case *RichTextProxy: + case *RichText: return elem.RichText - case *CounterProxy: + case *Counter: return elem.Counter - case *json.Primitive: + case *crdt.Primitive: return elem } diff --git a/pkg/document/json/object.go b/pkg/document/json/object.go index 44609a673..d68d2f656 100644 --- a/pkg/document/json/object.go +++ b/pkg/document/json/object.go @@ -17,131 +17,277 @@ package json import ( + gotime "time" + + "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" + "github.com/yorkie-team/yorkie/pkg/document/operations" "github.com/yorkie-team/yorkie/pkg/document/time" ) -// Object represents a JSON object, but unlike regular JSON, it has time -// tickets which is created by logical clock. +// Object represents an object in the document. As a proxy for the CRDT object, +// it is used when the user manipulates the object from the outside. type Object struct { - memberNodes *RHTPriorityQueueMap - createdAt *time.Ticket - movedAt *time.Ticket - removedAt *time.Ticket + *crdt.Object + context *change.Context } // NewObject creates a new instance of Object. -func NewObject(memberNodes *RHTPriorityQueueMap, createdAt *time.Ticket) *Object { +func NewObject(ctx *change.Context, root *crdt.Object) *Object { return &Object{ - memberNodes: memberNodes, - createdAt: createdAt, + Object: root, + context: ctx, } } -// Purge physically purge child element. -func (o *Object) Purge(elem Element) { - o.memberNodes.purge(elem) +// SetNewObject sets a new Object for the given key. +func (p *Object) SetNewObject(k string) *Object { + v := p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return NewObject(p.context, crdt.NewObject(crdt.NewRHTPriorityQueueMap(), ticket)) + }) + + return v.(*Object) } -// Set sets the given element of the given key. -func (o *Object) Set(k string, v Element) Element { - return o.memberNodes.Set(k, v) +// SetNewArray sets a new Array for the given key. +func (p *Object) SetNewArray(k string) *Array { + v := p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return NewArray(p.context, crdt.NewArray(crdt.NewRGATreeList(), ticket)) + }) + + return v.(*Array) } -// Members returns the member of this object as a map. -func (o *Object) Members() map[string]Element { - return o.memberNodes.Elements() +// SetNewText sets a new Text for the given key. +func (p *Object) SetNewText(k string) *Text { + v := p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return NewText( + p.context, + crdt.NewText(crdt.NewRGATreeSplit(crdt.InitialTextNode()), ticket), + ) + }) + + return v.(*Text) } -// Get returns the value of the given key. -func (o *Object) Get(k string) Element { - return o.memberNodes.Get(k) +// SetNewRichText sets a new RichText for the given key. +func (p *Object) SetNewRichText(k string) *RichText { + v := p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return NewRichText( + p.context, + crdt.NewInitialRichText(crdt.NewRGATreeSplit(crdt.InitialRichTextNode()), ticket), + ) + }) + + return v.(*RichText) } -// Has returns whether the element exists of the given key or not. -func (o *Object) Has(k string) bool { - return o.memberNodes.Has(k) +// SetNewCounter sets a new NewCounter for the given key. +func (p *Object) SetNewCounter(k string, n interface{}) *Counter { + v := p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return NewCounter( + p.context, + crdt.NewCounter(n, ticket), + ) + }) + + return v.(*Counter) } -// DeleteByCreatedAt deletes the element of the given creation time. -func (o *Object) DeleteByCreatedAt(createdAt *time.Ticket, deletedAt *time.Ticket) Element { - return o.memberNodes.DeleteByCreatedAt(createdAt, deletedAt) +// SetNull sets the null for the given key. +func (p *Object) SetNull(k string) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(nil, ticket) + }) + + return p } -// Delete deletes the element of the given key. -func (o *Object) Delete(k string, deletedAt *time.Ticket) Element { - return o.memberNodes.Delete(k, deletedAt) +// SetBool sets the given boolean for the given key. +func (p *Object) SetBool(k string, v bool) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) + + return p } -// Descendants traverse the descendants of this object. -func (o *Object) Descendants(callback func(elem Element, parent Container) bool) { - for _, node := range o.memberNodes.Nodes() { - if callback(node.elem, o) { - return - } +// SetInteger sets the given integer for the given key. +func (p *Object) SetInteger(k string, v int) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) - switch elem := node.elem.(type) { - case *Object: - elem.Descendants(callback) - case *Array: - elem.Descendants(callback) - } - } + return p } -// Marshal returns the JSON encoding of this object. -func (o *Object) Marshal() string { - return o.memberNodes.Marshal() +// SetLong sets the given long for the given key. +func (p *Object) SetLong(k string, v int64) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) + + return p } -// DeepCopy copies itself deeply. -func (o *Object) DeepCopy() Element { - members := NewRHTPriorityQueueMap() +// SetDouble sets the given double for the given key. +func (p *Object) SetDouble(k string, v float64) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) - for _, node := range o.memberNodes.Nodes() { - members.SetInternal(node.key, node.elem.DeepCopy()) - } + return p +} + +// SetString sets the given string for the given key. +func (p *Object) SetString(k, v string) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) - obj := NewObject(members, o.createdAt) - obj.removedAt = o.removedAt - return obj + return p } -// CreatedAt returns the creation time of this object. -func (o *Object) CreatedAt() *time.Ticket { - return o.createdAt +// SetBytes sets the given bytes for the given key. +func (p *Object) SetBytes(k string, v []byte) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) + + return p } -// MovedAt returns the move time of this object. -func (o *Object) MovedAt() *time.Ticket { - return o.movedAt +// SetDate sets the given date for the given key. +func (p *Object) SetDate(k string, v gotime.Time) *Object { + p.setInternal(k, func(ticket *time.Ticket) crdt.Element { + return crdt.NewPrimitive(v, ticket) + }) + + return p } -// SetMovedAt sets the move time of this object. -func (o *Object) SetMovedAt(movedAt *time.Ticket) { - o.movedAt = movedAt +// Delete deletes the value of the given key. +func (p *Object) Delete(k string) crdt.Element { + if !p.Object.Has(k) { + return nil + } + + ticket := p.context.IssueTimeTicket() + deleted := p.Object.Delete(k, ticket) + p.context.Push(operations.NewRemove( + p.CreatedAt(), + deleted.CreatedAt(), + ticket, + )) + p.context.RegisterRemovedElementPair(p, deleted) + return deleted } -// RemovedAt returns the removal time of this object. -func (o *Object) RemovedAt() *time.Ticket { - return o.removedAt +// GetObject returns Object of the given key. +func (p *Object) GetObject(k string) *Object { + elem := p.Object.Get(k) + if elem == nil { + return nil + } + + switch elem := p.Object.Get(k).(type) { + case *crdt.Object: + return NewObject(p.context, elem) + case *Object: + return elem + default: + panic("unsupported type") + } } -// SetRemovedAt sets the removal time of this array. -func (o *Object) SetRemovedAt(removedAt *time.Ticket) { - o.removedAt = removedAt +// GetArray returns Array of the given key. +func (p *Object) GetArray(k string) *Array { + elem := p.Object.Get(k) + if elem == nil { + return nil + } + + switch elem := p.Object.Get(k).(type) { + case *crdt.Array: + return NewArray(p.context, elem) + case *Array: + return elem + default: + panic("unsupported type") + } +} + +// GetText returns Text of the given key. +func (p *Object) GetText(k string) *Text { + elem := p.Object.Get(k) + if elem == nil { + return nil + } + + switch elem := p.Object.Get(k).(type) { + case *crdt.Text: + return NewText(p.context, elem) + case *Text: + return elem + default: + panic("unsupported type") + } } -// Remove removes this object. -func (o *Object) Remove(removedAt *time.Ticket) bool { - if (removedAt != nil && removedAt.After(o.createdAt)) && - (o.removedAt == nil || removedAt.After(o.removedAt)) { - o.removedAt = removedAt - return true +// GetRichText returns RichText of the given key. +func (p *Object) GetRichText(k string) *RichText { + elem := p.Object.Get(k) + if elem == nil { + return nil + } + + switch elem := p.Object.Get(k).(type) { + case *crdt.RichText: + return NewRichText(p.context, elem) + case *RichText: + return elem + default: + panic("unsupported type") } - return false } -// RHTNodes returns the RHTPriorityQueueMap nodes. -func (o *Object) RHTNodes() []*RHTPQMapNode { - return o.memberNodes.Nodes() +// GetCounter returns Counter of the given key. +func (p *Object) GetCounter(k string) *Counter { + elem := p.Object.Get(k) + if elem == nil { + return nil + } + + switch elem := p.Object.Get(k).(type) { + case *crdt.Counter: + return NewCounter(p.context, elem) + case *Counter: + return elem + default: + panic("unsupported type") + } +} + +func (p *Object) setInternal( + k string, + creator func(ticket *time.Ticket) crdt.Element, +) crdt.Element { + ticket := p.context.IssueTimeTicket() + elem := creator(ticket) + value := toOriginal(elem) + + p.context.Push(operations.NewSet( + p.CreatedAt(), + k, + value.DeepCopy(), + ticket, + )) + + removed := p.Set(k, value) + p.context.RegisterElement(value) + if removed != nil { + p.context.RegisterRemovedElementPair(p, removed) + } + + return elem } diff --git a/pkg/document/json/rich_text.go b/pkg/document/json/rich_text.go index 432b25241..b32657888 100644 --- a/pkg/document/json/rich_text.go +++ b/pkg/document/json/rich_text.go @@ -17,271 +17,81 @@ package json import ( - "fmt" - "strings" - "unicode/utf16" - - "github.com/yorkie-team/yorkie/pkg/document/time" + "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" + "github.com/yorkie-team/yorkie/pkg/document/operations" ) -// RichTextValue is a value of RichText which has an attributes that represent -// the text style. -type RichTextValue struct { - attrs *RHT - value string -} - -// NewRichTextValue creates a value of RichText. -func NewRichTextValue(attrs *RHT, value string) *RichTextValue { - return &RichTextValue{ - attrs: attrs, - value: value, - } -} - -// Attrs returns the attributes of this value. -func (t *RichTextValue) Attrs() *RHT { - return t.attrs -} - -// Value returns the value of this rich text value. -func (t *RichTextValue) Value() string { - return t.value -} - -// Len returns the length of this value. -// It is calculated in UTF-16 code units. -func (t *RichTextValue) Len() int { - encoded := utf16.Encode([]rune(t.value)) - return len(encoded) -} - -// String returns the string representation of this value. -func (t *RichTextValue) String() string { - return t.value -} - -// Marshal returns the JSON encoding of this text. -func (t *RichTextValue) Marshal() string { - return fmt.Sprintf( - `{"attrs":%s,"val":"%s"}`, - t.attrs.Marshal(), - EscapeString(t.value), - ) -} - -// AnnotatedString returns a String containing the metadata of this value -// for debugging purpose. -func (t *RichTextValue) AnnotatedString() string { - return fmt.Sprintf( - `%s "%s"`, - t.attrs.Marshal(), - EscapeString(t.value), - ) -} - -// Split splits this value by the given offset. -func (t *RichTextValue) Split(offset int) RGATreeSplitValue { - value := t.value - encoded := utf16.Encode([]rune(value)) - t.value = string(utf16.Decode(encoded[0:offset])) - return NewRichTextValue(t.attrs.DeepCopy(), string(utf16.Decode(encoded[offset:]))) -} - -// DeepCopy copies itself deeply. -func (t *RichTextValue) DeepCopy() RGATreeSplitValue { - return &RichTextValue{ - attrs: t.attrs.DeepCopy(), - value: t.value, - } -} - -// InitialRichTextNode creates an initial node of RichText. The text is edited -// as this node is split into multiple nodes. -func InitialRichTextNode() *RGATreeSplitNode[*RichTextValue] { - return NewRGATreeSplitNode(initialNodeID, &RichTextValue{ - attrs: NewRHT(), - value: "", - }) -} - -// RichText is an extended data type for the contents of a text editor. +// RichText represents a rich text in the document. As a proxy for the CRDT rich +// text, it is used when the user manipulates the rich text from the outside. type RichText struct { - rgaTreeSplit *RGATreeSplit[*RichTextValue] - selectionMap map[string]*Selection - createdAt *time.Ticket - movedAt *time.Ticket - removedAt *time.Ticket + *crdt.RichText + context *change.Context } // NewRichText creates a new instance of RichText. -func NewRichText(elements *RGATreeSplit[*RichTextValue], createdAt *time.Ticket) *RichText { +func NewRichText(ctx *change.Context, text *crdt.RichText) *RichText { return &RichText{ - rgaTreeSplit: elements, - selectionMap: make(map[string]*Selection), - createdAt: createdAt, - } -} - -// NewInitialRichText creates a new instance of RichText. -func NewInitialRichText(elements *RGATreeSplit[*RichTextValue], createdAt *time.Ticket) *RichText { - text := NewRichText(elements, createdAt) - fromPos, toPos := text.CreateRange(0, 0) - text.Edit(fromPos, toPos, nil, "\n", nil, createdAt) - return text -} - -// Marshal returns the JSON encoding of this rich text. -func (t *RichText) Marshal() string { - var values []string - - node := t.rgaTreeSplit.initialHead.next - for node != nil { - if node.createdAt().Compare(t.createdAt) == 0 { - // last line - } else if node.removedAt == nil { - values = append(values, node.Marshal()) - } - node = node.next - } - - return fmt.Sprintf("[%s]", strings.Join(values, ",")) -} - -// DeepCopy copies itself deeply. -func (t *RichText) DeepCopy() Element { - rgaTreeSplit := NewRGATreeSplit(InitialRichTextNode()) - - current := rgaTreeSplit.InitialHead() - for _, node := range t.Nodes() { - current = rgaTreeSplit.InsertAfter(current, node.DeepCopy()) - insPrevID := node.InsPrevID() - if insPrevID != nil { - insPrevNode := rgaTreeSplit.FindNode(insPrevID) - if insPrevNode == nil { - panic("insPrevNode should be presence") - } - current.SetInsPrev(insPrevNode) - } - } - - return NewRichText(rgaTreeSplit, t.createdAt) -} - -// CreatedAt returns the creation time of this Text. -func (t *RichText) CreatedAt() *time.Ticket { - return t.createdAt -} - -// RemovedAt returns the removal time of this Text. -func (t *RichText) RemovedAt() *time.Ticket { - return t.removedAt -} - -// MovedAt returns the move time of this Text. -func (t *RichText) MovedAt() *time.Ticket { - return t.movedAt -} - -// SetMovedAt sets the move time of this Text. -func (t *RichText) SetMovedAt(movedAt *time.Ticket) { - t.movedAt = movedAt -} - -// SetRemovedAt sets the removal time of this array. -func (t *RichText) SetRemovedAt(removedAt *time.Ticket) { - t.removedAt = removedAt -} - -// Remove removes this Text. -func (t *RichText) Remove(removedAt *time.Ticket) bool { - if (removedAt != nil && removedAt.After(t.createdAt)) && - (t.removedAt == nil || removedAt.After(t.removedAt)) { - t.removedAt = removedAt - return true + RichText: text, + context: ctx, } - return false -} - -// CreateRange returns a pair of RGATreeSplitNodePos of the given integer offsets. -func (t *RichText) CreateRange(from, to int) (*RGATreeSplitNodePos, *RGATreeSplitNodePos) { - return t.rgaTreeSplit.createRange(from, to) } // Edit edits the given range with the given content and attributes. -func (t *RichText) Edit( - from, - to *RGATreeSplitNodePos, - latestCreatedAtMapByActor map[string]*time.Ticket, - content string, - attributes map[string]string, - executedAt *time.Ticket, -) (*RGATreeSplitNodePos, map[string]*time.Ticket) { - val := NewRichTextValue(NewRHT(), content) - for key, value := range attributes { - val.attrs.Set(key, value, executedAt) +func (p *RichText) Edit(from, to int, content string, attributes map[string]string) *RichText { + if from > to { + panic("from should be less than or equal to to") } - - cursorPos, latestCreatedAtMapByActor := t.rgaTreeSplit.edit( - from, - to, - latestCreatedAtMapByActor, - val, - executedAt, + fromPos, toPos := p.RichText.CreateRange(from, to) + + ticket := p.context.IssueTimeTicket() + _, maxCreationMapByActor := p.RichText.Edit( + fromPos, + toPos, + nil, + content, + attributes, + ticket, ) - return cursorPos, latestCreatedAtMapByActor -} - -// SetStyle applies the style of the given range. -func (t *RichText) SetStyle( - from, - to *RGATreeSplitNodePos, - attributes map[string]string, - executedAt *time.Ticket, -) { - // 01. Split nodes with from and to - _, toRight := t.rgaTreeSplit.findNodeWithSplit(to, executedAt) - _, fromRight := t.rgaTreeSplit.findNodeWithSplit(from, executedAt) - - // 02. style nodes between from and to - nodes := t.rgaTreeSplit.findBetween(fromRight, toRight) - for _, node := range nodes { - val := node.value - for key, value := range attributes { - val.attrs.Set(key, value, executedAt) - } + p.context.Push(operations.NewRichEdit( + p.CreatedAt(), + fromPos, + toPos, + maxCreationMapByActor, + content, + attributes, + ticket, + )) + if !fromPos.Equal(toPos) { + p.context.RegisterTextElementWithGarbage(p) } -} -// Select stores that the given range has been selected. -func (t *RichText) Select( - from *RGATreeSplitNodePos, - to *RGATreeSplitNodePos, - executedAt *time.Ticket, -) { - if prev, ok := t.selectionMap[executedAt.ActorIDHex()]; !ok || executedAt.After(prev.updatedAt) { - t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) - } -} - -// Nodes returns the internal nodes of this rich text. -func (t *RichText) Nodes() []*RGATreeSplitNode[*RichTextValue] { - return t.rgaTreeSplit.nodes() + return p } -// AnnotatedString returns a String containing the metadata of the text -// for debugging purpose. -func (t *RichText) AnnotatedString() string { - return t.rgaTreeSplit.AnnotatedString() -} +// SetStyle applies the style of the given range. +func (p *RichText) SetStyle(from, to int, attributes map[string]string) *RichText { + if from > to { + panic("from should be less than or equal to to") + } + fromPos, toPos := p.RichText.CreateRange(from, to) + + ticket := p.context.IssueTimeTicket() + p.RichText.SetStyle( + fromPos, + toPos, + attributes, + ticket, + ) -// removedNodesLen returns length of removed nodes -func (t *RichText) removedNodesLen() int { - return t.rgaTreeSplit.removedNodesLen() -} + p.context.Push(operations.NewStyle( + p.CreatedAt(), + fromPos, + toPos, + attributes, + ticket, + )) -// purgeTextNodesWithGarbage physically purges nodes that have been removed. -func (t *RichText) purgeTextNodesWithGarbage(ticket *time.Ticket) int { - return t.rgaTreeSplit.purgeTextNodesWithGarbage(ticket) + return p } diff --git a/pkg/document/json/text.go b/pkg/document/json/text.go index e35d04648..8dfdf9314 100644 --- a/pkg/document/json/text.go +++ b/pkg/document/json/text.go @@ -17,217 +17,75 @@ package json import ( - "fmt" - "unicode/utf16" - - "github.com/yorkie-team/yorkie/pkg/document/time" + "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/crdt" + "github.com/yorkie-team/yorkie/pkg/document/operations" ) -// TextValue is a value of Text. -type TextValue struct { - value string -} - -// NewTextValue creates a value of Text. -func NewTextValue(value string) *TextValue { - return &TextValue{ - value: value, - } -} - -// Len returns the length of this value. -// It is calculated in UTF-16 code units. -func (t *TextValue) Len() int { - encoded := utf16.Encode([]rune(t.value)) - return len(encoded) -} - -// String returns the string representation of this value. -func (t *TextValue) String() string { - return t.value -} - -// Marshal returns the JSON encoding of this text. -func (t *TextValue) Marshal() string { - return EscapeString(t.value) -} - -// AnnotatedString returns a String containing the metadata of this value -// for debugging purpose. -func (t *TextValue) AnnotatedString() string { - return EscapeString(t.value) -} - -// Split splits this value by the given offset. -func (t *TextValue) Split(offset int) RGATreeSplitValue { - value := t.value - encoded := utf16.Encode([]rune(value)) - t.value = string(utf16.Decode(encoded[0:offset])) - return NewTextValue(string(utf16.Decode(encoded[offset:]))) -} - -// DeepCopy copies itself deeply. -func (t *TextValue) DeepCopy() RGATreeSplitValue { - return &TextValue{ - value: t.value, - } -} - -// InitialTextNode creates an initial node of Text. The text is edited -// as this node is split into multiple nodes. -func InitialTextNode() *RGATreeSplitNode[*TextValue] { - return NewRGATreeSplitNode(initialNodeID, &TextValue{ - value: "", - }) -} - -// Text is an extended data type for the contents of a text editor. +// Text represents a text in the document. As a proxy for the CRDT text, it is +// used when the user manipulates the text from the outside. type Text struct { - rgaTreeSplit *RGATreeSplit[*TextValue] - selectionMap map[string]*Selection - createdAt *time.Ticket - movedAt *time.Ticket - removedAt *time.Ticket + *crdt.Text + context *change.Context } // NewText creates a new instance of Text. -func NewText(elements *RGATreeSplit[*TextValue], createdAt *time.Ticket) *Text { +func NewText(ctx *change.Context, text *crdt.Text) *Text { return &Text{ - rgaTreeSplit: elements, - selectionMap: make(map[string]*Selection), - createdAt: createdAt, - } -} - -// Marshal returns the JSON encoding of this text. -func (t *Text) Marshal() string { - return fmt.Sprintf(`"%s"`, t.rgaTreeSplit.marshal()) -} - -// String returns a string representation of this text. -func (t *Text) String() string { - return t.rgaTreeSplit.string() -} - -// DeepCopy copies itself deeply. -func (t *Text) DeepCopy() Element { - rgaTreeSplit := NewRGATreeSplit(InitialTextNode()) - - current := rgaTreeSplit.InitialHead() - for _, node := range t.Nodes() { - current = rgaTreeSplit.InsertAfter(current, node.DeepCopy()) - insPrevID := node.InsPrevID() - if insPrevID != nil { - insPrevNode := rgaTreeSplit.FindNode(insPrevID) - if insPrevNode == nil { - panic("insPrevNode should be presence") - } - current.SetInsPrev(insPrevNode) - } + Text: text, + context: ctx, } - - return NewText(rgaTreeSplit, t.createdAt) -} - -// CreatedAt returns the creation time of this Text. -func (t *Text) CreatedAt() *time.Ticket { - return t.createdAt -} - -// RemovedAt returns the removal time of this Text. -func (t *Text) RemovedAt() *time.Ticket { - return t.removedAt -} - -// MovedAt returns the move time of this Text. -func (t *Text) MovedAt() *time.Ticket { - return t.movedAt -} - -// SetMovedAt sets the move time of this Text. -func (t *Text) SetMovedAt(movedAt *time.Ticket) { - t.movedAt = movedAt -} - -// SetRemovedAt sets the removal time of this array. -func (t *Text) SetRemovedAt(removedAt *time.Ticket) { - t.removedAt = removedAt -} - -// Remove removes this Text. -func (t *Text) Remove(removedAt *time.Ticket) bool { - if (removedAt != nil && removedAt.After(t.createdAt)) && - (t.removedAt == nil || removedAt.After(t.removedAt)) { - t.removedAt = removedAt - return true - } - return false -} - -// CreateRange returns a pair of RGATreeSplitNodePos of the given integer offsets. -func (t *Text) CreateRange(from, to int) (*RGATreeSplitNodePos, *RGATreeSplitNodePos) { - return t.rgaTreeSplit.createRange(from, to) } // Edit edits the given range with the given content. -func (t *Text) Edit( - from, - to *RGATreeSplitNodePos, - latestCreatedAtMapByActor map[string]*time.Ticket, - content string, - executedAt *time.Ticket, -) (*RGATreeSplitNodePos, map[string]*time.Ticket) { - cursorPos, latestCreatedAtMapByActor := t.rgaTreeSplit.edit( - from, - to, - latestCreatedAtMapByActor, - NewTextValue(content), - executedAt, +func (p *Text) Edit(from, to int, content string) *Text { + if from > to { + panic("from should be less than or equal to to") + } + fromPos, toPos := p.Text.CreateRange(from, to) + + ticket := p.context.IssueTimeTicket() + _, maxCreationMapByActor := p.Text.Edit( + fromPos, + toPos, + nil, + content, + ticket, ) - return cursorPos, latestCreatedAtMapByActor + p.context.Push(operations.NewEdit( + p.CreatedAt(), + fromPos, + toPos, + maxCreationMapByActor, + content, + ticket, + )) + if !fromPos.Equal(toPos) { + p.context.RegisterTextElementWithGarbage(p) + } + return p } // Select stores that the given range has been selected. -func (t *Text) Select( - from *RGATreeSplitNodePos, - to *RGATreeSplitNodePos, - executedAt *time.Ticket, -) { - if _, ok := t.selectionMap[executedAt.ActorIDHex()]; !ok { - t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) - return +func (p *Text) Select(from, to int) *Text { + if from > to { + panic("from should be less than or equal to to") } + fromPos, toPos := p.Text.CreateRange(from, to) - prevSelection := t.selectionMap[executedAt.ActorIDHex()] - if executedAt.After(prevSelection.updatedAt) { - t.selectionMap[executedAt.ActorIDHex()] = newSelection(from, to, executedAt) - } -} - -// Nodes returns the internal nodes of this text. -func (t *Text) Nodes() []*RGATreeSplitNode[*TextValue] { - return t.rgaTreeSplit.nodes() -} - -// AnnotatedString returns a String containing the metadata of the text -// for debugging purpose. -func (t *Text) AnnotatedString() string { - return t.rgaTreeSplit.AnnotatedString() -} - -// CheckWeight returns false when there is an incorrect weight node. -// for debugging purpose. -func (t *Text) CheckWeight() bool { - return t.rgaTreeSplit.CheckWeight() -} - -// removedNodesLen returns length of removed nodes -func (t *Text) removedNodesLen() int { - return t.rgaTreeSplit.removedNodesLen() -} + ticket := p.context.IssueTimeTicket() + p.Text.Select( + fromPos, + toPos, + ticket, + ) -// purgeTextNodesWithGarbage physically purges nodes that have been removed. -func (t *Text) purgeTextNodesWithGarbage(ticket *time.Ticket) int { - return t.rgaTreeSplit.purgeTextNodesWithGarbage(ticket) + p.context.Push(operations.NewSelect( + p.CreatedAt(), + fromPos, + toPos, + ticket, + )) + return p } diff --git a/pkg/document/operations/add.go b/pkg/document/operations/add.go index 1ed542ca8..eb4be7aa9 100644 --- a/pkg/document/operations/add.go +++ b/pkg/document/operations/add.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -30,7 +30,7 @@ type Add struct { prevCreatedAt *time.Ticket // value is an element added by the insert operations. - value json.Element + value crdt.Element // executedAt is the time the operation was executed. executedAt *time.Ticket @@ -40,7 +40,7 @@ type Add struct { func NewAdd( parentCreatedAt *time.Ticket, prevCreatedAt *time.Ticket, - value json.Element, + value crdt.Element, executedAt *time.Ticket, ) *Add { return &Add{ @@ -52,10 +52,10 @@ func NewAdd( } // Execute executes this operation on the given document(`root`). -func (o *Add) Execute(root *json.Root) error { +func (o *Add) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(o.parentCreatedAt) - obj, ok := parent.(*json.Array) + obj, ok := parent.(*crdt.Array) if !ok { return ErrNotApplicableDataType } @@ -68,7 +68,7 @@ func (o *Add) Execute(root *json.Root) error { } // Value returns the value of this operation. -func (o *Add) Value() json.Element { +func (o *Add) Value() crdt.Element { return o.value } diff --git a/pkg/document/operations/edit.go b/pkg/document/operations/edit.go index a39225e05..fbc2ab6df 100644 --- a/pkg/document/operations/edit.go +++ b/pkg/document/operations/edit.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -27,10 +27,10 @@ type Edit struct { parentCreatedAt *time.Ticket // from represents the start point of the editing range. - from *json.RGATreeSplitNodePos + from *crdt.RGATreeSplitNodePos // to represents the end point of the editing range. - to *json.RGATreeSplitNodePos + to *crdt.RGATreeSplitNodePos // latestCreatedAtMapByActor is a map that stores the latest creation time // by actor for the nodes included in the editing range. @@ -46,8 +46,8 @@ type Edit struct { // NewEdit creates a new instance of Edit. func NewEdit( parentCreatedAt *time.Ticket, - from *json.RGATreeSplitNodePos, - to *json.RGATreeSplitNodePos, + from *crdt.RGATreeSplitNodePos, + to *crdt.RGATreeSplitNodePos, latestCreatedAtMapByActor map[string]*time.Ticket, content string, executedAt *time.Ticket, @@ -63,11 +63,11 @@ func NewEdit( } // Execute executes this operation on the given document(`root`). -func (e *Edit) Execute(root *json.Root) error { +func (e *Edit) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(e.parentCreatedAt) switch obj := parent.(type) { - case *json.Text: + case *crdt.Text: obj.Edit(e.from, e.to, e.latestCreatedAtMapByActor, e.content, e.executedAt) if !e.from.Equal(e.to) { root.RegisterTextElementWithGarbage(obj) @@ -80,12 +80,12 @@ func (e *Edit) Execute(root *json.Root) error { } // From returns the start point of the editing range. -func (e *Edit) From() *json.RGATreeSplitNodePos { +func (e *Edit) From() *crdt.RGATreeSplitNodePos { return e.from } // To returns the end point of the editing range. -func (e *Edit) To() *json.RGATreeSplitNodePos { +func (e *Edit) To() *crdt.RGATreeSplitNodePos { return e.to } diff --git a/pkg/document/operations/increase.go b/pkg/document/operations/increase.go index a8ae5e356..cff99aeea 100644 --- a/pkg/document/operations/increase.go +++ b/pkg/document/operations/increase.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -25,14 +25,14 @@ import ( // Among Primitives, numeric types Integer, Long, and Double are used as values. type Increase struct { parentCreatedAt *time.Ticket - value json.Element + value crdt.Element executedAt *time.Ticket } // NewIncrease creates the increase instance. func NewIncrease( parentCreatedAt *time.Ticket, - value json.Element, + value crdt.Element, executedAt *time.Ticket, ) *Increase { return &Increase{ @@ -43,21 +43,21 @@ func NewIncrease( } // Execute executes this operation on the given document(`root`). -func (o *Increase) Execute(root *json.Root) error { +func (o *Increase) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(o.parentCreatedAt) - cnt, ok := parent.(*json.Counter) + cnt, ok := parent.(*crdt.Counter) if !ok { return ErrNotApplicableDataType } - value := o.value.(*json.Primitive) + value := o.value.(*crdt.Primitive) cnt.Increase(value) return nil } // Value return the value of this operation. -func (o *Increase) Value() json.Element { +func (o *Increase) Value() crdt.Element { return o.value } diff --git a/pkg/document/operations/move.go b/pkg/document/operations/move.go index 36aac058e..265006292 100644 --- a/pkg/document/operations/move.go +++ b/pkg/document/operations/move.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -52,10 +52,10 @@ func NewMove( } // Execute executes this operation on the given document(`root`). -func (o *Move) Execute(root *json.Root) error { +func (o *Move) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(o.parentCreatedAt) - obj, ok := parent.(*json.Array) + obj, ok := parent.(*crdt.Array) if !ok { return ErrNotApplicableDataType } diff --git a/pkg/document/operations/operation.go b/pkg/document/operations/operation.go index 335137b5a..5fefc67b8 100644 --- a/pkg/document/operations/operation.go +++ b/pkg/document/operations/operation.go @@ -19,7 +19,7 @@ package operations import ( "errors" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -32,7 +32,7 @@ var ( // Operation represents an operation to be executed on a document. type Operation interface { // Execute executes this operation on the given document(`root`). - Execute(root *json.Root) error + Execute(root *crdt.Root) error // ExecutedAt returns execution time of this operation. ExecutedAt() *time.Ticket diff --git a/pkg/document/operations/remove.go b/pkg/document/operations/remove.go index cb77035ed..691628218 100644 --- a/pkg/document/operations/remove.go +++ b/pkg/document/operations/remove.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -48,11 +48,11 @@ func NewRemove( } // Execute executes this operation on the given document(`root`). -func (o *Remove) Execute(root *json.Root) error { +func (o *Remove) Execute(root *crdt.Root) error { parentElem := root.FindByCreatedAt(o.parentCreatedAt) switch parent := parentElem.(type) { - case json.Container: + case crdt.Container: elem := parent.DeleteByCreatedAt(o.createdAt, o.executedAt) if elem != nil { root.RegisterRemovedElementPair(parent, elem) diff --git a/pkg/document/operations/rich_edit.go b/pkg/document/operations/rich_edit.go index d1e5deebe..50fb49477 100644 --- a/pkg/document/operations/rich_edit.go +++ b/pkg/document/operations/rich_edit.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -29,10 +29,10 @@ type RichEdit struct { parentCreatedAt *time.Ticket // from represents the start point of the editing range. - from *json.RGATreeSplitNodePos + from *crdt.RGATreeSplitNodePos // to represents the end point of the editing range. - to *json.RGATreeSplitNodePos + to *crdt.RGATreeSplitNodePos // latestCreatedAtMapByActor is a map that stores the latest creation time // by actor for the nodes included in the editing range. @@ -51,8 +51,8 @@ type RichEdit struct { // NewRichEdit creates a new instance of RichEdit. func NewRichEdit( parentCreatedAt *time.Ticket, - from *json.RGATreeSplitNodePos, - to *json.RGATreeSplitNodePos, + from *crdt.RGATreeSplitNodePos, + to *crdt.RGATreeSplitNodePos, latestCreatedAtMapByActor map[string]*time.Ticket, content string, attributes map[string]string, @@ -70,11 +70,11 @@ func NewRichEdit( } // Execute executes this operation on the given document(`root`). -func (e *RichEdit) Execute(root *json.Root) error { +func (e *RichEdit) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(e.parentCreatedAt) switch obj := parent.(type) { - case *json.RichText: + case *crdt.RichText: obj.Edit(e.from, e.to, e.latestCreatedAtMapByActor, e.content, e.attributes, e.executedAt) if !e.from.Equal(e.to) { root.RegisterTextElementWithGarbage(obj) @@ -87,12 +87,12 @@ func (e *RichEdit) Execute(root *json.Root) error { } // From returns the start point of the editing range. -func (e *RichEdit) From() *json.RGATreeSplitNodePos { +func (e *RichEdit) From() *crdt.RGATreeSplitNodePos { return e.from } // To returns the end point of the editing range. -func (e *RichEdit) To() *json.RGATreeSplitNodePos { +func (e *RichEdit) To() *crdt.RGATreeSplitNodePos { return e.to } diff --git a/pkg/document/operations/select.go b/pkg/document/operations/select.go index 515e6cd2d..4bc38e540 100644 --- a/pkg/document/operations/select.go +++ b/pkg/document/operations/select.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -27,10 +27,10 @@ type Select struct { parentCreatedAt *time.Ticket // from represents the start point of the selection. - from *json.RGATreeSplitNodePos + from *crdt.RGATreeSplitNodePos // to represents the end point of the selection. - to *json.RGATreeSplitNodePos + to *crdt.RGATreeSplitNodePos // executedAt is the time the operation was executed. executedAt *time.Ticket @@ -39,8 +39,8 @@ type Select struct { // NewSelect creates a new instance of Select. func NewSelect( parentCreatedAt *time.Ticket, - from *json.RGATreeSplitNodePos, - to *json.RGATreeSplitNodePos, + from *crdt.RGATreeSplitNodePos, + to *crdt.RGATreeSplitNodePos, executedAt *time.Ticket, ) *Select { return &Select{ @@ -52,13 +52,13 @@ func NewSelect( } // Execute executes this operation on the given document(`root`). -func (s *Select) Execute(root *json.Root) error { +func (s *Select) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(s.parentCreatedAt) switch obj := parent.(type) { - case *json.Text: + case *crdt.Text: obj.Select(s.from, s.to, s.executedAt) - case *json.RichText: + case *crdt.RichText: obj.Select(s.from, s.to, s.executedAt) default: return ErrNotApplicableDataType @@ -68,12 +68,12 @@ func (s *Select) Execute(root *json.Root) error { } // From returns the start point of the selection. -func (s *Select) From() *json.RGATreeSplitNodePos { +func (s *Select) From() *crdt.RGATreeSplitNodePos { return s.from } // To returns the end point of the selection. -func (s *Select) To() *json.RGATreeSplitNodePos { +func (s *Select) To() *crdt.RGATreeSplitNodePos { return s.to } diff --git a/pkg/document/operations/set.go b/pkg/document/operations/set.go index 39db74d98..398bdd16e 100644 --- a/pkg/document/operations/set.go +++ b/pkg/document/operations/set.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -31,7 +31,7 @@ type Set struct { key string // value is the value of this operation. - value json.Element + value crdt.Element // executedAt is the time the operation was executed. executedAt *time.Ticket @@ -41,7 +41,7 @@ type Set struct { func NewSet( parentCreatedAt *time.Ticket, key string, - value json.Element, + value crdt.Element, executedAt *time.Ticket, ) *Set { return &Set{ @@ -53,10 +53,10 @@ func NewSet( } // Execute executes this operation on the given document(`root`). -func (o *Set) Execute(root *json.Root) error { +func (o *Set) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(o.parentCreatedAt) - obj, ok := parent.(*json.Object) + obj, ok := parent.(*crdt.Object) if !ok { return ErrNotApplicableDataType } @@ -91,6 +91,6 @@ func (o *Set) Key() string { } // Value returns the value of this operation. -func (o *Set) Value() json.Element { +func (o *Set) Value() crdt.Element { return o.value } diff --git a/pkg/document/operations/style.go b/pkg/document/operations/style.go index 8b5af195b..3d41a9d0c 100644 --- a/pkg/document/operations/style.go +++ b/pkg/document/operations/style.go @@ -17,7 +17,7 @@ package operations import ( - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -27,10 +27,10 @@ type Style struct { parentCreatedAt *time.Ticket // from is the starting point of the range to apply the style to. - from *json.RGATreeSplitNodePos + from *crdt.RGATreeSplitNodePos // to is the end point of the range to apply the style to. - to *json.RGATreeSplitNodePos + to *crdt.RGATreeSplitNodePos // attributes represents the text style. attributes map[string]string @@ -42,8 +42,8 @@ type Style struct { // NewStyle creates a new instance of Style. func NewStyle( parentCreatedAt *time.Ticket, - from *json.RGATreeSplitNodePos, - to *json.RGATreeSplitNodePos, + from *crdt.RGATreeSplitNodePos, + to *crdt.RGATreeSplitNodePos, attributes map[string]string, executedAt *time.Ticket, ) *Style { @@ -57,9 +57,9 @@ func NewStyle( } // Execute executes this operation on the given document(`root`). -func (e *Style) Execute(root *json.Root) error { +func (e *Style) Execute(root *crdt.Root) error { parent := root.FindByCreatedAt(e.parentCreatedAt) - obj, ok := parent.(*json.RichText) + obj, ok := parent.(*crdt.RichText) if !ok { return ErrNotApplicableDataType } @@ -69,12 +69,12 @@ func (e *Style) Execute(root *json.Root) error { } // From returns the start point of the editing range. -func (e *Style) From() *json.RGATreeSplitNodePos { +func (e *Style) From() *crdt.RGATreeSplitNodePos { return e.from } // To returns the end point of the editing range. -func (e *Style) To() *json.RGATreeSplitNodePos { +func (e *Style) To() *crdt.RGATreeSplitNodePos { return e.to } diff --git a/pkg/document/proxy/array_proxy.go b/pkg/document/proxy/array_proxy.go deleted file mode 100644 index 46293f63e..000000000 --- a/pkg/document/proxy/array_proxy.go +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2020 The Yorkie Authors. All rights reserved. - * - * 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 proxy - -import ( - gotime "time" - - "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" - "github.com/yorkie-team/yorkie/pkg/document/operations" - "github.com/yorkie-team/yorkie/pkg/document/time" -) - -// ArrayProxy is a proxy representing Array. -type ArrayProxy struct { - *json.Array - context *change.Context -} - -// NewArrayProxy creates a new instance of ArrayProxy. -func NewArrayProxy(ctx *change.Context, array *json.Array) *ArrayProxy { - return &ArrayProxy{ - Array: array, - context: ctx, - } -} - -// AddNull adds the null at the last. -func (p *ArrayProxy) AddNull() *ArrayProxy { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(nil, ticket) - }) - - return p -} - -// AddBool adds the given boolean at the last. -func (p *ArrayProxy) AddBool(values ...bool) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddInteger adds the given integer at the last. -func (p *ArrayProxy) AddInteger(values ...int) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddLong adds the given long at the last. -func (p *ArrayProxy) AddLong(values ...int64) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddDouble adds the given double at the last. -func (p *ArrayProxy) AddDouble(values ...float64) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddString adds the given string at the last. -func (p *ArrayProxy) AddString(values ...string) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddBytes adds the given bytes at the last. -func (p *ArrayProxy) AddBytes(values ...[]byte) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddDate adds the given date at the last. -func (p *ArrayProxy) AddDate(values ...gotime.Time) *ArrayProxy { - for _, value := range values { - p.addInternal(func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(value, ticket) - }) - } - - return p -} - -// AddNewArray adds a new array at the last. -func (p *ArrayProxy) AddNewArray() *ArrayProxy { - v := p.addInternal(func(ticket *time.Ticket) json.Element { - return NewArrayProxy(p.context, json.NewArray(json.NewRGATreeList(), ticket)) - }) - - return v.(*ArrayProxy) -} - -// MoveBefore moves the given element to its new position before the given next element. -func (p *ArrayProxy) MoveBefore(nextCreatedAt, createdAt *time.Ticket) { - p.moveBeforeInternal(nextCreatedAt, createdAt) -} - -// InsertIntegerAfter inserts the given integer after the given previous -// element. -func (p *ArrayProxy) InsertIntegerAfter(index int, v int) *ArrayProxy { - p.insertAfterInternal(p.Get(index).CreatedAt(), func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// Delete deletes the element of the given index. -func (p *ArrayProxy) Delete(idx int) json.Element { - if p.Len() <= idx { - return nil - } - - ticket := p.context.IssueTimeTicket() - deleted := p.Array.Delete(idx, ticket) - p.context.Push(operations.NewRemove( - p.CreatedAt(), - deleted.CreatedAt(), - ticket, - )) - p.context.RegisterRemovedElementPair(p, deleted) - return deleted -} - -// Len returns length of this Array. -func (p *ArrayProxy) Len() int { - return p.Array.Len() -} - -func (p *ArrayProxy) addInternal( - creator func(ticket *time.Ticket) json.Element, -) json.Element { - return p.insertAfterInternal(p.Array.LastCreatedAt(), creator) -} - -func (p *ArrayProxy) insertAfterInternal( - prevCreatedAt *time.Ticket, - creator func(ticket *time.Ticket) json.Element, -) json.Element { - ticket := p.context.IssueTimeTicket() - proxy := creator(ticket) - value := toOriginal(proxy) - - p.context.Push(operations.NewAdd( - p.Array.CreatedAt(), - prevCreatedAt, - value.DeepCopy(), - ticket, - )) - - p.InsertAfter(prevCreatedAt, value) - p.context.RegisterElement(value) - - return proxy -} - -func (p *ArrayProxy) moveBeforeInternal(nextCreatedAt, createdAt *time.Ticket) { - ticket := p.context.IssueTimeTicket() - - prevCreatedAt := p.FindPrevCreatedAt(nextCreatedAt) - - p.context.Push(operations.NewMove( - p.Array.CreatedAt(), - prevCreatedAt, - createdAt, - ticket, - )) - - p.MoveAfter(prevCreatedAt, createdAt, ticket) -} diff --git a/pkg/document/proxy/counter_proxy.go b/pkg/document/proxy/counter_proxy.go deleted file mode 100644 index 57ef3c17a..000000000 --- a/pkg/document/proxy/counter_proxy.go +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2020 The Yorkie Authors. All rights reserved. - * - * 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 proxy - -import ( - "reflect" - - "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" - "github.com/yorkie-team/yorkie/pkg/document/operations" -) - -// CounterProxy is a proxy representing counter. -type CounterProxy struct { - *json.Counter - context *change.Context -} - -// NewCounterProxy create CounterProxy instance. -func NewCounterProxy(ctx *change.Context, counter *json.Counter) *CounterProxy { - if !counter.IsNumericType() { - panic("unsupported type") - } - return &CounterProxy{ - Counter: counter, - context: ctx, - } -} - -// Increase adds an increase operations. -// Only numeric types are allowed as operand values, excluding -// uint64 and uintptr. -func (p *CounterProxy) Increase(v interface{}) *CounterProxy { - if !isAllowedOperand(v) { - panic("unsupported type") - } - var primitive *json.Primitive - ticket := p.context.IssueTimeTicket() - - value, kind := convertAssertableOperand(v) - isInt := kind == reflect.Int - switch p.ValueType() { - case json.LongCnt: - if isInt { - primitive = json.NewPrimitive(int64(value.(int)), ticket) - } else { - primitive = json.NewPrimitive(int64(value.(float64)), ticket) - } - case json.IntegerCnt: - if isInt { - primitive = json.NewPrimitive(value, ticket) - } else { - primitive = json.NewPrimitive(int(value.(float64)), ticket) - } - case json.DoubleCnt: - if isInt { - primitive = json.NewPrimitive(float64(value.(int)), ticket) - } else { - primitive = json.NewPrimitive(value, ticket) - } - default: - panic("unsupported type") - } - - p.context.Push(operations.NewIncrease( - p.CreatedAt(), - primitive, - ticket, - )) - - return p -} - -// isAllowedOperand indicates whether -// the operand of increase is an allowable type. -func isAllowedOperand(v interface{}) bool { - vt := reflect.ValueOf(v).Kind() - if vt >= reflect.Int && vt <= reflect.Float64 && vt != reflect.Uint64 && vt != reflect.Uintptr { - return true - } - - return false -} - -// convertAssertableOperand converts the operand -// to be used in the increase function to assertable type. -func convertAssertableOperand(v interface{}) (interface{}, reflect.Kind) { - vt := reflect.ValueOf(v).Kind() - switch vt { - case reflect.Int: - return v, reflect.Int - case reflect.Int8: - return int(v.(int8)), reflect.Int - case reflect.Int16: - return int(v.(int16)), reflect.Int - case reflect.Int32: - return int(v.(int32)), reflect.Int - case reflect.Int64: - return int(v.(int64)), reflect.Int - case reflect.Uint: - return int(v.(uint)), reflect.Int - case reflect.Uint8: - return int(v.(uint8)), reflect.Int - case reflect.Uint16: - return int(v.(uint16)), reflect.Int - case reflect.Uint32: - return int(v.(uint32)), reflect.Int - case reflect.Float32: - return float64(v.(float32)), reflect.Float64 - default: - return v, reflect.Float64 - } -} diff --git a/pkg/document/proxy/object_proxy.go b/pkg/document/proxy/object_proxy.go deleted file mode 100644 index d90ae9c39..000000000 --- a/pkg/document/proxy/object_proxy.go +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2020 The Yorkie Authors. All rights reserved. - * - * 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 proxy - -import ( - gotime "time" - - "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" - "github.com/yorkie-team/yorkie/pkg/document/operations" - "github.com/yorkie-team/yorkie/pkg/document/time" -) - -// ObjectProxy is a proxy representing Object. -type ObjectProxy struct { - *json.Object - context *change.Context -} - -// NewObjectProxy creates a new instance of ObjectProxy. -func NewObjectProxy(ctx *change.Context, root *json.Object) *ObjectProxy { - return &ObjectProxy{ - Object: root, - context: ctx, - } -} - -// SetNewObject sets a new Object for the given key. -func (p *ObjectProxy) SetNewObject(k string) *ObjectProxy { - v := p.setInternal(k, func(ticket *time.Ticket) json.Element { - return NewObjectProxy(p.context, json.NewObject(json.NewRHTPriorityQueueMap(), ticket)) - }) - - return v.(*ObjectProxy) -} - -// SetNewArray sets a new Array for the given key. -func (p *ObjectProxy) SetNewArray(k string) *ArrayProxy { - v := p.setInternal(k, func(ticket *time.Ticket) json.Element { - return NewArrayProxy(p.context, json.NewArray(json.NewRGATreeList(), ticket)) - }) - - return v.(*ArrayProxy) -} - -// SetNewText sets a new Text for the given key. -func (p *ObjectProxy) SetNewText(k string) *TextProxy { - v := p.setInternal(k, func(ticket *time.Ticket) json.Element { - return NewTextProxy( - p.context, - json.NewText(json.NewRGATreeSplit(json.InitialTextNode()), ticket), - ) - }) - - return v.(*TextProxy) -} - -// SetNewRichText sets a new RichText for the given key. -func (p *ObjectProxy) SetNewRichText(k string) *RichTextProxy { - v := p.setInternal(k, func(ticket *time.Ticket) json.Element { - return NewRichTextProxy( - p.context, - json.NewInitialRichText(json.NewRGATreeSplit(json.InitialRichTextNode()), ticket), - ) - }) - - return v.(*RichTextProxy) -} - -// SetNewCounter sets a new NewCounter for the given key. -func (p *ObjectProxy) SetNewCounter(k string, n interface{}) *CounterProxy { - v := p.setInternal(k, func(ticket *time.Ticket) json.Element { - return NewCounterProxy( - p.context, - json.NewCounter(n, ticket), - ) - }) - - return v.(*CounterProxy) -} - -// SetNull sets the null for the given key. -func (p *ObjectProxy) SetNull(k string) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(nil, ticket) - }) - - return p -} - -// SetBool sets the given boolean for the given key. -func (p *ObjectProxy) SetBool(k string, v bool) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetInteger sets the given integer for the given key. -func (p *ObjectProxy) SetInteger(k string, v int) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetLong sets the given long for the given key. -func (p *ObjectProxy) SetLong(k string, v int64) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetDouble sets the given double for the given key. -func (p *ObjectProxy) SetDouble(k string, v float64) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetString sets the given string for the given key. -func (p *ObjectProxy) SetString(k, v string) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetBytes sets the given bytes for the given key. -func (p *ObjectProxy) SetBytes(k string, v []byte) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// SetDate sets the given date for the given key. -func (p *ObjectProxy) SetDate(k string, v gotime.Time) *ObjectProxy { - p.setInternal(k, func(ticket *time.Ticket) json.Element { - return json.NewPrimitive(v, ticket) - }) - - return p -} - -// Delete deletes the value of the given key. -func (p *ObjectProxy) Delete(k string) json.Element { - if !p.Object.Has(k) { - return nil - } - - ticket := p.context.IssueTimeTicket() - deleted := p.Object.Delete(k, ticket) - p.context.Push(operations.NewRemove( - p.CreatedAt(), - deleted.CreatedAt(), - ticket, - )) - p.context.RegisterRemovedElementPair(p, deleted) - return deleted -} - -// GetObject returns Object of the given key. -func (p *ObjectProxy) GetObject(k string) *ObjectProxy { - elem := p.Object.Get(k) - if elem == nil { - return nil - } - - switch elem := p.Object.Get(k).(type) { - case *json.Object: - return NewObjectProxy(p.context, elem) - case *ObjectProxy: - return elem - default: - panic("unsupported type") - } -} - -// GetArray returns Array of the given key. -func (p *ObjectProxy) GetArray(k string) *ArrayProxy { - elem := p.Object.Get(k) - if elem == nil { - return nil - } - - switch elem := p.Object.Get(k).(type) { - case *json.Array: - return NewArrayProxy(p.context, elem) - case *ArrayProxy: - return elem - default: - panic("unsupported type") - } -} - -// GetText returns Text of the given key. -func (p *ObjectProxy) GetText(k string) *TextProxy { - elem := p.Object.Get(k) - if elem == nil { - return nil - } - - switch elem := p.Object.Get(k).(type) { - case *json.Text: - return NewTextProxy(p.context, elem) - case *TextProxy: - return elem - default: - panic("unsupported type") - } -} - -// GetRichText returns RichText of the given key. -func (p *ObjectProxy) GetRichText(k string) *RichTextProxy { - elem := p.Object.Get(k) - if elem == nil { - return nil - } - - switch elem := p.Object.Get(k).(type) { - case *json.RichText: - return NewRichTextProxy(p.context, elem) - case *RichTextProxy: - return elem - default: - panic("unsupported type") - } -} - -// GetCounter returns CounterProxy of the given key. -func (p *ObjectProxy) GetCounter(k string) *CounterProxy { - elem := p.Object.Get(k) - if elem == nil { - return nil - } - - switch elem := p.Object.Get(k).(type) { - case *json.Counter: - return NewCounterProxy(p.context, elem) - case *CounterProxy: - return elem - default: - panic("unsupported type") - } -} - -func (p *ObjectProxy) setInternal( - k string, - creator func(ticket *time.Ticket) json.Element, -) json.Element { - ticket := p.context.IssueTimeTicket() - proxy := creator(ticket) - value := toOriginal(proxy) - - p.context.Push(operations.NewSet( - p.CreatedAt(), - k, - value.DeepCopy(), - ticket, - )) - - removed := p.Set(k, value) - p.context.RegisterElement(value) - if removed != nil { - p.context.RegisterRemovedElementPair(p, removed) - } - - return proxy -} diff --git a/pkg/document/proxy/rich_text_proxy.go b/pkg/document/proxy/rich_text_proxy.go deleted file mode 100644 index ee0b02518..000000000 --- a/pkg/document/proxy/rich_text_proxy.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020 The Yorkie Authors. All rights reserved. - * - * 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 proxy - -import ( - "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" - "github.com/yorkie-team/yorkie/pkg/document/operations" -) - -// RichTextProxy is a proxy representing RichText. -type RichTextProxy struct { - *json.RichText - context *change.Context -} - -// NewRichTextProxy creates a new instance of RichTextProxy. -func NewRichTextProxy(ctx *change.Context, text *json.RichText) *RichTextProxy { - return &RichTextProxy{ - RichText: text, - context: ctx, - } -} - -// Edit edits the given range with the given content and attributes. -func (p *RichTextProxy) Edit(from, to int, content string, attributes map[string]string) *RichTextProxy { - if from > to { - panic("from should be less than or equal to to") - } - fromPos, toPos := p.RichText.CreateRange(from, to) - - ticket := p.context.IssueTimeTicket() - _, maxCreationMapByActor := p.RichText.Edit( - fromPos, - toPos, - nil, - content, - attributes, - ticket, - ) - - p.context.Push(operations.NewRichEdit( - p.CreatedAt(), - fromPos, - toPos, - maxCreationMapByActor, - content, - attributes, - ticket, - )) - if !fromPos.Equal(toPos) { - p.context.RegisterTextElementWithGarbage(p) - } - - return p -} - -// SetStyle applies the style of the given range. -func (p *RichTextProxy) SetStyle(from, to int, attributes map[string]string) *RichTextProxy { - if from > to { - panic("from should be less than or equal to to") - } - fromPos, toPos := p.RichText.CreateRange(from, to) - - ticket := p.context.IssueTimeTicket() - p.RichText.SetStyle( - fromPos, - toPos, - attributes, - ticket, - ) - - p.context.Push(operations.NewStyle( - p.CreatedAt(), - fromPos, - toPos, - attributes, - ticket, - )) - - return p -} diff --git a/pkg/document/proxy/text_proxy.go b/pkg/document/proxy/text_proxy.go deleted file mode 100644 index 17cbaf3d8..000000000 --- a/pkg/document/proxy/text_proxy.go +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2020 The Yorkie Authors. All rights reserved. - * - * 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 proxy - -import ( - "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" - "github.com/yorkie-team/yorkie/pkg/document/operations" -) - -// TextProxy is a proxy representing Text. -type TextProxy struct { - *json.Text - context *change.Context -} - -// NewTextProxy creates a new instance of TextProxy. -func NewTextProxy(ctx *change.Context, text *json.Text) *TextProxy { - return &TextProxy{ - Text: text, - context: ctx, - } -} - -// Edit edits the given range with the given content. -func (p *TextProxy) Edit(from, to int, content string) *TextProxy { - if from > to { - panic("from should be less than or equal to to") - } - fromPos, toPos := p.Text.CreateRange(from, to) - - ticket := p.context.IssueTimeTicket() - _, maxCreationMapByActor := p.Text.Edit( - fromPos, - toPos, - nil, - content, - ticket, - ) - - p.context.Push(operations.NewEdit( - p.CreatedAt(), - fromPos, - toPos, - maxCreationMapByActor, - content, - ticket, - )) - if !fromPos.Equal(toPos) { - p.context.RegisterTextElementWithGarbage(p) - } - return p -} - -// Select stores that the given range has been selected. -func (p *TextProxy) Select(from, to int) *TextProxy { - if from > to { - panic("from should be less than or equal to to") - } - fromPos, toPos := p.Text.CreateRange(from, to) - - ticket := p.context.IssueTimeTicket() - p.Text.Select( - fromPos, - toPos, - ticket, - ) - - p.context.Push(operations.NewSelect( - p.CreatedAt(), - fromPos, - toPos, - ticket, - )) - return p -} diff --git a/server/backend/database/memory/database_test.go b/server/backend/database/memory/database_test.go index bcc810937..b9114d3fe 100644 --- a/server/backend/database/memory/database_test.go +++ b/server/backend/database/memory/database_test.go @@ -26,8 +26,8 @@ import ( "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/pkg/document" "github.com/yorkie-team/yorkie/pkg/document/change" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/pkg/document/time" "github.com/yorkie-team/yorkie/server/backend/database" "github.com/yorkie-team/yorkie/server/backend/database/memory" @@ -180,12 +180,12 @@ func TestDB(t *testing.T) { actorID, _ := time.ActorIDFromBytes(bytesID) doc := document.New(key.Key(t.Name())) doc.SetActor(actorID) - assert.NoError(t, doc.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, doc.Update(func(root *json.Object) error { root.SetNewArray("array") return nil })) for idx := 0; idx < 10; idx++ { - assert.NoError(t, doc.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, doc.Update(func(root *json.Object) error { root.GetArray("array").AddInteger(idx) return nil })) @@ -222,7 +222,7 @@ func TestDB(t *testing.T) { doc := document.New(key.Key(t.Name())) doc.SetActor(actorID) - assert.NoError(t, doc.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, doc.Update(func(root *json.Object) error { root.SetNewArray("array") return nil })) diff --git a/test/bench/document_bench_test.go b/test/bench/document_bench_test.go index c1ce0f5e2..0570b2092 100644 --- a/test/bench/document_bench_test.go +++ b/test/bench/document_bench_test.go @@ -27,7 +27,7 @@ import ( "github.com/yorkie-team/yorkie/pkg/document" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/proxy" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/time" ) @@ -56,7 +56,7 @@ func BenchmarkDocument(b *testing.B) { doc2 := document.New("d2") doc3 := document.New("d3") - err := doc1.Update(func(root *proxy.ObjectProxy) error { + err := doc1.Update(func(root *json.Object) error { root.SetString("k1", "v1") return nil }, "updates k1") @@ -75,7 +75,7 @@ func BenchmarkDocument(b *testing.B) { assert.Equal(b, "{}", doc.Marshal()) assert.False(b, doc.HasLocalChanges()) - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") root.SetNewObject("k2").SetString("k4", "v4") root.SetNewArray("k3").AddString("v5", "v6") @@ -96,7 +96,7 @@ func BenchmarkDocument(b *testing.B) { assert.False(b, doc.HasLocalChanges()) expected := `{"k1":"v1","k2":{"k4":"v4"},"k3":["v5","v6"]}` - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") root.SetNewObject("k2").SetString("k4", "v4") root.SetNewArray("k3").AddString("v5", "v6") @@ -107,7 +107,7 @@ func BenchmarkDocument(b *testing.B) { assert.Equal(b, expected, doc.Marshal()) expected = `{"k1":"v1","k3":["v5","v6"]}` - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.Delete("k2") assert.Equal(b, expected, root.Marshal()) return nil @@ -120,7 +120,7 @@ func BenchmarkDocument(b *testing.B) { b.Run("object test", func(b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") assert.Equal(b, `{"k1":"v1"}`, root.Marshal()) root.SetString("k1", "v2") @@ -136,7 +136,7 @@ func BenchmarkDocument(b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewArray("k1").AddInteger(1).AddInteger(2).AddInteger(3) assert.Equal(b, 3, root.GetArray("k1").Len()) assert.Equal(b, `{"k1":[1,2,3]}`, root.Marshal()) @@ -179,7 +179,7 @@ func BenchmarkDocument(b *testing.B) { // ---------- ins links -------- // | | | // [init] - [A] - [12] - [BC deleted] - [D] - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewText("k1"). Edit(0, 0, "ABCD"). Edit(1, 3, "12") @@ -189,7 +189,7 @@ func BenchmarkDocument(b *testing.B) { assert.NoError(b, err) assert.Equal(b, `{"k1":"A12D"}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("k1") assert.Equal(b, "[0:0:00:0 ][1:2:00:0 A][1:3:00:0 12]{1:2:00:1 BC}[1:2:00:3 D]", @@ -220,7 +220,7 @@ func BenchmarkDocument(b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewText("k1"). Edit(0, 0, "ㅎ"). Edit(0, 1, "하"). @@ -240,7 +240,7 @@ func BenchmarkDocument(b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewRichText("k1") text.Edit(0, 0, "Hello world", nil) assert.Equal( @@ -253,7 +253,7 @@ func BenchmarkDocument(b *testing.B) { assert.NoError(b, err) assert.Equal(b, `{"k1":[{"attrs":{},"val":"Hello world"}]}`, doc.Marshal()) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 5, map[string]string{"b": "1"}) assert.Equal(b, @@ -269,7 +269,7 @@ func BenchmarkDocument(b *testing.B) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 5, map[string]string{"b": "1"}) assert.Equal( @@ -293,7 +293,7 @@ func BenchmarkDocument(b *testing.B) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.Edit(5, 11, " Yorkie", nil) assert.Equal( @@ -311,7 +311,7 @@ func BenchmarkDocument(b *testing.B) { doc.Marshal(), ) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.Edit(5, 5, "\n", map[string]string{"list": "true"}) assert.Equal( @@ -340,7 +340,7 @@ func BenchmarkDocument(b *testing.B) { var double = 5.66 // integer type test - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewCounter("age", 5) age := root.GetCounter("age") @@ -356,7 +356,7 @@ func BenchmarkDocument(b *testing.B) { assert.Equal(b, `{"age":128}`, doc.Marshal()) // long type test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewCounter("price", 9000000000000000000) price := root.GetCounter("price") price.Increase(long) @@ -371,7 +371,7 @@ func BenchmarkDocument(b *testing.B) { assert.Equal(b, `{"age":128,"price":9000000000000000123}`, doc.Marshal()) // double type test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewCounter("width", 10.5) width := root.GetCounter("width") width.Increase(long) @@ -386,7 +386,7 @@ func BenchmarkDocument(b *testing.B) { assert.Equal(b, `{"age":128,"price":9000000000000000123,"width":134.300000}`, doc.Marshal()) // negative operator test - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { age := root.GetCounter("age") age.Increase(-5) age.Increase(-3.14) @@ -406,7 +406,7 @@ func BenchmarkDocument(b *testing.B) { // TODO: it should be modified to error check // when 'Remove panic from server code (#50)' is completed. - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { defer func() { r := recover() assert.NotNil(b, r) @@ -501,7 +501,7 @@ func benchmarkText(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewText("k1") for c := 0; c < cnt; c++ { text.Edit(c, c, "a") @@ -517,7 +517,7 @@ func benchmarkTextDeleteAll(cnt int, b *testing.B) { b.StopTimer() doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewText("k1") for c := 0; c < cnt; c++ { text.Edit(c, c, "a") @@ -527,7 +527,7 @@ func benchmarkTextDeleteAll(cnt int, b *testing.B) { assert.NoError(b, err) b.StartTimer() - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("k1") text.Edit(0, cnt, "") return nil @@ -544,7 +544,7 @@ func benchmarkTextEditGC(cnt int, b *testing.B) { assert.Equal(b, "{}", doc.Marshal()) assert.False(b, doc.HasLocalChanges()) - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewText("k1") for i := 0; i < cnt; i++ { text.Edit(i, i, "a") @@ -553,7 +553,7 @@ func benchmarkTextEditGC(cnt int, b *testing.B) { }, "creates a text then appends a") assert.NoError(b, err) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("k1") for i := 0; i < cnt; i++ { text.Edit(i, i+1, "b") @@ -575,14 +575,14 @@ func benchmarkTextSplitGC(cnt int, b *testing.B) { for i := 0; i < cnt; i++ { builder.WriteString("a") } - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewText("k2") text.Edit(0, 0, builder.String()) return nil }, "initial") assert.NoError(b, err) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("k2") for i := 0; i < cnt; i++ { if i != cnt { @@ -602,7 +602,7 @@ func benchmarkArray(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { array := root.SetNewArray("k1") for c := 0; c < cnt; c++ { array.AddInteger(c) @@ -616,7 +616,7 @@ func benchmarkArray(cnt int, b *testing.B) { func benchmarkArrayGC(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetNewArray("1") for i := 0; i < cnt; i++ { root.GetArray("1").AddInteger(i) @@ -626,7 +626,7 @@ func benchmarkArrayGC(cnt int, b *testing.B) { }, "creates an array then adds integers") assert.NoError(b, err) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.Delete("1") return nil }, "deletes the array") @@ -641,7 +641,7 @@ func benchmarkCounter(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { counter := root.SetNewCounter("k1", 0) for c := 0; c < cnt; c++ { counter.Increase(c) @@ -656,7 +656,7 @@ func benchmarkRichText(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { text := root.SetNewRichText("k1") for c := 0; c < cnt; c++ { text.Edit(0, 0, "a", nil) @@ -671,7 +671,7 @@ func benchmarkObject(cnt int, b *testing.B) { for i := 0; i < b.N; i++ { doc := document.New("d1") - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { for c := 0; c < cnt; c++ { root.SetInteger("k1", c) } diff --git a/test/bench/grpc_bench_test.go b/test/bench/grpc_bench_test.go index 75752a846..13f818ced 100644 --- a/test/bench/grpc_bench_test.go +++ b/test/bench/grpc_bench_test.go @@ -27,12 +27,13 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/yorkie-team/yorkie/admin" "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/client" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/server" "github.com/yorkie-team/yorkie/server/backend/database" "github.com/yorkie-team/yorkie/server/logging" @@ -88,7 +89,7 @@ func benchmarkUpdateAndSync( key string, ) { for i := 0; i < cnt; i++ { - err := d.Update(func(root *proxy.ObjectProxy) error { + err := d.Update(func(root *json.Object) error { text := root.GetText(key) text.Edit(0, 0, "c") return nil @@ -181,7 +182,7 @@ func BenchmarkRPC(b *testing.B) { for i := 0; i < b.N; i++ { testKey := "testKey" - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText(testKey) return nil }) @@ -202,7 +203,7 @@ func BenchmarkRPC(b *testing.B) { err := c1.Attach(ctx, d1) assert.NoError(b, err) testKey1 := "testKey1" - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText(testKey1) return nil }) @@ -212,7 +213,7 @@ func BenchmarkRPC(b *testing.B) { err = c2.Attach(ctx, d2) assert.NoError(b, err) testKey2 := "testKey2" - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetNewText(testKey2) return nil }) @@ -267,13 +268,13 @@ func BenchmarkRPC(b *testing.B) { doc1 := document.New(key.Key(b.Name())) doc2 := document.New(key.Key(b.Name())) - err := doc1.Update(func(root *proxy.ObjectProxy) error { + err := doc1.Update(func(root *json.Object) error { text := root.SetNewText("k1") text.Edit(0, 0, builder.String()) return nil }) assert.NoError(b, err) - err = doc2.Update(func(root *proxy.ObjectProxy) error { + err = doc2.Update(func(root *json.Object) error { text := root.SetNewText("k1") text.Edit(0, 0, builder.String()) return nil diff --git a/test/bench/text_editing_bench_test.go b/test/bench/text_editing_bench_test.go index 16554eec2..0a61fb0cc 100644 --- a/test/bench/text_editing_bench_test.go +++ b/test/bench/text_editing_bench_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" - "github.com/yorkie-team/yorkie/pkg/document/proxy" + "github.com/yorkie-team/yorkie/pkg/document/json" ) func BenchmarkTextEditing(b *testing.B) { @@ -41,7 +41,7 @@ func BenchmarkTextEditing(b *testing.B) { b.StartTimer() doc := document.New("d1") - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { root.SetNewText("text") return nil }) @@ -50,7 +50,7 @@ func BenchmarkTextEditing(b *testing.B) { cursor := int(edit[0].(float64)) mode := int(edit[1].(float64)) - err = doc.Update(func(root *proxy.ObjectProxy) error { + err = doc.Update(func(root *json.Object) error { text := root.GetText("text") if mode == 0 { value := edit[2].(string) diff --git a/test/helper/helper.go b/test/helper/helper.go index 49b5749de..429f63739 100644 --- a/test/helper/helper.go +++ b/test/helper/helper.go @@ -26,7 +26,7 @@ import ( adminClient "github.com/yorkie-team/yorkie/admin" "github.com/yorkie-team/yorkie/pkg/document/change" - "github.com/yorkie-team/yorkie/pkg/document/json" + "github.com/yorkie-team/yorkie/pkg/document/crdt" "github.com/yorkie-team/yorkie/pkg/document/time" "github.com/yorkie-team/yorkie/server" "github.com/yorkie-team/yorkie/server/admin" @@ -91,12 +91,12 @@ func CreateAdminCli(t assert.TestingT, adminAddr string) *adminClient.Client { } // TestRoot returns the root -func TestRoot() *json.Root { - return json.NewRoot(json.NewObject(json.NewRHTPriorityQueueMap(), time.InitialTicket)) +func TestRoot() *crdt.Root { + return crdt.NewRoot(crdt.NewObject(crdt.NewRHTPriorityQueueMap(), time.InitialTicket)) } // TextChangeContext returns the context of test change. -func TextChangeContext(root *json.Root) *change.Context { +func TextChangeContext(root *crdt.Root) *change.Context { return change.NewContext( change.InitialID, "", diff --git a/test/integration/array_test.go b/test/integration/array_test.go index e6658db91..4eaa2f910 100644 --- a/test/integration/array_test.go +++ b/test/integration/array_test.go @@ -25,8 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestArray(t *testing.T) { @@ -40,7 +40,7 @@ func TestArray(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k1"). AddString("v1"). AddNewArray().AddString("1", "2", "3") @@ -62,7 +62,7 @@ func TestArray(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k1").AddString("v1", "v2") return nil }, "add v1, v2 by c1") @@ -75,13 +75,13 @@ func TestArray(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetArray("k1").Delete(1) return nil }, "delete v2 by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetArray("k1").AddString("v3") return nil }, "add v3 by c2") @@ -97,7 +97,7 @@ func TestArray(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k1").AddString("v1") return nil }, "new array and add v1") @@ -109,14 +109,14 @@ func TestArray(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetArray("k1").AddString("v2", "v3") root.GetArray("k1").Delete(1) return nil }, "add v2, v3 and delete v2 by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetArray("k1").AddString("v4", "v5") return nil }, "add v4, v5 by c2") @@ -132,7 +132,7 @@ func TestArray(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k1").AddString("v1", "v2", "v3") return nil }, "new array and add v1 v2 v3") @@ -144,20 +144,20 @@ func TestArray(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetArray("k1").Delete(1) return nil }, "delete v2") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetArray("k1").Delete(1) return nil }, "delete v2") assert.NoError(t, err) syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { assert.Equal(t, 2, root.GetArray("k1").Len()) return nil }, "check array length") @@ -172,7 +172,7 @@ func TestArray(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k1").AddInteger(0, 1, 2) assert.Equal(t, `{"k1":[0,1,2]}`, root.Marshal()) return nil @@ -185,7 +185,7 @@ func TestArray(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { prev := root.GetArray("k1").Get(0) elem := root.GetArray("k1").Get(2) root.GetArray("k1").MoveBefore(prev.CreatedAt(), elem.CreatedAt()) @@ -194,7 +194,7 @@ func TestArray(t *testing.T) { }, "move 2 before 0") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { prev := root.GetArray("k1").Get(1) elem := root.GetArray("k1").Get(2) root.GetArray("k1").MoveBefore(prev.CreatedAt(), elem.CreatedAt()) @@ -211,7 +211,7 @@ func TestArray(t *testing.T) { d1 := document.New(key.Key(t.Name())) assert.NoError(t, c1.Attach(ctx, d1)) - assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d1.Update(func(root *json.Object) error { root.SetNewArray("k1").AddInteger(0, 1, 2) assert.Equal(t, `{"k1":[0,1,2]}`, root.Marshal()) return nil @@ -220,7 +220,7 @@ func TestArray(t *testing.T) { d2 := document.New(key.Key(t.Name())) assert.NoError(t, c2.Attach(ctx, d2)) - assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d1.Update(func(root *json.Object) error { next := root.GetArray("k1").Get(0) elem := root.GetArray("k1").Get(2) root.GetArray("k1").MoveBefore(next.CreatedAt(), elem.CreatedAt()) @@ -228,7 +228,7 @@ func TestArray(t *testing.T) { return nil })) - assert.NoError(t, d2.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d2.Update(func(root *json.Object) error { next := root.GetArray("k1").Get(0) elem := root.GetArray("k1").Get(1) root.GetArray("k1").MoveBefore(next.CreatedAt(), elem.CreatedAt()) diff --git a/test/integration/auth_webhook_test.go b/test/integration/auth_webhook_test.go index ae8065e02..782bb4f0c 100644 --- a/test/integration/auth_webhook_test.go +++ b/test/integration/auth_webhook_test.go @@ -33,8 +33,8 @@ import ( "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/client" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/server" "github.com/yorkie-team/yorkie/test/helper" ) @@ -323,7 +323,7 @@ func TestAuthWebhook(t *testing.T) { // 01. multiple requests to update the document. for i := 0; i < 3; i++ { - assert.NoError(t, doc.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, doc.Update(func(root *json.Object) error { root.SetNewObject("k1") return nil })) @@ -333,7 +333,7 @@ func TestAuthWebhook(t *testing.T) { // 02. multiple requests to update the document after eviction by ttl. time.Sleep(authorizedTTL) for i := 0; i < 3; i++ { - assert.NoError(t, doc.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, doc.Update(func(root *json.Object) error { root.SetNewObject("k1") return nil })) diff --git a/test/integration/cluster_mode_test.go b/test/integration/cluster_mode_test.go index d4dbb78b0..3b85dfdb1 100644 --- a/test/integration/cluster_mode_test.go +++ b/test/integration/cluster_mode_test.go @@ -31,8 +31,8 @@ import ( "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/client" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/server/backend/sync" "github.com/yorkie-team/yorkie/test/helper" ) @@ -190,7 +190,7 @@ func TestClusterMode(t *testing.T) { }) cancel2() - assert.NoError(t, d2.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d2.Update(func(root *json.Object) error { root.SetString("hello", "world") return nil })) diff --git a/test/integration/counter_test.go b/test/integration/counter_test.go index b0fd97ee0..494197880 100644 --- a/test/integration/counter_test.go +++ b/test/integration/counter_test.go @@ -26,8 +26,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestCounter(t *testing.T) { @@ -41,7 +41,7 @@ func TestCounter(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewCounter("age", 1). Increase(2). Increase(2.5). @@ -66,7 +66,7 @@ func TestCounter(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewCounter("age", 0) root.SetNewCounter("width", 0) root.SetNewCounter("height", 0) @@ -80,7 +80,7 @@ func TestCounter(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetCounter("age"). Increase(1). Increase(2). @@ -90,13 +90,13 @@ func TestCounter(t *testing.T) { }) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetCounter("width").Increase(math.MaxInt32 + 100).Increase(10) return nil }) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetCounter("age").Increase(20) root.GetCounter("width").Increase(100).Increase(200) root.GetCounter("height").Increase(50) diff --git a/test/integration/document_test.go b/test/integration/document_test.go index e79f3b206..00d92c778 100644 --- a/test/integration/document_test.go +++ b/test/integration/document_test.go @@ -20,6 +20,7 @@ package integration import ( "context" + "github.com/yorkie-team/yorkie/pkg/document/json" "io" "sync" "testing" @@ -29,7 +30,6 @@ import ( "github.com/yorkie-team/yorkie/client" "github.com/yorkie-team/yorkie/pkg/document" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestDocument(t *testing.T) { @@ -40,7 +40,7 @@ func TestDocument(t *testing.T) { t.Run("attach/detach test", func(t *testing.T) { ctx := context.Background() doc := document.New(key.Key(t.Name())) - err := doc.Update(func(root *proxy.ObjectProxy) error { + err := doc.Update(func(root *json.Object) error { root.SetString("k1", "v1") return nil }, "update k1 with v1") @@ -55,7 +55,7 @@ func TestDocument(t *testing.T) { assert.False(t, doc.IsAttached()) doc2 := document.New(key.Key(t.Name())) - err = doc2.Update(func(root *proxy.ObjectProxy) error { + err = doc2.Update(func(root *json.Object) error { root.SetString("k1", "v2") return nil }, "update k1 with v2") @@ -77,26 +77,26 @@ func TestDocument(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewObject("k1").SetNewArray("k1.1").AddString("1", "2") return nil }) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewArray("k2").AddString("1", "2", "3") return nil }) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetNewArray("k1").AddString("4", "5") root.SetNewArray("k2").AddString("6", "7") return nil }) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.Delete("k2") return nil }) @@ -142,7 +142,7 @@ func TestDocument(t *testing.T) { }() // 02. cli2 updates doc2. - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetString("key", "value") return nil }) diff --git a/test/integration/gc_test.go b/test/integration/gc_test.go index 0a2c3ca45..f8ab49ebc 100644 --- a/test/integration/gc_test.go +++ b/test/integration/gc_test.go @@ -25,8 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestGarbageCollection(t *testing.T) { @@ -45,7 +45,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetInteger("1", 1) root.SetNewArray("2").AddInteger(1, 2, 3) root.SetInteger("3", 3) @@ -63,7 +63,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Sync(ctx) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.Delete("2") return nil }, "removes 2") @@ -113,7 +113,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("text"). Edit(0, 0, "Hello world") root.SetNewRichText("richText"). @@ -132,7 +132,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Sync(ctx) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("text"). Edit(0, 1, "a"). Edit(1, 2, "b") @@ -186,7 +186,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetInteger("1", 1) root.SetNewArray("2").AddInteger(1, 2, 3) root.SetInteger("3", 3) @@ -206,7 +206,7 @@ func TestGarbageCollection(t *testing.T) { err = c2.Sync(ctx) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.Delete("2") root.GetText("4").Edit(0, 1, "h") root.GetRichText("5").Edit(0, 1, "h", map[string]string{"b": "1"}) diff --git a/test/integration/history_test.go b/test/integration/history_test.go index 87479972d..b0f4e9f69 100644 --- a/test/integration/history_test.go +++ b/test/integration/history_test.go @@ -25,8 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/test/helper" ) @@ -45,19 +45,19 @@ func TestHistory(t *testing.T) { assert.NoError(t, cli.Attach(ctx, d1)) defer func() { assert.NoError(t, cli.Detach(ctx, d1)) }() - assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d1.Update(func(root *json.Object) error { root.SetNewArray("todos") return nil }, "create todos")) assert.Equal(t, `{"todos":[]}`, d1.Marshal()) - assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d1.Update(func(root *json.Object) error { root.GetArray("todos").AddString("buy coffee") return nil }, "buy coffee")) assert.Equal(t, `{"todos":["buy coffee"]}`, d1.Marshal()) - assert.NoError(t, d1.Update(func(root *proxy.ObjectProxy) error { + assert.NoError(t, d1.Update(func(root *json.Object) error { root.GetArray("todos").AddString("buy bread") return nil }, "buy bread")) diff --git a/test/integration/object_test.go b/test/integration/object_test.go index 395d541ef..e8f9e372f 100644 --- a/test/integration/object_test.go +++ b/test/integration/object_test.go @@ -25,8 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestObject(t *testing.T) { @@ -45,7 +45,7 @@ func TestObject(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewObject("k1"). SetString("k1.1", "v1"). SetString("k1.2", "v2"). @@ -59,7 +59,7 @@ func TestObject(t *testing.T) { assert.NoError(t, err) syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.Delete("k1") root.GetObject("k2").Delete("k2.2") return nil @@ -74,7 +74,7 @@ func TestObject(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewObject("k1") return nil }, "set v1 by c1") @@ -87,7 +87,7 @@ func TestObject(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.Delete("k1") root.SetString("k1", "v1") return nil @@ -95,7 +95,7 @@ func TestObject(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"k1":"v1"}`, d1.Marshal()) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.Delete("k1") root.SetString("k1", "v2") return nil @@ -116,12 +116,12 @@ func TestObject(t *testing.T) { assert.NoError(t, err) // 01. concurrent set on same key - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetString("k1", "v1") return nil }, "set k1 by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetString("k1", "v2") return nil }, "set k1 by c2") @@ -129,19 +129,19 @@ func TestObject(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) // 02. concurrent set between ancestor descendant - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewObject("k2") return nil }, "set k2 by c1") assert.NoError(t, err) syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetString("k2", "v2") return nil }, "set k2 by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetObject("k2").SetNewObject("k2.1").SetString("k2.1.1", "v2") return nil }, "set k2.1.1 by c2") @@ -149,12 +149,12 @@ func TestObject(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) // 03. concurrent set between independent keys - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetString("k3", "v3") return nil }, "set k3 by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetString("k4", "v4") return nil }, "set k4 by c2") diff --git a/test/integration/primitive_test.go b/test/integration/primitive_test.go index 281b9d2c6..a0e13dfae 100644 --- a/test/integration/primitive_test.go +++ b/test/integration/primitive_test.go @@ -26,8 +26,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestPrimitive(t *testing.T) { @@ -41,7 +41,7 @@ func TestPrimitive(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewObject("k1"). SetBool("k1.1", true). SetInteger("k1.2", 2147483647). diff --git a/test/integration/snapshot_test.go b/test/integration/snapshot_test.go index c04dd8b45..705b6ec47 100644 --- a/test/integration/snapshot_test.go +++ b/test/integration/snapshot_test.go @@ -21,6 +21,7 @@ package integration import ( "context" "fmt" + "github.com/yorkie-team/yorkie/pkg/document/json" "testing" "time" @@ -28,7 +29,6 @@ import ( "github.com/yorkie-team/yorkie/pkg/document" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" "github.com/yorkie-team/yorkie/test/helper" ) @@ -50,7 +50,7 @@ func TestSnapshot(t *testing.T) { // 01. Update changes over snapshot threshold. for i := 0; i <= int(helper.SnapshotThreshold); i++ { - err := d1.Update(func(root *proxy.ObjectProxy) error { + err := d1.Update(func(root *json.Object) error { root.SetInteger(fmt.Sprintf("%d", i), i) return nil }) @@ -63,7 +63,7 @@ func TestSnapshot(t *testing.T) { time.Sleep(500 * time.Millisecond) // 02. Makes local changes then pull a snapshot from the server. - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.SetString("key", "value") return nil }) @@ -83,7 +83,7 @@ func TestSnapshot(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("k1") return nil }) @@ -104,7 +104,7 @@ func TestSnapshot(t *testing.T) { } for _, edit := range edits { - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(edit.from, edit.to, edit.content) return nil }) @@ -128,7 +128,7 @@ func TestSnapshot(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("k1") return nil }) @@ -141,14 +141,14 @@ func TestSnapshot(t *testing.T) { assert.NoError(t, err) for i := 0; i <= int(helper.SnapshotThreshold); i++ { - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(i, i, "x") return nil }) assert.NoError(t, err) } - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 0, "o") return nil }) diff --git a/test/integration/text_test.go b/test/integration/text_test.go index 9a1470d63..9c5f0a9c6 100644 --- a/test/integration/text_test.go +++ b/test/integration/text_test.go @@ -25,8 +25,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/yorkie-team/yorkie/pkg/document" + "github.com/yorkie-team/yorkie/pkg/document/json" "github.com/yorkie-team/yorkie/pkg/document/key" - "github.com/yorkie-team/yorkie/pkg/document/proxy" ) func TestText(t *testing.T) { @@ -41,7 +41,7 @@ func TestText(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("k1") return nil }, "set a new text by c1") @@ -53,13 +53,13 @@ func TestText(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 0, "ABCD") return nil }, "edit 0,0 ABCD by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 0, "1234") return nil }, "edit 0,0 1234 by c2") @@ -67,13 +67,13 @@ func TestText(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(2, 3, "XX") return nil }, "edit 2,3 XX by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(2, 3, "YY") return nil }, "edit 2,3 YY by c2") @@ -81,13 +81,13 @@ func TestText(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(4, 5, "ZZ") return nil }, "edit 4,5 ZZ by c1") assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(2, 3, "TT") return nil }, "edit 2,3 TT by c2") @@ -103,7 +103,7 @@ func TestText(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewRichText("k1").Edit(0, 0, "Hello world", nil) return nil }, `set a new text with "Hello world" by c1`) @@ -115,14 +115,14 @@ func TestText(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 1, map[string]string{"b": "1"}) return nil }, `set style b to "H" by c1`) assert.NoError(t, err) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { text := root.GetRichText("k1") text.SetStyle(0, 5, map[string]string{"i": "1"}) return nil @@ -139,7 +139,7 @@ func TestText(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("k1") return nil }, "set a new text by c1") @@ -151,7 +151,7 @@ func TestText(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 0, "123") root.GetText("k1").Edit(3, 3, "456") root.GetText("k1").Edit(6, 6, "789") @@ -162,14 +162,14 @@ func TestText(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) assert.Equal(t, `{"k1":"123456789"}`, d2.Marshal()) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(1, 7, "") return nil }, "delete block by c1") assert.NoError(t, err) assert.Equal(t, `{"k1":"189"}`, d1.Marshal()) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(2, 5, "") return nil }, "delete block by c2") @@ -186,7 +186,7 @@ func TestText(t *testing.T) { err := c1.Attach(ctx, d1) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.SetNewText("k1") return nil }, "set a new text by c1") @@ -198,7 +198,7 @@ func TestText(t *testing.T) { err = c2.Attach(ctx, d2) assert.NoError(t, err) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 0, "0") root.GetText("k1").Edit(1, 1, "0") root.GetText("k1").Edit(2, 2, "0") @@ -209,7 +209,7 @@ func TestText(t *testing.T) { syncClientsThenAssertEqual(t, []clientAndDocPair{{c1, d1}, {c2, d2}}) assert.Equal(t, `{"k1":"000"}`, d2.Marshal()) - err = d1.Update(func(root *proxy.ObjectProxy) error { + err = d1.Update(func(root *json.Object) error { root.GetText("k1").Edit(1, 2, "1") root.GetText("k1").Edit(1, 2, "1") root.GetText("k1").Edit(1, 2, "") @@ -218,7 +218,7 @@ func TestText(t *testing.T) { assert.NoError(t, err) assert.Equal(t, `{"k1":"00"}`, d1.Marshal()) - err = d2.Update(func(root *proxy.ObjectProxy) error { + err = d2.Update(func(root *json.Object) error { root.GetText("k1").Edit(0, 3, "") return nil }, "delete the range includes above new nodes")