Last active
October 25, 2020 21:04
-
-
Save jayesh15111988/8814e7e71894bcc1d451a2176cb47d57 to your computer and use it in GitHub Desktop.
This is the Swift implementation to perform basic database operations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TransactionOperation.swift | |
// NotificationCenterImplementation | |
// | |
// Created by Jayesh Kawli on 10/18/20. | |
// Copyright © 2020 Jayesh Kawli. All rights reserved. | |
// | |
import Foundation | |
// One of the errors that can be throws due to incorrect order of operations | |
enum OperationError: Error { | |
case transactionNotInProgress | |
case keyDoesNotExist | |
} | |
// There are two ways to perform data transaction. They're differentiated the way transaction is | |
// Rolled back to previous state | |
enum RollbackMode { | |
case revertTransactions | |
case duplicateDatabase | |
} | |
// MARK: Protocol DataTransactionable | |
protocol DataTransactionable { | |
// Database store where final data will be stored | |
var data: [String: Int] { get set } | |
// Setting value for key | |
func set(key: String, value: Int) | |
// Get previously stored value. Return nil if key does not exist | |
func get(key: String) -> Int? | |
// Delete previously stored key | |
func delete(key: String) throws -> Int? | |
// Mark the beginning of transaction | |
func begin() | |
// Commit the transaction | |
func commit() throws | |
// Rollback the transaction | |
func rollback() throws | |
} | |
class RevertTransactionRollbackType: DataTransactionable { | |
class Transaction { | |
let transactionIdentifier: Int | |
let operation: () -> Void | |
init(transactionIdentifier: Int, operation: @escaping () -> Void) { | |
self.transactionIdentifier = transactionIdentifier | |
self.operation = operation | |
} | |
} | |
var data: [String: Int] | |
private var transactionIdentifier: Int | |
private var pendingTransactions: [Transaction] | |
init() { | |
self.data = [:] | |
self.transactionIdentifier = -1 | |
self.pendingTransactions = [] | |
} | |
func set(key: String, value: Int) { | |
if transactionIdentifier > -1 { | |
let previousValue = self.data[key] | |
pendingTransactions.append(Transaction(transactionIdentifier: transactionIdentifier, operation: { [weak self] in | |
if let previousValue = previousValue { | |
self?.data[key] = previousValue | |
} else { | |
self?.data.removeValue(forKey: key) | |
} | |
})) | |
} | |
data[key] = value | |
} | |
func get(key: String) -> Int? { | |
return data[key] | |
} | |
func delete(key: String) throws -> Int? { | |
var deletedValue: Int? | |
if let value = data[key] { | |
pendingTransactions.append(Transaction(transactionIdentifier: transactionIdentifier, operation: { [weak self] in | |
self?.data[key] = value | |
})) | |
deletedValue = data.removeValue(forKey: key) | |
} else { | |
throw OperationError.keyDoesNotExist | |
} | |
return deletedValue | |
} | |
func begin() { | |
transactionIdentifier += 1 | |
} | |
func commit() throws { | |
guard transactionIdentifier > -1 else { | |
throw OperationError.transactionNotInProgress | |
} | |
while(pendingTransactions.last?.transactionIdentifier == transactionIdentifier) { | |
pendingTransactions.removeLast() | |
} | |
transactionIdentifier -= 1 | |
} | |
func rollback() throws { | |
guard transactionIdentifier > -1 else { | |
throw OperationError.transactionNotInProgress | |
} | |
while(pendingTransactions.last?.transactionIdentifier == transactionIdentifier) { | |
pendingTransactions.removeLast().operation() | |
} | |
transactionIdentifier -= 1 | |
} | |
} | |
class DuplicateDatabaseRollbackType: DataTransactionable { | |
var data: [String: Int] | |
private var databaseCollection: [[String: Int]] | |
init() { | |
data = [:] | |
self.databaseCollection = [] | |
} | |
func set(key: String, value: Int) { | |
if databaseCollection.isEmpty { | |
data[key] = value | |
} else { | |
var lastDatabase = databaseCollection.removeLast() | |
lastDatabase[key] = value | |
databaseCollection.append(lastDatabase) | |
} | |
} | |
func get(key: String) -> Int? { | |
var value: Int? | |
if databaseCollection.isEmpty { | |
value = data[key] | |
} else { | |
let latestDatabaseSnapshot = databaseCollection.last | |
value = latestDatabaseSnapshot?[key] | |
} | |
return value | |
} | |
func delete(key: String) throws -> Int? { | |
var deletedValue: Int? | |
if databaseCollection.isEmpty { | |
deletedValue = data.removeValue(forKey: key) | |
} else { | |
var lastDatabase = databaseCollection.removeLast() | |
deletedValue = lastDatabase.removeValue(forKey: key) | |
databaseCollection.append(lastDatabase) | |
} | |
if let deletedValue = deletedValue { | |
return deletedValue | |
} | |
throw OperationError.keyDoesNotExist | |
} | |
func begin() { | |
if let duplicatedData = databaseCollection.isEmpty ? data : databaseCollection.last { | |
databaseCollection.append(duplicatedData) | |
} | |
} | |
func commit() throws { | |
guard !databaseCollection.isEmpty else { | |
throw OperationError.transactionNotInProgress | |
} | |
let lastDatabaseState = databaseCollection.removeLast() | |
if databaseCollection.isEmpty { | |
data = lastDatabaseState | |
} else { | |
databaseCollection[databaseCollection.count - 1] = lastDatabaseState | |
} | |
} | |
func rollback() throws { | |
guard !databaseCollection.isEmpty else { | |
throw OperationError.transactionNotInProgress | |
} | |
databaseCollection.removeLast() | |
} | |
} | |
// A utility class to get appropriate transaction instance based on the input mode | |
class TransactionOperation { | |
static func getTransactionOperation(mode: RollbackMode) -> DataTransactionable { | |
return mode == .revertTransactions ? RevertTransactionRollbackType() : DuplicateDatabaseRollbackType() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TransactionOperationTests.swift | |
// TransactionOperationTests | |
// | |
// Created by Jayesh Kawli on 10/18/20. | |
// Copyright © 2020 Jayesh Kawli. All rights reserved. | |
// | |
import XCTest | |
@testable import TransactionOperationImplementation | |
class TransactionOperationTests: XCTestCase { | |
override func setUp() { | |
super.setUp() | |
} | |
func testRevertTransactionOperation() { | |
let transaction = TransactionOperation.getTransactionOperation(mode: .revertTransactions) | |
transaction.set(key: "a", value: 1) | |
XCTAssertEqual(transaction.get(key: "a"), 1) | |
XCTAssertThrowsError(try transaction.commit()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
XCTAssertThrowsError(try transaction.rollback()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
transaction.begin() | |
transaction.set(key: "a", value: 2) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
XCTAssertNoThrow(try transaction.commit()) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
transaction.begin() | |
transaction.set(key: "a", value: 3) | |
transaction.begin() | |
transaction.set(key: "a", value: 4) | |
transaction.begin() | |
transaction.set(key: "a", value: 5) | |
XCTAssertEqual(transaction.get(key: "a"), 5) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 4) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 3) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
XCTAssertThrowsError(try transaction.rollback()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
XCTAssertThrowsError(try transaction.commit()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
} | |
func testDuplicateDatabaseTransactionOperation() { | |
let transaction = TransactionOperation.getTransactionOperation(mode: .duplicateDatabase) | |
transaction.set(key: "a", value: 1) | |
XCTAssertEqual(transaction.get(key: "a"), 1) | |
XCTAssertThrowsError(try transaction.commit()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
XCTAssertThrowsError(try transaction.rollback()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
transaction.begin() | |
transaction.set(key: "a", value: 2) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
XCTAssertNoThrow(try transaction.commit()) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
transaction.begin() | |
transaction.set(key: "a", value: 3) | |
transaction.begin() | |
transaction.set(key: "a", value: 4) | |
transaction.begin() | |
transaction.set(key: "a", value: 5) | |
XCTAssertEqual(transaction.get(key: "a"), 5) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 4) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 3) | |
XCTAssertNoThrow(try transaction.rollback()) | |
XCTAssertEqual(transaction.get(key: "a"), 2) | |
XCTAssertThrowsError(try transaction.rollback()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
XCTAssertThrowsError(try transaction.commit()) { error in | |
XCTAssertEqual(error as? OperationError, OperationError.transactionNotInProgress) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment