Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions mdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ def create_query_plan(query, keywords, action):

This can and will be used recursively
'''

dic = {val: None for val in keywords if val!=';'}

ql = [val for val in query.split(' ') if val !='']

kw_in_query = []
Expand Down Expand Up @@ -105,6 +103,17 @@ def create_query_plan(query, keywords, action):
dic['primary key'] = arglist[arglist.index('primary')-2]
else:
dic['primary key'] = None

# parse unique arg
arg_no_unique = args.replace('unique', '')[1:-1]
arglist = [val.strip().split(' ') for val in arg_no_unique.split(',')]
dic['column_names'] = ','.join([val[0] for val in arglist])
dic['column_types'] = ','.join([val[1] for val in arglist])
if 'unique' in args:
arglist = args[1:-1].split(' ')
dic['unique'] = arglist[arglist.index('unique')-2]
else:
dic['unique'] = None

if action=='import':
dic = {'import table' if key=='import' else key: val for key, val in dic.items()}
Expand All @@ -121,6 +130,7 @@ def create_query_plan(query, keywords, action):
else:
dic['force'] = False

# print("create query plan - dic:",dic)
return dic


Expand Down
56 changes: 38 additions & 18 deletions miniDB/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from joins import Inlj, Smj
from btree import Btree
from misc import split_condition
from misc import split_condition, not_op
from table import Table


Expand Down Expand Up @@ -54,7 +54,7 @@ def __init__(self, name, load=True, verbose = True):
self.create_table('meta_length', 'table_name,no_of_rows', 'str,int')
self.create_table('meta_locks', 'table_name,pid,mode', 'str,int,str')
self.create_table('meta_insert_stack', 'table_name,indexes', 'str,list')
self.create_table('meta_indexes', 'table_name,index_name', 'str,str')
self.create_table('meta_indexes', 'table_name,index_name,column_name', 'str,str,str')
self.save_database()

def save_database(self):
Expand Down Expand Up @@ -86,6 +86,7 @@ def load_database(self):
continue
f = open(path+'/'+file, 'rb')
tmp_dict = pickle.load(f)
# print("Load - tmp_dict: ",tmp_dict)
f.close()
name = f'{file.split(".")[0]}'
self.tables.update({name: tmp_dict})
Expand All @@ -101,7 +102,7 @@ def _update(self):
self._update_meta_insert_stack()


def create_table(self, name, column_names, column_types, primary_key=None, load=None):
def create_table(self, name, column_names, column_types, primary_key=None, unique=None, load=None):
'''
This method create a new table. This table is saved and can be accessed via db_object.tables['table_name'] or db_object.table_name

Expand All @@ -110,14 +111,17 @@ def create_table(self, name, column_names, column_types, primary_key=None, load=
column_names: list. Names of columns.
column_types: list. Types of columns.
primary_key: string. The primary key (if it exists).
unique: string
load: boolean. Defines table object parameters as the name of the table and the column names.
'''
# print('here -> ', column_names.split(','))
self.tables.update({name: Table(name=name, column_names=column_names.split(','), column_types=column_types.split(','), primary_key=primary_key, load=load)})
self.tables.update({name: Table(name=name, column_names=column_names.split(','),
column_types=column_types.split(','), primary_key=primary_key, unique=unique, load=load)})
# self._name = Table(name=name, column_names=column_names, column_types=column_types, load=load)
# check that new dynamic var doesnt exist already
# self.no_of_tables += 1
self._update()

self.save_database()
# (self.tables[name])
if self.verbose:
Expand Down Expand Up @@ -319,7 +323,14 @@ def delete_from(self, table_name, condition):
Operatores supported: (<,<=,==,>=,>)
'''
self.load_database()


# conditions = []
# if 'and' in condition:
# for cond in condition.split('and'):
# conditions.append(cond)



lock_ownership = self.lock_table(table_name, mode='x')
deleted = self.tables[table_name]._delete_where(condition)
if lock_ownership:
Expand Down Expand Up @@ -352,26 +363,30 @@ def select(self, columns, table_name, condition, distinct=None, order_by=None, \
distinct: boolean. If True, the resulting table will contain only unique rows.
'''

# print(table_name)
self.load_database()
if isinstance(table_name,Table):
if isinstance(table_name, Table):
return table_name._select_where(columns, condition, distinct, order_by, desc, limit)

if condition is not None:
condition_column = split_condition(condition)[0]
if 'and' not in condition and 'or' not in condition:
condition_column = split_condition(condition)[0]
else:
condition_column = ''

# self.lock_table(table_name, mode='x')


if self.is_locked(table_name):
return
if self._has_index(table_name) and condition_column==self.tables[table_name].column_names[self.tables[table_name].pk_idx]:
# if (condition_column==self.tables[table_name].column_names[self.tables[table_name].pk_idx] or \
# condition_column in self.tables[table_name].unique_column_names):
# #condition_column in (self.tables[table_name].column_names[self.tables[table_name].unique] for unique in self.unique_columns)):
if (condition_column in self.tables['meta_indexes'].column_name):
index_name = self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name('index_name')[0]
bt = self._load_idx(index_name)
table = self.tables[table_name]._select_where_with_btree(columns, bt, condition, distinct, order_by, desc, limit)
else:
table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit)

# self.unlock_table(table_name)
if save_as is not None:
table._name = save_as
Expand Down Expand Up @@ -650,7 +665,7 @@ def _update_meta_insert_stack_for_tb(self, table_name, new_stack):


# indexes
def create_index(self, index_name, table_name, index_type='btree'):
def create_index(self, index_name, table_name, column_name, index_type='btree'):
'''
Creates an index on a specified table with a given name.
Important: An index can only be created on a primary key (the user does not specify the column).
Expand All @@ -659,14 +674,16 @@ def create_index(self, index_name, table_name, index_type='btree'):
table_name: string. Table name (must be part of database).
index_name: string. Name of the created index.
'''
if self.tables[table_name].pk_idx is None: # if no primary key, no index
raise Exception('Cannot create index. Table has no primary key.')
if self.tables[table_name].pk_idx is None and self.tables[table_name].unique_columns is None: # if no primary key, no index
raise Exception('Cannot create index. Table has no primary key nor unique.')


if index_name not in self.tables['meta_indexes'].column_by_name('index_name'):
# currently only btree is supported. This can be changed by adding another if.
if index_type=='btree':
logging.info('Creating Btree index.')
# insert a record with the name of the index and the table on which it's created to the meta_indexes table
self.tables['meta_indexes']._insert([table_name, index_name])
self.tables['meta_indexes']._insert([table_name, index_name, column_name])
# crate the actual index
self._construct_index(table_name, index_name)
self.save_database()
Expand All @@ -684,17 +701,20 @@ def _construct_index(self, table_name, index_name):
bt = Btree(3) # 3 is arbitrary

# for each record in the primary key of the table, insert its value and index to the btree
# loop this process for each pk OR unique column - as user wish
for idx, key in enumerate(self.tables[table_name].column_by_name(self.tables[table_name].pk)):
if key is None:
continue
bt.insert(key, idx)
for uidx, ukey in enumerate(self.tables[table_name].column_by_name(self.tables[table_name].unique) for unique in self.tables[table_name].unique_columns):
if ukey is None:
continue
bt.insert(key, idx)
# save the btree
self._save_index(index_name, bt)


def _has_index(self, table_name):
'''
Check whether the specified table's primary key column is indexed.
Check whether the specified table is indexed.

Args:
table_name: string. Table name (must be part of database).
Expand Down
35 changes: 28 additions & 7 deletions miniDB/misc.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
import operator


def get_op(op, a, b):
'''
Get op as a function of a and b by using a symbol
'''
ops = {'>': operator.gt,
'<': operator.lt,
'>=': operator.ge,
'<=': operator.le,
'=': operator.eq}
'<': operator.lt,
'>=': operator.ge,
'<=': operator.le,
'=': operator.eq,
'<>': operator.ne}

try:
return ops[op](a,b)
return ops[op](a, b)
except TypeError: # if a or b is None (deleted record), python3 raises typerror
return False


def split_condition(condition):
ops = {'>=': operator.ge,
'<=': operator.le,
'<>': operator.ne,
'=': operator.eq,
'>': operator.gt,
'<': operator.lt}

for op_key in ops.keys():
splt=condition.split(op_key)
if len(splt)>1:
splt = condition.split(op_key)
if len(splt) > 1:
left, right = splt[0].strip(), splt[1].strip()

if right[0] == '"' == right[-1]: # If the value has leading and trailing quotes, remove them.
Expand All @@ -48,3 +52,20 @@ def reverse_op(op):
'<=' : '>=',
'=' : '='
}.get(op)


def not_op(op):
"""
Reverse operator for 'NOT'
"""
return {
'>': '<',
'<': ' >',
'>=': '<',
'<=': ' >',
'=': '<>',
'<>': '='}.get(op)




Loading