-
Notifications
You must be signed in to change notification settings - Fork 0
/
database.py
130 lines (105 loc) · 4.55 KB
/
database.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
class Database:
def __init__(self):
self.db = {}
self.rollback_queue = []
self.value_count = {}
def validate_args(expected_args=[]):
def real_decorator(func):
def wrapper(self, *args):
if len(args) == len(expected_args):
func(self, *args)
else:
msg = '{} expects {} argument(s)'.format(action, len(expected_args))
if len(expected_args) > 0:
msg += ': {}'.format(expected_args)
print(msg)
return wrapper
return real_decorator
def log_transaction_rollback(func):
def wrapper(self, name, *args):
# If Rollback Queue is empty that means there are no active transactions
# If name is already in the current transaction then do not overwrite the original rollback state
# Otherwise, store the original value for the given name as the rollback state
if len(self.rollback_queue) > 0 and name not in self.rollback_queue[-1].keys():
# Note: if the name does not exist yet in the db that means it is getting added new
# The rollback_queue will store a value of None, which will then be handled in the rollback function
self.rollback_queue[-1][name] = self.db.get(name)
# Once the original value has been stored to be able to rollback, then process the command
func(self, name, *args)
return wrapper
def _decrement_value_count(self, value):
count = self.value_count.get(value, 0) if value else 0
if count > 0:
self.value_count[value] = count - 1
def _increment_value_count(self, value):
self.value_count[value] = self.value_count.get(value, 0) + 1
# This _set function contains the actual set logic but can also be called internally to bypass the decorators
def _set(self, name, value):
# before updating value, decrement count of original value
orig_value = self.db.get(name, None)
self._decrement_value_count(orig_value)
# then check if new value is already in value_count, if so increment, otherwise add with initial value of 1
self._increment_value_count(value)
self.db[name] = value
# This _delete function contains the actual delete logic but can also be called internally to bypass the decorators
def _delete(self, name):
# before deleting record, decrement count of original value
value = self.db.get(name, None)
self._decrement_value_count(value)
# use pop in case name doesn't exist
self.db.pop(name, None)
@validate_args(['name', 'value'])
@log_transaction_rollback
def set(self, name, value):
# This set function is called by the command line actions to run the validation and rollback logic
self._set(name, value)
@validate_args(['name'])
def get(self, name):
print(self.db.get(name) or 'NULL')
@validate_args(['name'])
@log_transaction_rollback
def delete(self, name):
# This delete function is called by the command line actions to run the validation and rollback logic
self._delete(name)
@validate_args(['value'])
def count(self, value):
print(self.value_count.get(value, 0))
@validate_args()
def begin(self):
self.rollback_queue.append({})
@validate_args()
def rollback(self):
try:
rollback_state = self.rollback_queue.pop()
for name, value in rollback_state.items():
# call the private _set or _delete functions to skip the rollback logic since we are in the middle of a rollback itself
if value:
self._set(name, value)
else:
self._delete(name)
except IndexError:
print('TRANSACTION NOT FOUND')
@validate_args()
def commit(self):
self.rollback_queue = []
import sys
db = Database()
COMMANDS = {
'SET': db.set,
'GET': db.get,
'DELETE': db.delete,
'COUNT': db.count,
'BEGIN': db.begin,
'ROLLBACK': db.rollback,
'COMMIT': db.commit,
'END': sys.exit
}
while True:
line = input('>> ').strip().split(' ')
action = line[0].upper()
args = line[1:]
command = COMMANDS.get(action, None)
if command:
command(*args)
else:
print('{} is not a recognized action'.format(action))