Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
This is the Swift implementation to perform basic database operations
//
// 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()
}
}
//
// 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