Skip to content

Commit

Permalink
Merge branch 'main' into tree
Browse files Browse the repository at this point in the history
  • Loading branch information
humdrum committed Jul 21, 2023
2 parents 89f1ba1 + e02ff33 commit acd3244
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 37 deletions.
2 changes: 1 addition & 1 deletion Sources/Document/Change/Change.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import Foundation
/**
* `Change` represents a unit of modification in the document.
*/
struct Change {
public struct Change {
/// The ID of this change.
private(set) var id: ChangeID

Expand Down
36 changes: 32 additions & 4 deletions Sources/Document/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,21 +276,21 @@ public actor Document {
/**
* `toJSON` returns the JSON encoding of this array.
*/
func toJSON() -> String {
public func toJSON() -> String {
return self.root.toJSON()
}

/**
* `toSortedJSON` returns the sorted JSON encoding of this array.
*/
func toSortedJSON() -> String {
public func toSortedJSON() -> String {
return self.root.debugDescription
}

/**
* `applySnapshot` applies the given snapshot into this document.
*/
func applySnapshot(serverSeq: Int64, snapshot: Data) throws {
public func applySnapshot(serverSeq: Int64, snapshot: Data) throws {
let obj = try Converter.bytesToObject(bytes: snapshot)
self.root = CRDTRoot(rootObject: obj)
self.changeID.syncLamport(with: serverSeq)
Expand All @@ -305,7 +305,7 @@ public actor Document {
/**
* `applyChanges` applies the given changes into this document.
*/
func applyChanges(changes: [Change]) throws {
public func applyChanges(changes: [Change]) throws {
Logger.debug(
"""
trying to apply \(changes.count) remote changes.
Expand Down Expand Up @@ -348,6 +348,34 @@ public actor Document {
)
}

/**
* `getValueByPath` returns the JSONElement corresponding to the given path.
*/
public func getValueByPath(path: String) throws -> Any? {
guard path.starts(with: JSONObject.rootKey) else {
throw YorkieError.unexpected(message: "The path must start with \(JSONObject.rootKey)")
}

let rootObject = self.getRoot()

if path == JSONObject.rootKey {
return rootObject
}

var subPath = path
subPath.removeFirst(JSONObject.rootKey.count) // remove root path("$")

let keySeparator = JSONObject.keySeparator

guard subPath.starts(with: keySeparator) else {
throw YorkieError.unexpected(message: "Invalid path.")
}

subPath.removeFirst(keySeparator.count)

return rootObject.get(keyPath: subPath)
}

private func createPaths(change: Change) -> [String] {
let pathTrie = Trie<String>(value: "$")
for op in change.operations {
Expand Down
10 changes: 8 additions & 2 deletions Sources/Document/Json/JSONObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import Foundation
*/
@dynamicMemberLookup
public class JSONObject {
private static let keySeparator = "/.^/"
static let rootKey = "$"
static let keySeparator = "."
private let reservedCharacterForKey = JSONObject.keySeparator
private func isValidKey(_ key: String) -> Bool {
return key.contains(self.reservedCharacterForKey) == false
Expand All @@ -46,7 +47,7 @@ public class JSONObject {

func set<T>(key: String, value: T) {
guard self.isValidKey(key) else {
Logger.error("The key \(key) doesn't have the reserved characters: \(self.reservedCharacterForKey)")
assertionFailure("The key \(key) doesn't have the reserved characters: \(self.reservedCharacterForKey)")
return
}

Expand Down Expand Up @@ -176,6 +177,11 @@ public class JSONObject {
}

public func get(key: String) -> Any? {
guard self.isValidKey(key) else {
assertionFailure("The key \(key) doesn't have the reserved characters: \(self.reservedCharacterForKey)")
return nil
}

guard let value = try? self.target.get(key: key) else {
Logger.error("The value does not exist. - key: \(key)")
return nil
Expand Down
36 changes: 18 additions & 18 deletions Sources/Util/LLRBTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ import Foundation
* Invariant 2: Every leaf path has the same number of black nodes
* Invariant 3: Only the left child can be red (left leaning)
*/
public class LLRBTree<K: Comparable, V> {
public typealias Entry<K, V> = (key: K, value: V)
class LLRBTree<K: Comparable, V> {
typealias Entry<K, V> = (key: K, value: V)

/**
* `LLRBNode` is node of LLRBTree.
*/
class Node<K, V>: CustomDebugStringConvertible {
public var key: K
public var value: V
public var parent: Node<K, V>?
public var left: Node<K, V>?
public var right: Node<K, V>?
public var isRed: Bool
var key: K
var value: V
var parent: Node<K, V>?
var left: Node<K, V>?
var right: Node<K, V>?
var isRed: Bool

init(_ key: K, _ value: V, _ isRed: Bool) {
self.key = key
Expand Down Expand Up @@ -66,7 +66,7 @@ public class LLRBTree<K: Comparable, V> {
* `put` puts the value of the given key.
*/
@discardableResult
public func put(_ key: K, _ value: V) -> V {
func put(_ key: K, _ value: V) -> V {
self.root = self.putInternal(key, value, self.root)
self.root?.isRed = false

Expand All @@ -76,14 +76,14 @@ public class LLRBTree<K: Comparable, V> {
/**
* `get` gets a value of the given key.
*/
public func get(_ key: K) -> V? {
func get(_ key: K) -> V? {
self.getInternal(key, self.root)?.value
}

/**
* `remove` removes a element of key.
*/
public func remove(_ key: K) {
func remove(_ key: K) {
guard let root else {
return
}
Expand All @@ -97,7 +97,7 @@ public class LLRBTree<K: Comparable, V> {
self.root?.isRed = false
}

public var values: [V] {
var values: [V] {
var result = [V]()

self.traverseInorder(self.root, &result)
Expand All @@ -119,7 +119,7 @@ public class LLRBTree<K: Comparable, V> {
* `floorEntry` returns the entry for the greatest key less than or equal to the
* given key. If there is no such key, returns `undefined`.
*/
public func floorEntry(_ key: K) -> Entry<K, V>? {
func floorEntry(_ key: K) -> Entry<K, V>? {
var node = self.root

while node != nil {
Expand Down Expand Up @@ -154,7 +154,7 @@ public class LLRBTree<K: Comparable, V> {
/**
* `lastEntry` returns last entry of LLRBTree.
*/
public func lastEntry() -> Entry<K, V>? {
func lastEntry() -> Entry<K, V>? {
if self.root == nil {
return nil
}
Expand All @@ -169,26 +169,26 @@ public class LLRBTree<K: Comparable, V> {
/**
* `size` is a size of LLRBTree.
*/
public var size: Int {
var size: Int {
self.counter
}

/**
* `isEmpty` checks if size is empty.
*/
public var isEmpty: Bool {
var isEmpty: Bool {
self.counter == 0
}

public var minValue: V? {
var minValue: V? {
guard let root else {
return nil
}

return self.min(root).value
}

public var maxValue: V? {
var maxValue: V? {
guard let root else {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

import Foundation

let yorkieVersion = "0.3.5"
let yorkieVersion = "0.3.6"
47 changes: 38 additions & 9 deletions Tests/Unit/Document/DocumentTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -728,9 +728,21 @@ class DocumentTests: XCTestCase {
func test_change_paths_test_for_object() async throws {
let target = Document(key: "test-doc")

var eventCount = 0
let paths = [["$", "$.", "$..obj"], ["$..obj", "$..obj", "$..obj", "$."]]

await target.subscribe { event in
XCTAssertEqual(event.type, .localChange)
XCTAssertEqual((event as? LocalChangeEvent)?.value.operations.compactMap { $0.path }, ["$", "$.", "$..obj", "$..obj", "$..obj", "$..obj", "$."])
XCTAssertEqual((event as? LocalChangeEvent)?.value.operations.compactMap { $0.path }, paths[eventCount])
eventCount += 1
}

await target.subscribe(targetPath: "$.") { _ in
Task {
let array = try? await target.getValueByPath(path: "$.") as? JSONObject

XCTAssertTrue(array != nil)
}
}

try await target.update { root in
Expand All @@ -756,6 +768,15 @@ class DocumentTests: XCTestCase {
"""
{"":{"obj":{"a":1}}}
""")
}

let value = try await target.getValueByPath(path: "$..obj.a") as? Int64

XCTAssertEqual(value, 1)

try await target.update { root in
let emptyKey = root[""] as? JSONObject
let obj = emptyKey!.obj as? JSONObject

obj!.remove(key: "a")

Expand All @@ -764,14 +785,14 @@ class DocumentTests: XCTestCase {
{"":{"obj":{}}}
""")

obj!["$.hello"] = Int64(1)
obj!["$hello"] = Int64(1)

XCTAssertEqual(root.debugDescription,
"""
{"":{"obj":{"$.hello":1}}}
{"":{"obj":{"$hello":1}}}
""")

obj!.remove(key: "$.hello")
obj!.remove(key: "$hello")

XCTAssertEqual(root.debugDescription,
"""
Expand Down Expand Up @@ -799,8 +820,8 @@ class DocumentTests: XCTestCase {
XCTAssertEqual(ops[1] as! AddOpInfo, AddOpInfo(path: "$.arr", index: 0))
XCTAssertEqual(ops[2] as! AddOpInfo, AddOpInfo(path: "$.arr", index: 1))
XCTAssertEqual(ops[3] as! RemoveOpInfo, RemoveOpInfo(path: "$.arr", key: nil, index: 1))
XCTAssertEqual(ops[4] as! SetOpInfo, SetOpInfo(path: "$", key: "$$...hello"))
XCTAssertEqual(ops[5] as! AddOpInfo, AddOpInfo(path: "$.$$...hello", index: 0))
XCTAssertEqual(ops[4] as! SetOpInfo, SetOpInfo(path: "$", key: "$$hello"))
XCTAssertEqual(ops[5] as! AddOpInfo, AddOpInfo(path: "$.$$hello", index: 0))
} else {
XCTAssert(false, "No operations.")
}
Expand All @@ -824,13 +845,13 @@ class DocumentTests: XCTestCase {
arr?.append(Int64(0))
arr?.append(Int64(1))
arr?.remove(index: 1)
root["$$...hello"] = [] as [String]
let hello = root["$$...hello"] as? JSONArray
root["$$hello"] = [] as [Any]
let hello = root["$$hello"] as? JSONArray
hello?.append(Int64(0))

XCTAssertEqual(root.debugDescription,
"""
{"$$...hello":[0],"arr":[0]}
{"$$hello":[0],"arr":[0]}
""")
}
}
Expand All @@ -857,6 +878,10 @@ class DocumentTests: XCTestCase {
(root.cnt as? JSONCounter<Int64>)?.increase(value: 10)
(root.cnt as? JSONCounter<Int64>)?.increase(value: -3)
}

let cnt = try await target.getValueByPath(path: "$.cnt") as? JSONCounter<Int64>

XCTAssertEqual(cnt?.value, 8)
}

func test_change_paths_test_for_text() async throws {
Expand All @@ -880,6 +905,10 @@ class DocumentTests: XCTestCase {
(root.text as? JSONText)?.edit(0, 0, "hello world")
(root.text as? JSONText)?.select(0, 2)
}

let text = try await target.getValueByPath(path: "$.text") as? JSONText

XCTAssertEqual(text?.plainText, "hello world")
}

func test_change_paths_test_for_text_with_attributes() async throws {
Expand Down
4 changes: 2 additions & 2 deletions Tests/Unit/Document/JSONObjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class JSONObjectTests: XCTestCase {
{"object":{"array":[{"id":200}],"id":100,"type":"struct"}}
""")

let array = root[keyPath: "object/.^/array"] as? JSONArray
let array = root[keyPath: "object.array"] as? JSONArray
array!.append(JsonArrayTestType(id: 300))

XCTAssertEqual(root.debugDescription,
Expand Down Expand Up @@ -222,7 +222,7 @@ class JSONObjectTests: XCTestCase {
{"object":{"first":{"second":{"third":{"value":"initial"}}}}}
""")

let third = root[keyPath: "object/.^/first/.^/second/.^/third"] as? JSONObject
let third = root[keyPath: "object.first.second.third"] as? JSONObject
third!.value = "changed"

XCTAssertEqual(root.debugDescription,
Expand Down

0 comments on commit acd3244

Please sign in to comment.