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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<p>PULL REQUEST BY P16058 P16197</p>

<p align=center>
<img width="550" alt="mdblogo" src="https://user-images.githubusercontent.com/15364873/146045747-5dbdce9c-a70a-494b-8fdd-52ba932cdd19.png">
</p>
Expand Down
Binary file added dbdata/None_db/meta_indexes.pkl
Binary file not shown.
Binary file added dbdata/None_db/meta_insert_stack.pkl
Binary file not shown.
Binary file added dbdata/None_db/meta_length.pkl
Binary file not shown.
Binary file added dbdata/None_db/meta_locks.pkl
Binary file not shown.
Binary file added dbdata/None_db/unique_test.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/advisor.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/classroom.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/course.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/department.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/instructor.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/meta_indexes.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/meta_insert_stack.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/meta_length.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/meta_locks.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/prereq.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/section.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/student.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/takes.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/teaches.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/time_slot.pkl
Binary file not shown.
Binary file added dbdata/smdb_db/unique_test.pkl
Binary file not shown.
Binary file added documentation.pdf
Binary file not shown.
85 changes: 81 additions & 4 deletions mdb.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#CHANGES BY P16058 P16197


import os
import re
from pprint import pprint
import sys
import readline
import traceback
import shutil
from tabulate import tabulate
sys.path.append('miniDB')

from database import Database
Expand Down Expand Up @@ -105,6 +109,13 @@ def create_query_plan(query, keywords, action):
dic['primary key'] = arglist[arglist.index('primary')-2]
else:
dic['primary key'] = None
#adding the unique constraing in the way of the primary key
if 'unique' in args:
arglist=args[1:-1].split(' ')
#REMOVING ALL CHARACTERS BEFORE THE COMMA
u=arglist[arglist.index('unique')-2].split(",",1)
dic['unique']=u[1]


if action=='import':
dic = {'import table' if key=='import' else key: val for key, val in dic.items()}
Expand Down Expand Up @@ -290,9 +301,75 @@ def remove_db(db_name):
dic = interpret(line.removeprefix('explain '))
pprint(dic, sort_dicts=False)
else:
dic = interpret(line)
result = execute_dic(dic)
if isinstance(result,Table):
result.show()
#this condition changes the way the queries are executed
l=line.split()
queries=[]
initial_part=[]
conditions=[]
position=0
#first we get the select (condition) part of statement
for i in range(0,len(l)):
initial_part.append(l[i])
if(l[i]=='where'):
position=i
break
#this part collects the parts that correspond to the condition
q=[]
for i in range(position+1,len(l)):
if(l[i]=='or'):
queries.append(q)
q=[]
conditions.append('or')
elif(l[i]=='and'):
queries.append(q)
q=[]
conditions.append('and')
else:
q.append(l[i])
queries.append(q)
#converting the lists into the strings that the parser operates on
for i in range(0,len(queries)):
queries[i]=' '.join(queries[i])
if(queries[i][-1]!=';'):
queries[i]=queries[i]+';'

initial_part=' '.join(initial_part)
for i in range(0,len(queries)):
queries[i]=initial_part+' '+queries[i]
if(len(queries)==1):
#this condition prints only one condition
dic = interpret(line)
result = execute_dic(dic)
if isinstance(result,Table):
result.show()
else:
#multiple conditions
dicts=[]
for q in queries:
dicts.append(interpret(q))
#this line gets the headers in order to print like normal
headers=execute_dic(dicts[0]).get_op_headers()
results=[]
for d in dicts:
results.append(execute_dic(d).get_op_res())
output=results[0]
for i in range(1,len(results)):
if(conditions[i-1]=='or'):
for r in results[i]:
if(r not in output):
output.append(r)
elif(conditions[i-1]=='and'):
positions=[]
for r in results[i]:
for j in range(0,len(output)):
if(r==output[j]):
positions.append(j)
to_be_popped=[]
for j in range(0,len(output)):
if(j not in positions):
to_be_popped.append(j)
for j in range(len(to_be_popped)-1,-1,-1):
output.pop(to_be_popped[j])
print(tabulate(output,headers=headers))
except Exception:
print(traceback.format_exc())
Binary file added miniDB/__pycache__/__init__.cpython-39.pyc
Binary file not shown.
Binary file added miniDB/__pycache__/btree.cpython-39.pyc
Binary file not shown.
Binary file added miniDB/__pycache__/database.cpython-39.pyc
Binary file not shown.
Binary file added miniDB/__pycache__/joins.cpython-39.pyc
Binary file not shown.
Binary file added miniDB/__pycache__/misc.cpython-39.pyc
Binary file not shown.
Binary file added miniDB/__pycache__/table.cpython-39.pyc
Binary file not shown.
13 changes: 10 additions & 3 deletions miniDB/database.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#CHANGES BY P16058 P16197

from __future__ import annotations
import pickle
from time import sleep, localtime, strftime
Expand Down Expand Up @@ -100,8 +102,8 @@ def _update(self):
self._update_meta_length()
self._update_meta_insert_stack()


