Change Counter increase() to accept any numeric value (#51)

This commit is contained in:
Jung gyun Ahn 2023-01-13 19:54:59 +09:00 committed by GitHub
parent 43872169ed
commit 247744cf1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 44 deletions

View File

@ -60,17 +60,9 @@ class CRDTCounter<T: YorkieCountable>: CRDTElement {
public func increase(_ primitive: Primitive) throws -> CRDTCounter {
switch primitive.value {
case .integer(let int32Value):
guard let value = int32Value as? T else {
throw YorkieError.type(message: "Value Type mismatch: \(type(of: primitive)), \(T.self)")
}
self.value = self.value.addingReportingOverflow(value).partialValue
self.value &+= T(int32Value)
case .long(let int64Value):
guard let value = int64Value as? T else {
throw YorkieError.type(message: "Value Type mismatch: \(type(of: primitive.value))")
}
self.value = self.value.addingReportingOverflow(value).partialValue
self.value &+= T(int64Value)
default:
throw YorkieError.type(message: "Unsupported type of value: \(type(of: primitive.value))")
}

View File

@ -65,6 +65,8 @@ class Primitive: CRDTElement {
return .integer(casted)
case let casted as Int64:
return .long(casted)
case let casted as Int:
return .long(Int64(casted))
case let casted as Double:
return .double(casted)
case let casted as String:

View File

@ -59,28 +59,47 @@ public class JSONCounter<T: YorkieCountable> {
* `increase` increases numeric data.
*/
@discardableResult
public func increase<T: YorkieCountable>(value: T) throws -> Self {
public func increase(value: any BinaryInteger) -> Self {
guard let context, let counter else {
let log = "it is not initialized yet"
Logger.critical(log)
throw YorkieError.unexpected(message: log)
return self
}
guard let primitiveValue = Primitive.type(of: value) else {
throw YorkieError.type(message: "Unsupported type of value: \(type(of: T.self))")
let log = "Unsupported type of value: \(type(of: T.self))"
Logger.critical(log)
return self
}
let ticket = context.issueTimeTicket()
let primitive = Primitive(value: primitiveValue, createdAt: ticket)
guard primitive.isNumericType else {
throw YorkieError.type(message: "Unsupported type of value: \(type(of: T.self))")
let log = "Unsupported type of value: \(type(of: T.self))"
Logger.critical(log)
return self
}
do {
try counter.increase(primitive)
} catch {
let log = "catch error when increase counter: [\(error)]"
Logger.critical(log)
return self
}
context.push(operation: IncreaseOperation(parentCreatedAt: counter.createdAt, value: primitive, executedAt: ticket))
return self
}
@discardableResult
public func increase(value: any BinaryFloatingPoint) -> Self {
self.increase(value: T(value))
}
}

View File

@ -19,6 +19,7 @@ import Foundation
public protocol YorkieCountable: BinaryInteger {
var littleEndian: Self { get }
func addingReportingOverflow(_ other: Self) -> (partialValue: Self, overflow: Bool)
static func &+= (lhs: inout Self, rhs: Self)
}
extension Int32: YorkieCountable {}

View File

@ -32,10 +32,8 @@ final class CounterIntegrationTests: XCTestCase {
await doc.update { root in
root.age = JSONCounter(value: Int32(1))
root.length = JSONCounter(value: Int64(10))
do {
try (root.age as! JSONCounter<Int32>).increase(value: Int32(5))
try (root.length as! JSONCounter<Int64>).increase(value: Int64(3))
} catch {}
(root.age as! JSONCounter<Int32>).increase(value: Int32(5))
(root.length as! JSONCounter<Int64>).increase(value: Int64(3))
}
try await Task.sleep(nanoseconds: 1_000_000_000)
@ -48,10 +46,8 @@ final class CounterIntegrationTests: XCTestCase {
XCTAssert(result == "{\"age\":6,\"length\":13}")
await doc.update { root in
do {
try (root.age as! JSONCounter<Int32>).increase(value: Int32(1)).increase(value: Int32(1))
try (root.length as! JSONCounter<Int64>).increase(value: Int64(3)).increase(value: Int64(1))
} catch {}
(root.age as! JSONCounter<Int32>).increase(value: Int32(1)).increase(value: Int32(1))
(root.length as! JSONCounter<Int64>).increase(value: Int64(3)).increase(value: Int64(1))
}
try await Task.sleep(nanoseconds: 1_000_000_000)
@ -59,21 +55,11 @@ final class CounterIntegrationTests: XCTestCase {
result = await doc.toSortedJSON()
XCTAssert(result == "{\"age\":8,\"length\":17}")
let expectation = XCTestExpectation(description: "failed Test")
var failedTestResult = false
await doc.update { root in
do {
try (root.age as! JSONCounter<Int32>).increase(value: Int64(1))
} catch {
failedTestResult = true
expectation.fulfill()
(root.age as! JSONCounter<Int32>).increase(value: Int64(1))
}
}
wait(for: [expectation], timeout: 1)
XCTAssert(failedTestResult)
result = await doc.toSortedJSON()
XCTAssertEqual(result, "{\"age\":9,\"length\":17}")
}
func test_can_sync_counter() async throws {
@ -110,10 +96,8 @@ final class CounterIntegrationTests: XCTestCase {
XCTAssert(result1 == result2)
await self.d1.update { root in
do {
try (root.age as! JSONCounter<Int32>).increase(value: Int32(5))
try (root.length as! JSONCounter<Int64>).increase(value: Int64(3))
} catch {}
(root.age as! JSONCounter<Int32>).increase(value: Int32(5))
(root.length as! JSONCounter<Int64>).increase(value: Int64(3))
}
try await Task.sleep(nanoseconds: 2_000_000_000)
@ -165,10 +149,8 @@ final class CounterIntegrationTests: XCTestCase {
XCTAssert(result1 == "{\"counts\":[1,10]}")
await self.d1.update { root in
do {
try ((root.counts as! JSONArray)[0] as! JSONCounter<Int32>).increase(value: Int32(5))
try ((root.counts as! JSONArray)[1] as! JSONCounter<Int32>).increase(value: Int32(3))
} catch {}
((root.counts as! JSONArray)[0] as! JSONCounter<Int32>).increase(value: Int32(5))
((root.counts as! JSONArray)[1] as! JSONCounter<Int32>).increase(value: Int32(3))
}
try await Task.sleep(nanoseconds: 1_000_000_000)

View File

@ -964,4 +964,42 @@ class DocumentTests: XCTestCase {
{"data":["\\"hello\\"","\\n","\\b","\\t","\\f","\\r","\\\\"]}
""")
}
func test_can_handle_counter_overflow() async throws {
let doc = Document(key: "test-doc")
await doc.update { root in
root.age = JSONCounter(value: Int32(2_147_483_647))
(root.age as? JSONCounter<Int32>)?.increase(value: 1)
}
var result = await doc.toSortedJSON()
XCTAssertEqual("{\"age\":-2147483648}", result)
await doc.update { root in
root.age = JSONCounter(value: Int64(9_223_372_036_854_775_807))
(root.age as? JSONCounter<Int64>)?.increase(value: 1)
}
result = await doc.toSortedJSON()
XCTAssertEqual("{\"age\":-9223372036854775808}", result)
}
func test_can_handle_counter_float_value() async throws {
let doc = Document(key: "test-doc")
await doc.update { root in
root.age = JSONCounter(value: Int32(10))
(root.age as? JSONCounter<Int32>)?.increase(value: 3.5)
}
var result = await doc.toSortedJSON()
XCTAssertEqual("{\"age\":13}", result)
await doc.update { root in
root.age = JSONCounter(value: Int64(0))
(root.age as? JSONCounter<Int64>)?.increase(value: -1.5)
}
result = await doc.toSortedJSON()
XCTAssertEqual("{\"age\":-1}", result)
}
}