def create_table(self, name, column_names, column_types, primary_key=None, load=None):
#modifying the constructor in order to use the unique identifier
def create_table(self, name, column_names, column_types, primary_key=None,unique_key=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 @@ -113,7 +115,7 @@ def create_table(self, name, column_names, column_types, primary_key=None, load=
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_key,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
Expand Down Expand Up @@ -370,6 +372,11 @@ def select(self, columns, table_name, condition, distinct=None, order_by=None, \
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)
#the following code block performs the btree search with the unique column
elif self._has_index(table_name) and condition_column==self.tables[table_name].column_names[self.tables[table_name].unique_idx]:
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)
Expand Down
30 changes: 27 additions & 3 deletions miniDB/misc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#CHANGES BY P16058 P16197


import operator

def get_op(op, a, b):
Expand All @@ -8,7 +11,11 @@ def get_op(op, a, b):
'<': operator.lt,
'>=': operator.ge,
'<=': operator.le,
'=': operator.eq}
'=': operator.eq,
#addition to implement NOT operator
'not'.casefold():operator.ne,
#addition to implement BETWEEN operator
'between'.casefold():between_op}

try:
return ops[op](a,b)
Expand All @@ -20,7 +27,11 @@ def split_condition(condition):
'<=': operator.le,
'=': operator.eq,
'>': operator.gt,
'<': operator.lt}
'<': operator.lt,
#addition to implement NOT operator
'not'.casefold():operator.ne,
#addition to implement BETWEEN operator
'between'.casefold():between_op}

for op_key in ops.keys():
splt=condition.split(op_key)
Expand All @@ -34,7 +45,6 @@ def split_condition(condition):

if right.find('"') != -1: # If there are any double quotes in the value, throw. (Notice we've already removed the leading and trailing ones)
raise ValueError(f'Invalid condition: {condition}\nDouble quotation marks are not allowed inside values.')

return left, op_key, right

def reverse_op(op):
Expand All @@ -48,3 +58,17 @@ def reverse_op(op):
'<=' : '>=',
'=' : '='
}.get(op)


#BETWEEN operator function
def between_op(column_value,between_range):
between=between_range.split(',')
res=False
if(type(column_value)==int or type(column_value)==float):
#numerical comparison
res=(float(between[0])<column_value and float(between[1])>column_value)
else:
#string comparison
res=between[0]<column_value and between[1]>column_value
return res

55 changes: 52 additions & 3 deletions miniDB/table.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#CHANGES BY P16058 P16197

from __future__ import annotations
from tabulate import tabulate
import pickle
Expand Down Expand Up @@ -26,7 +28,8 @@ class Table:
- a dictionary that includes the appropriate info (all the attributes in __init__)

'''
def __init__(self, name=None, column_names=None, column_types=None, primary_key=None, load=None):
#adding the unique constraint to the constructor
def __init__(self, name=None, column_names=None, column_types=None, primary_key=None,unique=None,load=None):

if load is not None:
# if load is a dict, replace the object dict with it (replaces the object with the specified one)
Expand Down Expand Up @@ -69,6 +72,11 @@ def __init__(self, name=None, column_names=None, column_types=None, primary_key=

self.pk = primary_key
# self._update()
#we will save in the database the unique constraint like the primary key constraint
if(unique is not None):
self.unique_idx=self.column_names.index(unique)
else:
self.unique_idx=None

# if any of the name, columns_names and column types are none. return an empty table object

Expand Down Expand Up @@ -536,10 +544,48 @@ def show(self, no_of_rows=None, is_locked=False):
# detect the rows that are no tfull of nones (these rows have been deleted)
# if we dont skip these rows, the returning table has empty rows at the deleted positions
non_none_rows = [row for row in self.data if any(row)]
#adding the header unique
if(hasattr(self,'unique_idx')):
if(self.unique_idx is not None and self.unique_idx<len(headers)):
headers[self.unique_idx]=headers[self.unique_idx]+'#UNIQUE#'
# print using tabulate
print(tabulate(non_none_rows[:no_of_rows], headers=headers)+'\n')


def get_op_headers(self, no_of_rows=None, is_locked=False):
#same function as show but returns the headers in order to be printed
output = ""
if is_locked:
output += f"\n## {self._name} (locked) ##\n"
else:
output += f"\n## {self._name} ##\n"
headers = [f'{col} ({tp.__name__})' for col, tp in zip(self.column_names, self.column_types)]
if self.pk_idx is not None:
headers[self.pk_idx] = headers[self.pk_idx]+' #PK#'
#adding the header unique
if(hasattr(self,'unique_idx')):
if(self.unique_idx is not None and self.unique_idx<len(headers)):
headers[self.unique_idx]=headers[self.unique_idx]+'#UNIQUE#'
return headers

def get_op_res(self, no_of_rows=None, is_locked=False):
#same function as show but returns the result rather than printing it
output = ""
if is_locked:
output += f"\n## {self._name} (locked) ##\n"
else:
output += f"\n## {self._name} ##\n"
headers = [f'{col} ({tp.__name__})' for col, tp in zip(self.column_names, self.column_types)]
if self.pk_idx is not None:
headers[self.pk_idx] = headers[self.pk_idx]+' #PK#'
#adding the header unique
if(hasattr(self,'unique_idx')):
if(self.unique_idx is not None and self.unique_idx<len(headers)):
headers[self.unique_idx]=headers[self.unique_idx]+'#UNIQUE#'
non_none_rows = [row for row in self.data if any(row)]
#only line that is different,that is used to combine table results
return non_none_rows

def _parse_condition(self, condition, join=False):
'''
Parse the single string condition and return the value of the column and the operator.
Expand All @@ -561,8 +607,11 @@ def _parse_condition(self, condition, join=False):
if left not in self.column_names:
raise ValueError(f'Condition is not valid (cant find column name)')
coltype = self.column_types[self.column_names.index(left)]

return left, op, coltype(right)
#this if statement exists in order to perform the between operation
if(split_condition(condition)[1]=='between'):
return left,op,str(right)
else:
return left, op, coltype(right)


def _load_from_file(self, filename):
Expand Down