From c87206c54c874dcd6b0819ee59fabd8aa93de2b0 Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:52:15 +0200 Subject: [PATCH 1/7] Join functions, select with btree and hash, and more... --- miniDB/table.py | 709 ++++++++++++++++++++---------------------------- 1 file changed, 287 insertions(+), 422 deletions(-) diff --git a/miniDB/table.py b/miniDB/table.py index f5c7d937..7a3f81f0 100644 --- a/miniDB/table.py +++ b/miniDB/table.py @@ -3,577 +3,442 @@ import pickle import os import sys - sys.path.append(f'{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/miniDB') - -from misc import get_op, split_condition +from misc import get_op, split_condition , reverse_op class Table: - ''' - Table object represents a table inside a database - - A Table object can be created either by assigning: - - a table name (string) - - column names (list of strings) - - column types (list of functions like str/int etc) - - primary (name of the primary key column) - - OR - - - by assigning a value to the variable called load. This value can be: - - a path to a Table file saved using the save function - - 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): - if load is not None: - # if load is a dict, replace the object dict with it (replaces the object with the specified one) + # if load is a dict, replace the object dict with it. if isinstance(load, dict): self.__dict__.update(load) # self._update() # if load is str, load from a file elif isinstance(load, str): - self._load_from_file(load) + self.fortose_apo_arxeio(load) - # if name, columns_names and column types are not none + elif (name is not None) and (column_names is not None) and (column_types is not None): - - self._name = name - - if len(column_names)!=len(column_types): - raise ValueError('Need same number of column names and types.') - - self.column_names = column_names - - self.columns = [] - - for col in self.column_names: - if col not in self.__dir__(): - # this is used in order to be able to call a column using its name as an attribute. - # example: instead of table.columns['column_name'], we do table.column_name - setattr(self, col, []) - self.columns.append([]) - else: - raise Exception(f'"{col}" attribute already exists in "{self.__class__.__name__} "class.') - - self.column_types = [eval(ct) if not isinstance(ct, type) else ct for ct in column_types] - self.data = [] # data is a list of lists, a list of rows that is. - - # if primary key is set, keep its index as an attribute - if primary_key is not None: - self.pk_idx = self.column_names.index(primary_key) - else: - self.pk_idx = None - - self.pk = primary_key - # self._update() + self._name = name + if len(column_names)!=len(column_types): + raise ValueError('Apaiteitai idios arithmos grammon kai stilon.') + self.column_names = column_names + self.columns = [] + for col in self.column_names: + if col not in self.__dir__(): + # this is used in order to be able to call a column using its name as an attribute. + setattr(self, col, []) + self.columns.append([]) + else: + raise Exception(f'"{col}" attribute already exists in "{self.__class__.__name__} "class.') + self.column_types = [eval(ct) if not isinstance(ct, type) else ct for ct in column_types] + self.data = [] # data is a list of lists, a list of rows that is. + # if primary key is set, keep its index as an attribute + if primary_key is not None: + self.pk_idx = self.column_names.index(primary_key) + else: + self.pk_idx = None + self.pk = primary_key + # if any of the name, columns_names and column types are none. return an empty table object - def column_by_name(self, column_name): - return [row[self.column_names.index(column_name)] for row in self.data] + def column_by_name(self, onoma_stilis): + return [row[self.column_names.index(onoma_stilis)] for row in self.data] - def _update(self): - ''' - Update all the available columns with the appended rows. - ''' + def _update(self): #Updates the columns with the appended rows. self.columns = [[row[i] for row in self.data] for i in range(len(self.column_names))] for ind, col in enumerate(self.column_names): setattr(self, col, self.columns[ind]) def _cast_column(self, column_name, cast_type): ''' - Cast all values of a column using a specified type. - Args: column_name: string. The column that will be casted. cast_type: type. Cast type (do not encapsulate in quotes). ''' # get the column from its name - column_idx = self.column_names.index(column_name) + index_stilis = self.column_names.index(column_name) # for every column's value in each row, replace it with itself but casted as the specified type for i in range(len(self.data)): - self.data[i][column_idx] = cast_type(self.data[i][column_idx]) + self.data[i][index_stilis] = cast_type(self.data[i][index_stilis]) # change the type of the column - self.column_types[column_idx] = cast_type - # self._update() - - - def _insert(self, row, insert_stack=[]): - ''' - Insert row to table. - - Args: - row: list. A list of values to be inserted (will be casted to a predifined type automatically). - insert_stack: list. The insert stack (empty by default). - ''' - if len(row)!=len(self.column_names): - raise ValueError(f'ERROR -> Cannot insert {len(row)} values. Only {len(self.column_names)} columns exist') + self.column_types[index_stilis] = cast_type + - for i in range(len(row)): + def _insert(self, seira, stoiva=[]): + if len(seira)!=len(self.column_names): + raise ValueError(f'Den ginetai na eisaxthoun {len(seira)} times. Yparxoun {len(self.column_names)} sthles') + for i in range(len(seira)): # for each value, cast and replace it in row. try: - row[i] = self.column_types[i](row[i]) + seira[i] = self.column_types[i](seira[i]) except ValueError: - if row[i] != 'NULL': - raise ValueError(f'ERROR -> Value {row[i]} of type {type(row[i])} is not of type {self.column_types[i]}.') - except TypeError as exc: - if row[i] != None: - print(exc) - + if seira[i] != 'NULL': + raise ValueError(f'I timi {seira[i]} typou {type(seira[i])} den einai ston sosto typo {self.column_types[i]}.') + except TypeError as eksairesi: + if seira[i] != None: + print(eksairesi) # if value is to be appended to the primary_key column, check that it doesnt alrady exist (no duplicate primary keys) - if i==self.pk_idx and row[i] in self.column_by_name(self.pk): - raise ValueError(f'## ERROR -> Value {row[i]} already exists in primary key column.') - elif i==self.pk_idx and row[i] is None: - raise ValueError(f'ERROR -> The value of the primary key cannot be None.') + if i==self.pk_idx and seira[i] in self.column_by_name(self.pk): + raise ValueError(f'## {seira[i]} yparxei hdh sthn sthlh tou proteuontos kleidiou.') + elif i==self.pk_idx and seira[i] is None: + raise ValueError(f'Prepei na exei timi to proteuon kleidi.') # if insert_stack is not empty, append to its last index - if insert_stack != []: - self.data[insert_stack[-1]] = row - else: # else append to the end - self.data.append(row) - # self._update() - - def _update_rows(self, set_value, set_column, condition): - ''' - Update where Condition is met. - - Args: - set_value: string. The provided set value. - set_column: string. The column to be altered. - condition: string. A condition using the following format: - 'column[<,<=,=,>=,>]value' or - 'value[<,<=,=,>=,>]column'. - - Operatores supported: (<,<=,=,>=,>) - ''' - # parse the condition - column_name, operator, value = self._parse_condition(condition) + if stoiva != []: + self.data[stoiva[-1]] = seira + else: + self.data.append(seira) + + def tropopoisi_seiras(self, orise_timi, orise_stili, synthiki): + column_name, telestis, value = self.analysi_synthikis(synthiki) #Analysi tis synthikis(parsing) # get the condition and the set column column = self.column_by_name(column_name) - set_column_idx = self.column_names.index(set_column) - - # set_columns_indx = [self.column_names.index(set_column_name) for set_column_name in set_column_names] + set_column_index = self.column_names.index(orise_stili) # for each value in column, if condition, replace it with set_value for row_ind, column_value in enumerate(column): - if get_op(operator, column_value, value): - self.data[row_ind][set_column_idx] = set_value - - # self._update() - # print(f"Updated {len(indexes_to_del)} rows") + if get_op(telestis, column_value, value): + self.data[row_ind][set_column_index] = orise_timi - - def _delete_where(self, condition): + + def diagrafi_opou(self, synthiki): ''' - Deletes rows where condition is met. - Important: delete replaces the rows to be deleted with rows filled with Nones. These rows are then appended to the insert_stack. - Args: condition: string. A condition using the following format: 'column[<,<=,==,>=,>]value' or 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) + Operators supported: (<,<=,==,>=,>) ''' - column_name, operator, value = self._parse_condition(condition) - - indexes_to_del = [] - - column = self.column_by_name(column_name) - for index, row_value in enumerate(column): - if get_op(operator, row_value, value): - indexes_to_del.append(index) - - # we pop from highest to lowest index in order to avoid removing the wrong item - # since we dont delete, we dont have to to pop in that order, but since delete is used - # to delete from meta tables too, we still implement it. - - for index in sorted(indexes_to_del, reverse=True): + column_name, telestis, value = self.analysi_synthikis(synthiki) + indexes_pros_diagrafi = [] + stili = self.column_by_name(column_name) + for index, row_value in enumerate(stili): + if get_op(telestis, row_value, value): + indexes_pros_diagrafi.append(index) + + for index in sorted(indexes_pros_diagrafi, reverse=True): if self._name[:4] != 'meta': # if the table is not a metatable, replace the row with a row of nones self.data[index] = [None for _ in range(len(self.column_names))] else: self.data.pop(index) + return indexes_pros_diagrafi - # self._update() - # we have to return the deleted indexes, since they will be appended to the insert_stack - return indexes_to_del - - def _select_where(self, return_columns, condition=None, distinct=False, order_by=None, desc=True, limit=None): + def _select_where(self, emfanisi_stilwn, condition=None, distinct=False, order_by=None, desc=True, orion=None): #Returns specific rows and columns where a condition is met ''' - Select and return a table containing specified columns and rows where condition is met. - Args: return_columns: list. The columns to be returned. condition: string. A condition using the following format: 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) + 'value[<,<=,==,>=,>]column' or + 'Between , NOT, AND , OR'. distinct: boolean. If True, the resulting table will contain only unique rows (False by default). order_by: string. A column name that signals that the resulting table should be ordered based on it (no order if None). - desc: boolean. If True, order_by will return results in descending order (False by default). - limit: int. An integer that defines the number of rows that will be returned (all rows if None). + desc: boolean. If True, order_by will return results in descending order. + orion: int. An integer that defines the number of rows that will be returned (all rows if None). ''' - - # if * return all columns, else find the column indexes for the columns specified - if return_columns == '*': + if emfanisi_stilwn == '*': #Returns all columns return_cols = [i for i in range(len(self.column_names))] else: - return_cols = [self.column_names.index(col.strip()) for col in return_columns.split(',')] - - # if condition is None, return all rows - # if not, return the rows with values where condition is met for value + return_cols = [self.column_names.index(col.strip()) for col in emfanisi_stilwn.split(',')] if condition is not None: - column_name, operator, value = self._parse_condition(condition) - column = self.column_by_name(column_name) - rows = [ind for ind, x in enumerate(column) if get_op(operator, x, value)] - else: + if "BETWEEN" in condition.split() or "between" in condition.split(): + split_con = condition.split() + if (split_con[3] != 'and'): + print('Prepei na exei and metaksy ton arithmon.') + exit() + else: + aristeri_timi = split_con[2] + dexia_timi = split_con[4] + column_name = split_con[0] + column = self.column_by_name(column_name) + rows = [] + if (aristeri_timi.isdigit() and dexia_timi.isdigit()): + for i, j in enumerate(column): + if int(j) >= int(aristeri_timi) and int(j) <= int(dexia_timi): + rows.append(i) #if between applies + else: + print('Den sygkrinontai ta alpharithmitika') #No value other than number accepted + exit() + elif "OR" in condition.split() or "or" in condition.split(): #OR operator + condition_list = condition.split("OR") + condition_list = condition_list[0].split("or") + row_lists = [] + for cond in condition_list: # run every condition seperatly + column_name, telestis, value = self.analysi_synthikis(cond) + column = self.column_by_name(column_name) + row_lists.append([ind for ind, x in enumerate(column) if get_op(telestis, x, value)]) + rows = [] + for l in row_lists: # move all rows into one list + for row in l: + if not(row in rows): + rows.append(row) + elif "AND" in condition.split() or "and" in condition.split(): #AND operator + condition_list = condition.split("AND") + condition_list = condition_list[0].split("and") + row_lists = [] + for cond in condition_list: # run every condition seperatly + column_name, telestis, value = self.analysi_synthikis(cond) + column = self.column_by_name(column_name) + row_lists.append([ind for ind, x in enumerate(column) if get_op(telestis, x, value)]) + rows = set(row_lists[0]).intersection(*row_lists) # get the intersection of the seperate conditions + elif "NOT" in condition.split() or "not" in condition.split(): #NOT operator + condition_list = condition.split("NOT") + condition_list = condition_list[0].split("not") + column_name, telestis, value = self.analysi_synthikis(condition_list[1]) + column = self.column_by_name(column_name) + operator2=reverse_op(telestis) + rows = [ind for ind, x in enumerate(column) if get_op(operator2, x, value)] + else: + column_name, telestis, value = self.analysi_synthikis(condition) + column = self.column_by_name(column_name) + rows = [ind for ind, x in enumerate(column) if get_op(telestis, x, value)] + else: #If condition is None rows = [i for i in range(len(self.data))] - # copy the old dict, but only the rows and columns of data with index in rows/columns (the indexes that we want returned) + # copy the old dict, but only the rows and columns of data with index dict = {(key):([[self.data[i][j] for j in return_cols] for i in rows] if key=="data" else value) for key,value in self.__dict__.items()} - - # we need to set the new column names/types and no of columns, since we might - # only return some columns dict['column_names'] = [self.column_names[i] for i in return_cols] dict['column_types'] = [self.column_types[i] for i in return_cols] - - s_table = Table(load=dict) - - s_table.data = list(set(map(lambda x: tuple(x), s_table.data))) if distinct else s_table.data - + pros_emfanisi = Table(load=dict) + pros_emfanisi.data = list(set(map(lambda x: tuple(x), pros_emfanisi.data))) if distinct else pros_emfanisi.data if order_by: - s_table.order_by(order_by, desc) - - # if isinstance(limit, str): - # try: - # k = int(limit) - # except ValueError: - # raise Exception("The value following 'top' in the query should be a number.") - - # # Remove from the table's data all the None-filled rows, as they are not shown by default - # # Then, show the first k rows - # s_table.data.remove(len(s_table.column_names) * [None]) - # s_table.data = s_table.data[:k] - if isinstance(limit,str): - s_table.data = [row for row in s_table.data if any(row)][:int(limit)] - - return s_table + pros_emfanisi.order_by(order_by, desc) + if isinstance(orion,str): + pros_emfanisi.data = [row for row in pros_emfanisi.data if any(row)][:int(orion)] + return pros_emfanisi - def _select_where_with_btree(self, return_columns, bt, condition, distinct=False, order_by=None, desc=True, limit=None): - - # if * return all columns, else find the column indexes for the columns specified + def epilogi_alla_me_btree(self, return_columns, b_dendro, synthiki, distinct=False, order_by=None, desc=True, orion=None):# Selecting with Btree if return_columns == '*': - return_cols = [i for i in range(len(self.column_names))] + emfanisi_stilwn = [i for i in range(len(self.column_names))] else: - return_cols = [self.column_names.index(colname) for colname in return_columns] - - - column_name, operator, value = self._parse_condition(condition) - - # if the column in condition is not a primary key, abort the select - if column_name != self.column_names[self.pk_idx]: - print('Column is not PK. Aborting') - - # here we run the same select twice, sequentially and using the btree. - # we then check the results match and compare performance (number of operation) + emfanisi_stilwn = [self.column_names.index(colname) for colname in return_columns] + column_name, telestis, value = self.analysi_synthikis(synthiki) column = self.column_by_name(column_name) # sequential rows1 = [] - opsseq = 0 + operations_sequential = 0 for ind, x in enumerate(column): - opsseq+=1 - if get_op(operator, x, value): + operations_sequential+=1 + if get_op(telestis, x, value): rows1.append(ind) - # btree find - rows = bt.find(operator, value) - + rows = b_dendro.find(telestis, value) try: - k = int(limit) + arithmos_oriou = int(orion) except TypeError: - k = None - # same as simple select from now on - rows = rows[:k] - # TODO: this needs to be dumbed down - dict = {(key):([[self.data[i][j] for j in return_cols] for i in rows] if key=="data" else value) for key,value in self.__dict__.items()} - - dict['column_names'] = [self.column_names[i] for i in return_cols] - dict['column_types'] = [self.column_types[i] for i in return_cols] - - s_table = Table(load=dict) - - s_table.data = list(set(map(lambda x: tuple(x), s_table.data))) if distinct else s_table.data - + arithmos_oriou = None + rows = rows[:arithmos_oriou] + dict = {(key):([[self.data[i][j] for j in emfanisi_stilwn] for i in rows] if key=="data" else value) for key,value in self.__dict__.items()} + dict['column_names'] = [self.column_names[i] for i in emfanisi_stilwn] + dict['column_types'] = [self.column_types[i] for i in emfanisi_stilwn] + pros_emfanisi = Table(load=dict) + pros_emfanisi.data = list(set(map(lambda x: tuple(x), pros_emfanisi.data))) if distinct else pros_emfanisi.data if order_by: - s_table.order_by(order_by, desc) + pros_emfanisi.order_by(order_by, desc) + if isinstance(orion,str): + pros_emfanisi.data = [row for row in pros_emfanisi.data if row is not None][:int(orion)] + return pros_emfanisi - if isinstance(limit,str): - s_table.data = [row for row in s_table.data if row is not None][:int(limit)] - return s_table - - def order_by(self, column_name, desc=True): - ''' - Order table based on column. + def epilogi_alla_me_hash(self, return_columns, hm, synthiki, distinct=False, order_by=None, desc=True, orion=None): #Selecting with hash + if return_columns == '*': + emfanisi_stilwn = [i for i in range(len(self.column_names))] + else: + emfanisi_stilwn = [self.column_names.index(colname) for colname in return_columns] + column_name, telestis, value = self.analysi_synthikis(synthiki) + rows = [] + if (telestis == '<' or telestis == '>'): + column = self.column_by_name(column_name) + # sequential + operations_sequential = 0 + for ind, x in enumerate(column): + operations_sequential+=1 + if get_op(telestis, x, value): + rows.append(ind) + else: + # hash value + athroisma_hash = 0 + for letter in value: + athroisma_hash += ord(letter) + hash_index = athroisma_hash % int(hm[0][0][0]) + # find in dictionary with hash_index + for item in hm[hash_index]: + if hm[hash_index][item][0] == hm[0][0][0]: + continue + if hm[hash_index][item][1] == value: + rows.append(hm[hash_index][item][0]) + + try: + arithmos_oriou = int(orion) + except TypeError: + arithmos_oriou = None + rows = rows[:arithmos_oriou] + dict = {(key):([[self.data[i][j] for j in emfanisi_stilwn] for i in rows] if key=="data" else value) for key,value in self.__dict__.items()} + dict['column_names'] = [self.column_names[i] for i in emfanisi_stilwn] + dict['column_types'] = [self.column_types[i] for i in emfanisi_stilwn] + pros_emfanisi = Table(load=dict) + pros_emfanisi.data = list(set(map(lambda x: tuple(x), pros_emfanisi.data))) if distinct else pros_emfanisi.data + if order_by: + pros_emfanisi.order_by(order_by, desc) + if isinstance(orion,str): + pros_emfanisi.data = [row for row in pros_emfanisi.data if row is not None][:int(orion)] + return pros_emfanisi - Args: - column_name: string. Name of column. - desc: boolean. If True, order_by will return results in descending order (False by default). - ''' - column = [val if val is not None else 0 for val in self.column_by_name(column_name)] - idx = sorted(range(len(column)), key=lambda k: column[k], reverse=desc) - # print(idx) + + def order_by(self, onoma_stilis, desc=True): + stili = self.column_by_name(onoma_stilis) + idx = sorted(range(len(stili)), key=lambda k: stili[k], reverse=desc) self.data = [self.data[i] for i in idx] - # self._update() - - - def _general_join_processing(self, table_right:Table, condition, join_type): - ''' - Performs the processes all the join operations need (regardless of type) so that there is no code repetition. - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operators supported: (<,<=,==,>=,>) - ''' + + def _general_join_processing(self, table_right:Table, synthiki, join_type): # get columns and operator - column_name_left, operator, column_name_right = self._parse_condition(condition, join=True) + column_name_left, telestis, column_name_right = self.analysi_synthikis(synthiki, join=True) # try to find both columns, if you fail raise error - - if(operator != '=' and join_type in ['left','right','full']): + if(telestis != '=' and join_type in ['left','right','full']): class CustomFailException(Exception): pass - raise CustomFailException('Outer Joins can only be used if the condition operator is "=".\n') + raise CustomFailException('Den ginetai outer join xoris "=". ') try: - column_index_left = self.column_names.index(column_name_left) + index_stilis_aristerou_pinaka = self.column_names.index(column_name_left) except: - raise Exception(f'Column "{column_name_left}" dont exist in left table. Valid columns: {self.column_names}.') + raise Exception(f'H sthlh "{column_name_left}" den yparxei ston aristero pinaka.') try: - column_index_right = table_right.column_names.index(column_name_right) + index_stilis_dexiou_pinaka = table_right.column_names.index(column_name_right) except: - raise Exception(f'Column "{column_name_right}" dont exist in right table. Valid columns: {table_right.column_names}.') - + raise Exception(f'H sthlh "{column_name_right}" den yparxei ston dexio pinaka.') # get the column names of both tables with the table name in front - # ex. for left -> name becomes left_table_name_name etc - left_names = [f'{self._name}.{colname}' if self._name!='' else colname for colname in self.column_names] - right_names = [f'{table_right._name}.{colname}' if table_right._name!='' else colname for colname in table_right.column_names] - - # define the new tables name, its column names and types - join_table_name = '' - join_table_colnames = left_names+right_names - join_table_coltypes = self.column_types+table_right.column_types - join_table = Table(name=join_table_name, column_names=join_table_colnames, column_types= join_table_coltypes) - - return join_table, column_index_left, column_index_right, operator - - - def _inner_join(self, table_right: Table, condition): - ''' - Join table (left) with a supplied table (right) where condition is met. - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operators supported: (<,<=,==,>=,>) - ''' - join_table, column_index_left, column_index_right, operator = self._general_join_processing(table_right, condition, 'inner') - + onomata_stilon_aristerou_pinaka = [f'{self._name}.{colname}' if self._name!='' else colname for colname in self.column_names] + onomata_stilon_dexiou_pinaka = [f'{table_right._name}.{colname}' if table_right._name!='' else colname for colname in table_right.column_names] + # define the new table + onoma_enomenou_pinaka = '' + stiles_enomenou_pinaka = onomata_stilon_aristerou_pinaka+onomata_stilon_dexiou_pinaka + typoi_stilon_enomenou_pinaka = self.column_types+table_right.column_types + join_table = Table(name=onoma_enomenou_pinaka, column_names=stiles_enomenou_pinaka, column_types= typoi_stilon_enomenou_pinaka) + return join_table, index_stilis_aristerou_pinaka, index_stilis_dexiou_pinaka, telestis + + + def _inner_join(self, table_right: Table, condition): #Inner join depending on the condition. + join_table, index_stilis_aristerou_pinaka, index_stilis_dexiou_pinaka, telestis = self._general_join_processing(table_right, condition, 'inner') # count the number of operations (<,> etc) - no_of_ops = 0 - # this code is dumb on purpose... it needs to illustrate the underline technique - # for each value in left column and right column, if condition, append the corresponding row to the new table + metritis_teleston = 0 for row_left in self.data: - left_value = row_left[column_index_left] + aristeri_timi = row_left[index_stilis_aristerou_pinakat] for row_right in table_right.data: - right_value = row_right[column_index_right] - if(left_value is None and right_value is None): + dexia_timi = row_right[index_stilis_dexiou_pinaka] + if(aristeri_timi is None and dexia_timi is None): continue - no_of_ops+=1 - if get_op(operator, left_value, right_value): #EQ_OP + metritis_teleston+=1 #Add the row to the table + if get_op(telestis, aristeri_timi, dexia_timi): join_table._insert(row_left+row_right) - return join_table - def _left_join(self, table_right: Table, condition): - ''' - Perform a left join on the table with the supplied table (right). - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operators supported: (<,<=,==,>=,>) - ''' - join_table, column_index_left, column_index_right, operator = self._general_join_processing(table_right, condition, 'left') - - right_column = table_right.column_by_name(table_right.column_names[column_index_right]) - right_table_row_length = len(table_right.column_names) - + def enosi_apo_aristera(self, table_right: Table, condition): #Performs left join of the table. + join_table, index_stilis_aristerou_pinaka, index_stilis_dexiou_pinaka, operator = self._general_join_processing(table_right, condition, 'left') + dexia_stili = table_right.column_by_name(table_right.column_names[index_stilis_dexiou_pinaka]) + mhkos_grammhs_dexiou_pinaka = len(table_right.column_names) for row_left in self.data: - left_value = row_left[column_index_left] - if left_value is None: + aristeri_timi = row_left[index_stilis_aristerou_pinaka] + if aristeri_timi is None: continue - elif left_value not in right_column: - join_table._insert(row_left + right_table_row_length*["NULL"]) + elif aristeri_timi not in dexia_stili: + join_table._insert(row_left + mhkos_grammhs_dexiou_pinaka*["NULL"]) else: for row_right in table_right.data: - right_value = row_right[column_index_right] - if left_value == right_value: + dexia_timi = row_right[index_stilis_dexiou_pinaka] + if aristeri_timi == dexia_timi: join_table._insert(row_left + row_right) - return join_table - def _right_join(self, table_right: Table, condition): - ''' - Perform a right join on the table with the supplied table (right). - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operators supported: (<,<=,==,>=,>) - ''' - join_table, column_index_left, column_index_right, operator = self._general_join_processing(table_right, condition, 'right') - - left_column = self.column_by_name(self.column_names[column_index_left]) - left_table_row_length = len(self.column_names) - + def enosi_apo_dexia(self, table_right: Table, condition): #Performs right join of the table. + join_table, index_stilis_aristerou_pinaka, index_stilis_dexiou_pinaka, operator = self._general_join_processing(table_right, condition, 'right') + aristeri_stili = self.column_by_name(self.column_names[index_stilis_aristerou_pinaka]) + mhkos_grammhs_aristerou_pinaka = len(self.column_names) for row_right in table_right.data: - right_value = row_right[column_index_right] - if right_value is None: + dexia_timi = row_right[index_stilis_dexiou_pinaka] + if dexia_timi is None: continue - elif right_value not in left_column: - join_table._insert(left_table_row_length*["NULL"] + row_right) + elif dexia_timi not in aristeri_stili: + join_table._insert(mhkos_grammhs_aristerou_pinaka*["NULL"] + row_right) else: for row_left in self.data: - left_value = row_left[column_index_left] - if left_value == right_value: + aristeri_timi = row_left[index_stilis_aristerou_pinaka] + if aristeri_timi == dexia_timi: join_table._insert(row_left + row_right) - return join_table - def _full_join(self, table_right: Table, condition): - ''' - Perform a full join on the table with the supplied table (right). - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operators supported: (<,<=,==,>=,>) - ''' - join_table, column_index_left, column_index_right, operator = self._general_join_processing(table_right, condition, 'full') - - right_column = table_right.column_by_name(table_right.column_names[column_index_right]) - left_column = self.column_by_name(self.column_names[column_index_left]) - - right_table_row_length = len(table_right.column_names) - left_table_row_length = len(self.column_names) - + def pliris_enosi(self, table_right: Table, condition): #Performs full join of the table,according to a specific condition. + join_table, index_stilis_aristerou_pinaka, index_stilis_dexiou_pinaka, telestis = self._general_join_processing(table_right, condition, 'full') + dexia_stili = table_right.column_by_name(table_right.column_names[index_stilis_dexiou_pinaka]) + aristeri_stili = self.column_by_name(self.column_names[index_stilis_aristerou_pinaka]) + mhkos_grammhs_dexiou_pinaka = len(table_right.column_names) + mhkos_grammhs_aristerou_pinaka = len(self.column_names) for row_left in self.data: - left_value = row_left[column_index_left] - if left_value is None: + aristeri_timi = row_left[index_stilis_aristerou_pinaka] + if aristeri_timi is None: continue - if left_value not in right_column: - join_table._insert(row_left + right_table_row_length*["NULL"]) + if aristeri_timi not in dexia_stili: + join_table._insert(row_left + mhkos_grammhs_dexiou_pinaka*["NULL"]) else: for row_right in table_right.data: - right_value = row_right[column_index_right] - if left_value == right_value: + dexia_timi = row_right[index_stilis_dexiou_pinaka] + if aristeri_timi == dexia_timi: join_table._insert(row_left + row_right) - for row_right in table_right.data: - right_value = row_right[column_index_right] - - if right_value is None: + dexia_timi = row_right[index_stilis_dexiou_pinaka] + if dexia_timi is None: continue - elif right_value not in left_column: - join_table._insert(left_table_row_length*["NULL"] + row_right) - + elif dexia_timi not in aristeri_stili: + join_table._insert(mhkos_grammhs_aristerou_pinaka*["NULL"] + row_right) return join_table - def show(self, no_of_rows=None, is_locked=False): - ''' - Print the table in a nice readable format. - - Args: - no_of_rows: int. Number of rows. - is_locked: boolean. Whether it is locked (False by default). - ''' - output = "" - # if the table is locked, add locked keyword to title - if is_locked: - output += f"\n## {self._name} (locked) ##\n" + def show(self, plithos_grammon=None, einai_kleidomeno=False): #Show the table! + pros_emfanisi = "" + if einai_kleidomeno: + pros_emfanisi += f"\n## {self._name} kleidomeno ##\n" else: - output += f"\n## {self._name} ##\n" - + pros_emfanisi += f"\n## {self._name} ##\n" # headers -> "column name (column type)" headers = [f'{col} ({tp.__name__})' for col, tp in zip(self.column_names, self.column_types)] if self.pk_idx is not None: # table has a primary key, add PK next to the appropriate column headers[self.pk_idx] = headers[self.pk_idx]+' #PK#' - # 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)] - # print using tabulate - print(tabulate(non_none_rows[:no_of_rows], headers=headers)+'\n') + # detect the rows that are not full of nones + mh_kenes_seires = [row for row in self.data if any(row)] + print(tabulate(mh_kenes_seires[:plithos_grammon], headers=headers)+'\n') - def _parse_condition(self, condition, join=False): + def analysi_synthikis(self, synthiki, join=False): ''' - Parse the single string condition and return the value of the column and the operator. - - Args: - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) + Args:(other than the condition) join: boolean. Whether to join or not (False by default). ''' - # if both_columns (used by the join function) return the names of the names of the columns (left first) + # if the function uses both columns, then return the names of the columns (left first) if join: - return split_condition(condition) - - # cast the value with the specified column's type and return the column name, the operator and the casted value - left, op, right = split_condition(condition) - 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 split_condition(synthiki) - return left, op, coltype(right) + # cast the value with the specified column's type + aristero, op, dexi = split_condition(synthiki) + if aristero not in self.column_names: + raise ValueError(f'Synthiki mh egkurh (den vrisko tin stili pou mou les)') + typos_sthlhs = self.column_types[self.column_names.index(aristero)] + return aristero, op, typos_sthlhs(dexi) - def _load_from_file(self, filename): - ''' - Load table from a pkl file (not used currently). - - Args: - filename: string. Name of pkl file. - ''' - f = open(filename, 'rb') - tmp_dict = pickle.load(f) - f.close() - - self.__dict__.update(tmp_dict.__dict__) + def fortose_apo_arxeio(self, arxeiouonoma): #Loads from a .pkl file + arxeio_pros_anoigma = open(arxeiouonoma, 'rb') + fortoma = pickle.load(arxeio_pros_anoigma) + arxeio_pros_anoigma.close() + self.__dict__.update(fortoma.__dict__) From e9b7e6eb0bd0f0fe4f287a5a202e6ef4e00138c0 Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:53:41 +0200 Subject: [PATCH 2/7] Created 2 query plans, by Thodoris --- mdb.py | 337 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 180 insertions(+), 157 deletions(-) diff --git a/mdb.py b/mdb.py index a981e5be..6996f2a4 100644 --- a/mdb.py +++ b/mdb.py @@ -1,3 +1,4 @@ +#First import the necessary modules import os import re from pprint import pprint @@ -6,10 +7,8 @@ import traceback import shutil sys.path.append('miniDB') - from database import Database from table import Table -# art font is "big" art = ''' _ _ _____ ____ (_) (_)| __ \ | _ \ @@ -18,70 +17,49 @@ | | | | | || || | | || || |__| || |_) | |_| |_| |_||_||_| |_||_||_____/ |____/ 2022 ''' - - -def search_between(s, first, last): - ''' - Search in 's' for the substring that is between 'first' and 'last' - ''' +def anazitisi_anamesa(s, proto, teleutaio): #Search the substring between arxh and telos. try: - start = s.index( first ) + len( first ) - end = s.index( last, start ) + arxh = s.index( proto ) + len(proto) + telos = s.index( teleutaio, start ) except: return - return s[start:end].strip() + return s[arxh:telos].strip() -def in_paren(qsplit, ind): +def entos_parenthesis(qsplit, ind): ''' Split string on space and return whether the item in index 'ind' is inside a parentheses ''' return qsplit[:ind].count('(')>qsplit[:ind].count(')') -def create_query_plan(query, keywords, action): - ''' - Given a query, the set of keywords that we expect to pe present and the overall action, return the query plan for this query. - - 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 = [] - kw_positions = [] - i=0 - while i ', auto_suggest=AutoSuggestFromHistory()).lower() + line = synedria.prompt(f'({db._name})> ', auto_suggest=AutoSuggestFromHistory()).lower() if line[-1]!=';': line+=';' except (KeyboardInterrupt, EOFError): - print('\nbye!') + print('Telos.') break try: if line=='exit': break if line.split(' ')[0].removesuffix(';') in ['lsdb', 'lstb', 'cdb', 'rmdb']: - interpret_meta(line) + diermineia_meta(line) elif line.startswith('explain'): - dic = interpret(line.removeprefix('explain ')) + dic = diermineia(line.removeprefix('explain ')) pprint(dic, sort_dicts=False) else: - dic = interpret(line) - result = execute_dic(dic) + dic = diermineia(line) + result = ektelesi_lexikou(dic) if isinstance(result,Table): result.show() except Exception: print(traceback.format_exc()) + +def dimiourgia_deuterou_planou(frasi, lexeis_kleidia, energeia): #Etero plano gia tin ektelesi mias sql entolis: σθ1∧θ2(E) = σθ1(σθ2(E)) + dic = {val: None for val in lexeis_kleidia if val!=';'} + ql = [val for val in frasi.split(' ') if val !=''] + keyword_in_query = [] + keyword_positions = [] + metritis=0 + while metritis Date: Mon, 20 Feb 2023 21:54:36 +0200 Subject: [PATCH 3/7] Better reduce the lines, no reason for spaces. --- miniDB/dashboard.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/miniDB/dashboard.py b/miniDB/dashboard.py index ee97b0a8..48638f2c 100644 --- a/miniDB/dashboard.py +++ b/miniDB/dashboard.py @@ -1,14 +1,8 @@ import sys import os - sys.path.append(f'{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/miniDB') - from database import Database - - db = Database(sys.argv[1]) - - for name in list(db.tables): if sys.argv[2]=='meta' and name[:4]!='meta': continue From 7a64009c994994ebde618a089967b0b4585fc140 Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:55:29 +0200 Subject: [PATCH 4/7] Thodoris added the cases for BETWEEN and OR. --- miniDB/database.py | 864 ++++++++++++++++----------------------------- 1 file changed, 312 insertions(+), 552 deletions(-) diff --git a/miniDB/database.py b/miniDB/database.py index a3ac6be7..ca0aa0b8 100644 --- a/miniDB/database.py +++ b/miniDB/database.py @@ -1,748 +1,508 @@ from __future__ import annotations import pickle from time import sleep, localtime, strftime -import os,sys +import os, sys import logging import warnings import readline from tabulate import tabulate - -sys.path.append(f'{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/miniDB') +sys.path.append(f'{os.path.dirname(os.path.dirname(os.path.abspath(__file__)))}/miniDB') #Our path for the miniDB from miniDB import table sys.modules['table'] = table - from joins import Inlj, Smj -from btree import Btree +from btree import Btree #Needed for the 2nd question from misc import split_condition from table import Table - -# readline.clear_history() - class Database: - ''' - Main Database class, containing tables. - ''' - - def __init__(self, name, load=True, verbose = True): + + def __init__(self, name, load=True, verbose=True): self.tables = {} self._name = name self.verbose = verbose - self.savedir = f'dbdata/{name}_db' - if load: try: - self.load_database() - logging.info(f'Loaded "{name}".') + self.fortoma_vasis() + logging.info(f'Fortonei "{name}".') return except: if verbose: - warnings.warn(f'Database "{name}" does not exist. Creating new.') + warnings.warn(f'H vash dedomenon "{name}" den yfistatai. Dhmiourgia neas.') #Creates a new database # create dbdata directory if it doesnt exist if not os.path.exists('dbdata'): os.mkdir('dbdata') - # create new dbs save directory try: os.mkdir(self.savedir) except: pass - - # create all the meta tables + 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.save_database() + self.create_table('meta_indexes', 'table_name,index_name,column_name', 'str,str,str')#After typing the command on Git Bash, meta are firstly created and then the others. + self.sosimo_vasis() - def save_database(self): - ''' - Save database as a pkl file. This method saves the database object, including all tables and attributes. - ''' + def sosimo_vasis(self): #Swzei thn vash ws arxeio for name, table in self.tables.items(): with open(f'{self.savedir}/{name}.pkl', 'wb') as f: pickle.dump(table, f) - def _save_locks(self): - ''' - Stores the meta_locks table to file as meta_locks.pkl. - ''' + def sosimo_louketon(self): #Swzontai ta locks ws arxeio with open(f'{self.savedir}/meta_locks.pkl', 'wb') as f: pickle.dump(self.tables['meta_locks'], f) - def load_database(self): - ''' - Load all tables that are part of the database (indices noted here are loaded). - - Args: - path: string. Directory (path) of the database on the system. - ''' + def fortoma_vasis(self): path = f'dbdata/{self._name}_db' for file in os.listdir(path): - - if file[-3:]!='pkl': # if used to load only pkl files + if file[-3:] != 'pkl': continue - f = open(path+'/'+file, 'rb') + f = open(path + '/' + file, 'rb') tmp_dict = pickle.load(f) f.close() name = f'{file.split(".")[0]}' self.tables.update({name: tmp_dict}) # setattr(self, name, self.tables[name]) - #### IO #### + def _update(self): #Tropopoihsi ton meta-pinakon. + self.tropopoisi_meta_length() + self.tropopoisi_tis_stoivas_meta_insert() - def _update(self): + def create_table(self, name, column_names, column_types, primary_key=None, load=None): #Creating and configuring the table ''' - Update all meta tables. - ''' - self._update_meta_length() - self._update_meta_insert_stack() - - - def create_table(self, name, column_names, column_types, primary_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 - + This table is saved and can be accessed via db_object.tables['table_name'] or db_object.table_name Args: name: string. Name of table. - column_names: list. Names of columns. - column_types: list. Types of columns. - primary_key: string. The primary key (if it exists). + column_names: list. + column_types: list. + primary_key: string.(if it exists) 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, 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]) + self.sosimo_vasis() if self.verbose: - print(f'Created table "{name}".') - - - def drop_table(self, table_name): - ''' - Drop table from current database. - - Args: - table_name: string. Name of table. - ''' - self.load_database() - self.lock_table(table_name) - - self.tables.pop(table_name) - if os.path.isfile(f'{self.savedir}/{table_name}.pkl'): - os.remove(f'{self.savedir}/{table_name}.pkl') + print(f'Dimiourgithike o pinakas "{name}".') + + def diagrafi_pinaka(self, onoma_pinaka):#Delete the table from the db + self.fortoma_vasis() + self.kleidoma_pinaka(onoma_pinaka) + self.tables.pop(onoma_pinaka) + if os.path.isfile(f'{self.savedir}/{onoma_pinaka}.pkl'): + os.remove(f'{self.savedir}/{onoma_pinaka}.pkl') else: - warnings.warn(f'"{self.savedir}/{table_name}.pkl" not found.') - self.delete_from('meta_locks', f'table_name={table_name}') - self.delete_from('meta_length', f'table_name={table_name}') - self.delete_from('meta_insert_stack', f'table_name={table_name}') - - if self._has_index(table_name): - to_be_deleted = [] + warnings.warn(f'"{self.savedir}/{onoma_pinaka}.pkl" den vrethike.') + self.diagrafi_apo('meta_locks', f'table_name={onoma_pinaka}') + self.diagrafi_apo('meta_length', f'table_name={onoma_pinaka}') + self.diagrafi_apo('meta_insert_stack', f'table_name={onoma_pinaka}') + if self._exei_index(onoma_pinaka): + pros_diagrafi = [] for key, table in enumerate(self.tables['meta_indexes'].column_by_name('table_name')): - if table == table_name: - to_be_deleted.append(key) - - for i in reversed(to_be_deleted): + if table == onoma_pinaka: + pros_diagrafi.append(key) + for i in reversed(pros_diagrafi): self.drop_index(self.tables['meta_indexes'].data[i][1]) - try: - delattr(self, table_name) + delattr(self, onoma_pinaka) except AttributeError: pass - # self._update() - self.save_database() - - - def import_table(self, table_name, filename, column_types=None, primary_key=None): - ''' - Creates table from CSV file. + self.sosimo_vasis() - Args: - filename: string. CSV filename. If not specified, filename's name will be used. - column_types: list. Types of columns. If not specified, all will be set to type str. - primary_key: string. The primary key (if it exists). - ''' - file = open(filename, 'r') - - first_line=True - for line in file.readlines(): - if first_line: + def eisagogi_pinaka(self, onoma_pinaka, onoma_arxeiou, column_types=None, primary_key=None): #Create table according to elements of a CSV file. + arxeio = open(onoma_arxeiou, 'r') + proti_seira = True + for line in arxeio.readlines(): + if proti_seira: colnames = line.strip('\n') if column_types is None: column_types = ",".join(['str' for _ in colnames.split(',')]) - self.create_table(name=table_name, column_names=colnames, column_types=column_types, primary_key=primary_key) - lock_ownership = self.lock_table(table_name, mode='x') - first_line = False + self.create_table(name=onoma_pinaka, column_names=colnames, column_types=column_types, primary_key=primary_key) + louketo = kleidoma_pinaka(onoma_pinaka, tropos='x') + proti_seira = False continue - self.tables[table_name]._insert(line.strip('\n').split(',')) - - if lock_ownership: - self.unlock_table(table_name) + self.tables[onoma_pinaka]._insert(line.strip('\n').split(',')) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() - + self.sosimo_vasis() - def export(self, table_name, filename=None): + def eksagogi_pinaka(self, onoma_pinaka, onoma_arxeiou=None): ''' - Transform table to CSV. - - Args: - table_name: string. Name of table. - filename: string. Output CSV filename. + O pinakas ginetai se morfi arxeiou csv ''' res = '' - for row in [self.tables[table_name].column_names]+self.tables[table_name].data: - res+=str(row)[1:-1].replace('\'', '').replace('"','').replace(' ','')+'\n' - - if filename is None: - filename = f'{table_name}.csv' - - with open(filename, 'w') as file: - file.write(res) + for row in [self.tables[onoma_pinaka].column_names] + self.tables[onoma_pinaka].data: + res += str(row)[1:-1].replace('\'', '').replace('"', '').replace(' ', '') + '\n' + if onoma_arxeiou is None: + onoma_arxeiou = f'{onoma_pinaka}.csv' + with open(onoma_arxeiou, 'w') as file: + file.write(res) - def table_from_object(self, new_table): + def pinakas_apo_antikeimeno(self, neos_pinakas): ''' Add table object to database. - - Args: - new_table: string. Name of new table. ''' - - self.tables.update({new_table._name: new_table}) - if new_table._name not in self.__dir__(): - setattr(self, new_table._name, new_table) + self.tables.update({neos_pinakas._name: neos_pinakas}) + if neos_pinakas._name not in self.__dir__(): + setattr(self, neos_pinakas._name, neos_pinakas) else: - raise Exception(f'"{new_table._name}" attribute already exists in class "{self.__class__.__name__}".') + raise Exception(f'"{neos_pinakas._name}" yparxei hdh, vres etero onoma "{self.__class__.__name__}".') self._update() - self.save_database() + self.sosimo_vasis() - - - ##### table functions ##### - - # In every table function a load command is executed to fetch the most recent table. - # In every table function, we first check whether the table is locked. Since we have implemented - # only the X lock, if the tables is locked we always abort. - # After every table function, we update and save. Update updates all the meta tables and save saves all - # tables. - - # these function calls are named close to the ones in postgres - - def cast(self, column_name, table_name, cast_type): + #Now, the functions regarding the table. + def cast(self, stili, onoma_pinaka, cast_type): ''' Modify the type of the specified column and cast all prexisting values. (Executes type() for every value in column and saves) - - Args: - table_name: string. Name of table (must be part of database). - column_name: string. The column that will be casted (must be part of database). - cast_type: type. Cast type (do not encapsulate in quotes). ''' - self.load_database() - - lock_ownership = self.lock_table(table_name, mode='x') - self.tables[table_name]._cast_column(column_name, eval(cast_type)) - if lock_ownership: - self.unlock_table(table_name) + self.fortoma_vasis() + louketo = self.kleidoma_pinaka(onoma_pinaka, tropos='x') + self.tables[onoma_pinaka]._cast_column(stili, eval(cast_type)) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() + self.sosimo_vasis() - def insert_into(self, table_name, row_str): + def insert_into(self, onoma_pinaka, row_str): ''' - Inserts data to given table. - Args: - table_name: string. Name of table (must be part of database). - row: list. A list of values to be inserted (will be casted to a predifined type automatically). - lock_load_save: boolean. If False, user needs to load, lock and save the states of the database (CAUTION). Useful for bulk-loading. + table_name: string.(prepei na apotelei meros tis vaseos). + row: list. Katalogos timon pros eisagogi (Tha oristei automatos os sygkekrimenos typos). + lock_load_save: boolean. If False, user needs to load, lock and save the states of the database. ''' row = row_str.strip().split(',') - self.load_database() - # fetch the insert_stack. For more info on the insert_stack - # check the insert_stack meta table - lock_ownership = self.lock_table(table_name, mode='x') - insert_stack = self._get_insert_stack_for_table(table_name) + self.fortoma_vasis() + louketo = self.kleidoma_pinaka(onoma_pinaka, tropos='x') + insert_stack = self.pare_tin_stoiva(onoma_pinaka) #Get the insert stack. try: - self.tables[table_name]._insert(row, insert_stack) - except Exception as e: - logging.info(e) - logging.info('ABORTED') - self._update_meta_insert_stack_for_tb(table_name, insert_stack[:-1]) - - if lock_ownership: - self.unlock_table(table_name) + self.tables[onoma_pinaka]._insert(row, insert_stack) + except Exception as eksairesi: + logging.info(eksairesi) + logging.info('H DIADIKASIA APETYXE') + self._update_meta_insert_stack_for_tb(onoma_pinaka, insert_stack[:-1]) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() - - - def update_table(self, table_name, set_args, condition): - ''' - Update the value of a column where a condition is met. - - Args: - table_name: string. Name of table (must be part of database). - set_value: string. New value of the predifined column name. - set_column: string. The column to be altered. - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) - ''' - set_column, set_value = set_args.replace(' ','').split('=') - self.load_database() - - lock_ownership = self.lock_table(table_name, mode='x') - self.tables[table_name]._update_rows(set_value, set_column, condition) - if lock_ownership: - self.unlock_table(table_name) + self.sosimo_vasis() + + def tropopoisi_pinaka(self, onoma_pinaka, vale_arguments, synthiki): #Allagi se sygkekrimeno simeio tou pinaka + set_column, set_value = vale_arguments.replace(' ', '').split('=') + self.fortoma_vasis() + louketo = self.kleidoma_pinaka(onoma_pinaka, tropos='x') + self.tables[onoma_pinaka].tropopoisi_seiras(set_value, set_column, condition) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() - - def delete_from(self, table_name, condition): - ''' - Delete rows of table where condition is met. - - Args: - table_name: string. Name of table (must be part of database). - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) - ''' - self.load_database() - - lock_ownership = self.lock_table(table_name, mode='x') - deleted = self.tables[table_name]._delete_where(condition) - if lock_ownership: - self.unlock_table(table_name) + self.sosimo_vasis() + + def diagrafi_apo(self, onoma_pinaka, synthiki): #Diagrafi sykekrimenon simeion tou pinaka. + self.fortoma_vasis() + louketo = self.kleidoma_pinaka(onoma_pinaka, tropos='x') + deleted = self.tables[onoma_pinaka].diagrafi_opou(synthiki) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() - # we need the save above to avoid loading the old database that still contains the deleted elements - if table_name[:4]!='meta': - self._add_to_insert_stack(table_name, deleted) - self.save_database() - - def select(self, columns, table_name, condition, distinct=None, order_by=None, \ - limit=True, desc=None, save_as=None, return_object=True): - ''' - Selects and outputs a table's data where condtion is met. + self.sosimo_vasis()#apothikeusi tis vasis meta apo kathe allagi + if onoma_pinaka[:4] != 'meta': + self.valto_sti_stoiva(onoma_pinaka, deleted) + self.sosimo_vasis() + def select(self, columns, table_name, condition, distinct=None, order_by=None, limit=True, desc=None, apothikeusi_ws=None, return_object=True): #Deixnei auta poy ikanopoioun thn sunthiki + ''' Args: - table_name: string. Name of table (must be part of database). - columns: list. The columns that will be part of the output table (use '*' to select all available columns) - condition: string. A condition using the following format: - 'column[<,<=,==,>=,>]value' or - 'value[<,<=,==,>=,>]column'. - - Operatores supported: (<,<=,==,>=,>) - order_by: string. A column name that signals that the resulting table should be ordered based on it (no order if None). - desc: boolean. If True, order_by will return results in descending order (True by default). - limit: int. An integer that defines the number of rows that will be returned (all rows if None). - save_as: string. The name that will be used to save the resulting table into the database (no save if None). - return_object: boolean. If True, the result will be a table object (useful for internal use - the result will be printed by default). - distinct: boolean. If True, the resulting table will contain only unique rows. + all written as parameters, plus the boolean variable distinct. ''' - - # print(table_name) - self.load_database() - 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 "BETWEEN" in condition.split() or "between" in condition.split():#Does the condition contain between? + condition_column = condition.split(" ")[0] + elif "NOT" in condition.split() or "not" in condition.split():#Looking for condition with NOT operator + condition_column = condition.split(" ")[0] + elif "AND" in condition.split() or "and" in condition.split() or "OR" in condition.split() or "or" in condition.split():#Looking for condition containing AND or OR + condition_column = condition.split(" ")[0] + else: + condition_column = split_condition(condition)[0] else: condition_column = '' - - - # self.lock_table(table_name, mode='x') - if self.is_locked(table_name): + if self.einai_kleidomeno(table_name): #Is table locked? return - if self._has_index(table_name) and condition_column==self.tables[table_name].column_names[self.tables[table_name].pk_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) + if self._exei_index(table_name) and condition_column == self.tables[table_name].column_names[self.tables[table_name].pk_idx]: + index_name = \ + self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name( + 'index_name')[0] + b_dendro = self._fortose_index(index_name) + try: + table = self.tables[table_name].epilogi_alla_me_btree(columns, b_dendro, condition, distinct, order_by, desc, limit) + except: + table = self.tables[table_name].epilogi_alla_me_hash(columns, b_dendro, condition, distinct, order_by, desc, limit) # if btree fails, try hash 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 - self.table_from_object(table) + if apothikeusi_ws is not None: + table._name = apothikeusi_ws + self.pinakas_apo_antikeimeno(table) else: if return_object: return table else: return table.show() + def deixe_ton_pinaka(self, onoma_pinaka, plithos_grammon=None): #Showing the table + self.fortoma_vasis() + self.tables[onoma_pinaka].show(plithos_grammon, self.einai_kleidomeno(onoma_pinaka)) - def show_table(self, table_name, no_of_rows=None): - ''' - Print table in a readable tabular design (using tabulate). - - Args: - table_name: string. Name of table (must be part of database). - ''' - self.load_database() - - self.tables[table_name].show(no_of_rows, self.is_locked(table_name)) - - - def sort(self, table_name, column_name, asc=False): + def taxinomisi(self, onoma_pinaka, stili, asc=False): ''' - Sorts a table based on a column. - - Args: - table_name: string. Name of table (must be part of database). - column_name: string. the column name that will be used to sort. - asc: If True sort will return results in ascending order (False by default). + asc: If True sort will return results in ascending order (False by default). ''' - - self.load_database() - - lock_ownership = self.lock_table(table_name, mode='x') - self.tables[table_name]._sort(column_name, asc=asc) - if lock_ownership: - self.unlock_table(table_name) + self.fortoma_vasis() + louketo = self.kleidoma_pinaka(onoma_pinaka, tropos='x') + self.tables[onoma_pinaka]._sort(stili, asc=asc) + if louketo: + self.xekleidoma_pinaka(onoma_pinaka) self._update() - self.save_database() - - def create_view(self, table_name, table): - ''' - Create a virtual table based on the result-set of the SQL statement provided. + self.sosimo_vasis() - Args: - table_name: string. Name of the table that will be saved. - table: table. The table that will be saved. - ''' + def provoli(self, table_name, table): #Emfanisi provolis tou pinaka table._name = table_name - self.table_from_object(table) + self.pinakas_apo_antikeimeno(table) - def join(self, mode, left_table, right_table, condition, save_as=None, return_object=True): + def enosi_opou_sinthiki_isxyei(self, tropos, left_table, right_table, synthiki, apothikeusi_ws=None, emfanisi_antikeimenou=True): ''' - Join two tables that are part of the database where condition is met. - Args: left_table: string. Name of the left table (must be in DB) or Table obj. right_table: string. Name of the right table (must be in DB) or Table obj. - condition: string. A condition using the following format: + synthiki: string. A condition using the following format: 'column[<,<=,==,>=,>]value' or 'value[<,<=,==,>=,>]column'. - Operators supported: (<,<=,==,>=,>) save_as: string. The output filename that will be used to save the resulting table in the database (won't save if None). - return_object: boolean. If True, the result will be a table object (useful for internal usage - the result will be printed by default). + emfanisi_antikeimenou: boolean. If True, the result will be a table object (useful for internal usage - the result will be printed by default). ''' - self.load_database() - if self.is_locked(left_table) or self.is_locked(right_table): + self.fortoma_vasis() + if self.einai_kleidomeno(left_table) or self.einai_kleidomeno(right_table): return - - left_table = left_table if isinstance(left_table, Table) else self.tables[left_table] - right_table = right_table if isinstance(right_table, Table) else self.tables[right_table] - - - if mode=='inner': - res = left_table._inner_join(right_table, condition) - - elif mode=='left': - res = left_table._left_join(right_table, condition) - - elif mode=='right': - res = left_table._right_join(right_table, condition) - - elif mode=='full': - res = left_table._full_join(right_table, condition) - - elif mode=='inl': - # Check if there is an index of either of the two tables available, as if there isn't we can't use inlj - leftIndexExists = self._has_index(left_table._name) - rightIndexExists = self._has_index(right_table._name) - - if not leftIndexExists and not rightIndexExists: - res = None - raise Exception('Index-nested-loop join cannot be executed. Use inner join instead.\n') - elif rightIndexExists: - index_name = self.select('*', 'meta_indexes', f'table_name={right_table._name}', return_object=True).column_by_name('index_name')[0] - res = Inlj(condition, left_table, right_table, self._load_idx(index_name), 'right').join() - elif leftIndexExists: - index_name = self.select('*', 'meta_indexes', f'table_name={left_table._name}', return_object=True).column_by_name('index_name')[0] - res = Inlj(condition, left_table, right_table, self._load_idx(index_name), 'left').join() - - elif mode=='sm': - res = Smj(condition, left_table, right_table).join() - + left_table = left_table if isinstance(left_table, Table) else self.tables[left_table] + right_table = right_table if isinstance(right_table, Table) else self.tables[right_table] + if tropos == 'inner':#Inner join + enosi = left_table._inner_join(right_table, synthiki) + elif tropos == 'left': + enosi = left_table.enosi_apo_aristera(right_table, synthiki) #Left join + elif tropos == 'right': + enosi = left_table.enosi_apo_dexia(right_table, synthiki) #Right join + elif tropos == 'full': + enosi = left_table.pliris_enosi(right_table, synthiki) #Full join + elif tropos == 'inl': #for index-nested-loop join + # Check if there is an index of either of the two tables available. + yparxei_aristero_index = self._exei_index(left_table._name) + yparxei_dexio_index = self._exei_index(right_table._name) + if not yparxei_aristero_index and not yparxei_dexio_index: + enosi = None + raise Exception('Adynati i ektelesi tou INLJ. Kalytera kane inner join.') + elif yparxei_dexio_index: + index_name = \ + self.select('*', 'meta_indexes', f'table_name={right_table._name}', + emfanisi_antikeimenou=True).column_by_name( + 'index_name')[0] + enosi = Inlj(synthiki, left_table, right_table, self._fortose_index(index_name), 'right').enosi_opou_sinthiki_isxyei() + elif yparxei_aristero_index: + index_name = \ + self.select('*', 'meta_indexes', f'table_name={left_table._name}', + emfanisi_antikeimenou=True).column_by_name( + 'index_name')[0] + enosi = Inlj(synthiki, left_table, right_table, self._fortose_index(index_name), 'left').enosi_opou_sinthiki_isxyei() + elif tropos == 'sm': + enosi = Smj(synthiki, left_table, right_table).enosi_opou_sinthiki_isxyei()#To implement sort-merge join else: raise NotImplementedError - - if save_as is not None: - res._name = save_as - self.table_from_object(res) + if apothikeusi_ws is not None: + enosi._name = apothikeusi_ws + self.pinakas_apo_antikeimeno(res) else: - if return_object: - return res + if emfanisi_antikeimenou: + return enosi else: - res.show() - - if return_object: - return res + enosi.show() + if emfanisi_antikeimenou: + return enosi else: - res.show() - - def lock_table(self, table_name, mode='x'): - ''' - Locks the specified table using the exclusive lock (X). + enosi.show() - Args: - table_name: string. Table name (must be part of database). - ''' - if table_name[:4]=='meta' or table_name not in self.tables.keys() or isinstance(table_name,Table): + def kleidoma_pinaka(self, onoma_pinaka, tropos='x'): #Lock the table! + if onoma_pinaka[:4] == 'meta' or onoma_pinaka not in self.tables.keys() or isinstance(onoma_pinaka, Table): return - with open(f'{self.savedir}/meta_locks.pkl', 'rb') as f: self.tables.update({'meta_locks': pickle.load(f)}) - try: - pid = self.tables['meta_locks']._select_where('pid',f'table_name={table_name}').data[0][0] - if pid!=os.getpid(): - raise Exception(f'Table "{table_name}" is locked by process with pid={pid}') + pid = self.tables['meta_locks']._select_where('pid', f'table_name={onoma_pinaka}').data[0][0] + if pid != os.getpid(): + raise Exception(f'O pinakas "{onoma_pinaka}" einai kleidomenos apo thn diadikasia me pid={pid}') else: return False - except IndexError: pass - - if mode=='x': - self.tables['meta_locks']._insert([table_name, os.getpid(), mode]) + if tropos == 'x': + self.tables['meta_locks']._insert([onoma_pinaka, os.getpid(), tropos]) else: raise NotImplementedError - self._save_locks() + self.sosimo_louketon() return True - # print(f'Locking table "{table_name}"') - - def unlock_table(self, table_name, force=False): - ''' - Unlocks the specified table that is exclusively locked (X). - - Args: - table_name: string. Table name (must be part of database). - ''' - if table_name not in self.tables.keys(): - raise Exception(f'Table "{table_name}" is not in database') + - if not force: + def xekleidoma_pinaka(self, onoma_pinaka, isxys=False): #Unlock the table. + if onoma_pinaka not in self.tables.keys(): + raise Exception(f'O pinakas "{onoma_pinaka}" den einai stin vasi') + if not isxys: try: - # pid = self.select('*','meta_locks', f'table_name={table_name}', return_object=True).data[0][1] - pid = self.tables['meta_locks']._select_where('pid',f'table_name={table_name}').data[0][0] - if pid!=os.getpid(): - raise Exception(f'Table "{table_name}" is locked by the process with pid={pid}') + pid = self.tables['meta_locks']._select_where('pid', f'table_name={onoma_pinaka}').data[0][0] + if pid != os.getpid(): + raise Exception(f'O pinakas "{onoma_pinaka}" einai kleidomenos apo thn diadikasia me pid={pid}') except IndexError: pass - self.tables['meta_locks']._delete_where(f'table_name={table_name}') - self._save_locks() - # print(f'Unlocking table "{table_name}"') - - def is_locked(self, table_name): - ''' - Check whether the specified table is exclusively locked (X). + self.tables['meta_locks'].diagrafi_opou(f'table_name={onoma_pinaka}') + self.sosimo_louketon() + - Args: - table_name: string. Table name (must be part of database). - ''' - if isinstance(table_name,Table) or table_name[:4]=='meta': # meta tables will never be locked (they are internal) + def einai_kleidomeno(self, onoma_pinaka): + if isinstance(onoma_pinaka, Table) or onoma_pinaka[:4] == 'meta': # meta tables will never be locked (they are internal) return False - with open(f'{self.savedir}/meta_locks.pkl', 'rb') as f: self.tables.update({'meta_locks': pickle.load(f)}) - try: - pid = self.tables['meta_locks']._select_where('pid',f'table_name={table_name}').data[0][0] - if pid!=os.getpid(): - raise Exception(f'Table "{table_name}" is locked by the process with pid={pid}') - + pid = self.tables['meta_locks']._select_where('pid', f'table_name={onoma_pinaka}').data[0][0] + if pid != os.getpid(): + raise Exception(f'O pinakas "{onoma_pinaka}" einai kleidomenos apo tin diadikasia me pid={pid}') except IndexError: pass return False - - #### META #### - - # The following functions are used to update, alter, load and save the meta tables. - # Important: Meta tables contain info regarding the NON meta tables ONLY. - # i.e. meta_length will not show the number of rows in meta_locks etc. - - def _update_meta_length(self): - ''' - Updates the meta_length table. - ''' + #Functions regarding the meta tables + def tropopoisi_meta_length(self): for table in self.tables.values(): - if table._name[:4]=='meta': #skip meta tables + if table._name[:4] == 'meta': #meta skipped continue - if table._name not in self.tables['meta_length'].column_by_name('table_name'): # if new table, add record with 0 no. of rows + if table._name not in self.tables['meta_length'].column_by_name( + 'table_name'): # if new table, add record with 0 no. of rows self.tables['meta_length']._insert([table._name, 0]) + + mh_kenes_seires = len([row for row in table.data if any(row)]) #Mh kenes seires + self.tables['meta_length'].tropopoisi_seiras(mh_kenes_seires, 'no_of_rows', f'table_name={table._name}') + - # the result needs to represent the rows that contain data. Since we use an insert_stack - # some rows are filled with Nones. We skip these rows. - non_none_rows = len([row for row in table.data if any(row)]) - self.tables['meta_length']._update_rows(non_none_rows, 'no_of_rows', f'table_name={table._name}') - # self.update_row('meta_length', len(table.data), 'no_of_rows', 'table_name', '==', table._name) - - def _update_meta_locks(self): - ''' - Updates the meta_locks table. - ''' + def tropopoisi_louketwn_meta(self): for table in self.tables.values(): - if table._name[:4]=='meta': #skip meta tables + if table._name[:4] == 'meta': # meta skipped continue if table._name not in self.tables['meta_locks'].column_by_name('table_name'): - self.tables['meta_locks']._insert([table._name, False]) - # self.insert('meta_locks', [table._name, False]) + - def _update_meta_insert_stack(self): - ''' - Updates the meta_insert_stack table. - ''' + def tropopoisi_tis_stoivas_meta_insert(self): for table in self.tables.values(): - if table._name[:4]=='meta': #skip meta tables + if table._name[:4] == 'meta': # meta skipped continue if table._name not in self.tables['meta_insert_stack'].column_by_name('table_name'): self.tables['meta_insert_stack']._insert([table._name, []]) + def valto_sti_stoiva(self, onoma_pinaka, indexes): #Adding indexes to the table's stack. + old_lst = self.pare_tin_stoiva(onoma_pinaka) + self._update_meta_insert_stack_for_tb(onoma_pinaka, old_lst + indexes) - def _add_to_insert_stack(self, table_name, indexes): - ''' - Adds provided indices to the insert stack of the specified table. - - Args: - table_name: string. Table name (must be part of database). - indexes: list. The list of indices that will be added to the insert stack (the indices of the newly deleted elements). - ''' - old_lst = self._get_insert_stack_for_table(table_name) - self._update_meta_insert_stack_for_tb(table_name, old_lst+indexes) - - def _get_insert_stack_for_table(self, table_name): - ''' - Returns the insert stack of the specified table. - - Args: - table_name: string. Table name (must be part of database). - ''' - return self.tables['meta_insert_stack']._select_where('*', f'table_name={table_name}').column_by_name('indexes')[0] - # res = self.select('meta_insert_stack', '*', f'table_name={table_name}', return_object=True).indexes[0] - # return res - - def _update_meta_insert_stack_for_tb(self, table_name, new_stack): - ''' - Replaces the insert stack of a table with the one supplied by the user. - - Args: - table_name: string. Table name (must be part of database). - new_stack: string. The stack that will be used to replace the existing one. - ''' - self.tables['meta_insert_stack']._update_rows(new_stack, 'indexes', f'table_name={table_name}') + def pare_tin_stoiva(self, table_name): + return \ + self.tables['meta_insert_stack']._select_where('*', f'table_name={table_name}').column_by_name('indexes')[0] + + def _update_meta_insert_stack_for_tb(self, onoma_pinaka, nea_stoiva): + self.tables['meta_insert_stack'].tropopoisi_seiras(nea_stoiva, 'indexes', f'table_name={onoma_pinaka}') # indexes - def create_index(self, index_name, table_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). - - Args: - 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 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]) - # crate the actual index - self._construct_index(table_name, index_name) - self.save_database() + def create_index(self, index_name, onoma_pinaka, onoma_stilis, typos_eurethriou): #Index in a specific column of the table + if onoma_pinaka not in self.tables: #Non-existing table + raise Exception('Den einai dynath h dhmiourgia eurethriou se pinaka pou den yparxei.') + if onoma_stilis not in self.tables[onoma_pinaka].column_names: #Non-existing column + raise Exception('Den einai dynath h dhmiourgia eurethriou se sthlh pou den yparxei.') + if index_name not in self.tables['meta_indexes'].column_by_name('index_name'): #if index_name already exists,or not. + if typos_eurethriou == 'BTREE' or typos_eurethriou == 'btree': #Trying to insert Btree index on the meta_indexes table + logging.info('Ftiaxno to btree.') + self.tables['meta_indexes']._insert([onoma_pinaka, index_name, onoma_stilis]) + self.kataskeui_euretiriou(onoma_pinaka, index_name, onoma_stilis) + self.sosimo_vasis() + elif typos_eurethriou == 'HASH' or typos_eurethriou == 'hash': #Trying to insert Hash index on the meta_indexes table + logging.info('Ftiaxno to hash.') + self.tables['meta_indexes']._insert([onoma_pinaka, index_name, onoma_stilis]) + self.ftiaxe_hash_euretirio(onoma_pinaka, index_name, onoma_stilis) + self.sosimo_vasis() else: - raise Exception('Cannot create index. Another index with the same name already exists.') - - def _construct_index(self, table_name, index_name): - ''' - Construct a btree on a table and save. + raise Exception('Hdh yparxei eurethrio me auto to onoma.') - Args: - table_name: string. Table name (must be part of database). - index_name: string. Name of the created index. - ''' - bt = Btree(3) # 3 is arbitrary - - # for each record in the primary key of the table, insert its value and index to the btree - for idx, key in enumerate(self.tables[table_name].column_by_name(self.tables[table_name].pk)): + def kataskeui_euretiriou(self, onoma_pinaka, index_name, onoma_stilis): #Create Btree in the primary_key of the table specified + b_dendro = Btree(3) + for idx, key in enumerate(self.tables[onoma_pinaka].column_by_name(onoma_stilis)): if key 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. - - Args: - table_name: string. Table name (must be part of database). - ''' - return table_name in self.tables['meta_indexes'].column_by_name('table_name') - - def _save_index(self, index_name, index): - ''' - Save the index object. - - Args: - index_name: string. Name of the created index. - index: obj. The actual index object (btree object). - ''' + b_dendro.insert(key, idx) + self._apothikeusi_index(index_name, b_dendro) + print('To eftiaxa to btree eurethrio.') + return + + def ftiaxe_hash_euretirio(self, onoma_pinaka, index_name, onoma_stilis): + mhkos_grammhs = len(self.tables[onoma_pinaka].data) + hm = {} + hm[0] = {} + hm[0][0] = [str(mhkos_grammhs)] # store the number of the rows + for idx, key in enumerate(self.tables[onoma_pinaka].column_by_name(onoma_stilis)): + if key is None: + continue + hash_athroisma = 0 + for letter in key: + hash_athroisma += ord(letter) #To gramma to metatrepoume ston antistoixo arithmo tou, px gia to a einai 1, gia to b einai 2 ktl. + index_megalou_hash = hash_athroisma % mhkos_grammhs #modulo + index_mikroterou_hash = index_megalou_hash + if not(index_megalou_hash in hm): + hm[index_megalou_hash] = {} + else: + while True: + if not(index_mikroterou_hash in hm[index_megalou_hash]): + break + if (index_mikroterou_hash == mhkos_grammhs): + index_mikroterou_hash = 0 + else: + index_mikroterou_hash += 1 + hm[index_megalou_hash][index_mikroterou_hash] = [idx, key] + self._apothikeusi_index(index_name, hm) + print ('To eftiaxa to hash eurethrio.') + + def _exei_index(self, onoma_pinaka): #Does the column have index? + return onoma_pinaka in self.tables['meta_indexes'].column_by_name('table_name') + + def _apothikeusi_index(self, index_name, index): #Save the index! try: os.mkdir(f'{self.savedir}/indexes') except: pass - with open(f'{self.savedir}/indexes/meta_{index_name}_index.pkl', 'wb') as f: pickle.dump(index, f) - def _load_idx(self, index_name): - ''' - Load and return the specified index. - - Args: - index_name: string. Name of created index. - ''' - f = open(f'{self.savedir}/indexes/meta_{index_name}_index.pkl', 'rb') - index = pickle.load(f) - f.close() - return index - - def drop_index(self, index_name): - ''' - Drop index from current database. + def _fortose_index(self, index_name):#Load index. + arxeion = open(f'{self.savedir}/indexes/meta_{index_name}_index.pkl', 'rb') + eurethrio = pickle.load(arxeion) + arxeion.close() + return eurethrio - Args: - index_name: string. Name of index. - ''' + def drop_index(self, index_name): #Get rid of the index. if index_name in self.tables['meta_indexes'].column_by_name('index_name'): - self.delete_from('meta_indexes', f'index_name = {index_name}') - + self.diagrafi_apo('meta_indexes', f'index_name = {index_name}') if os.path.isfile(f'{self.savedir}/indexes/meta_{index_name}_index.pkl'): os.remove(f'{self.savedir}/indexes/meta_{index_name}_index.pkl') else: - warnings.warn(f'"{self.savedir}/indexes/meta_{index_name}_index.pkl" not found.') - - self.save_database() - \ No newline at end of file + warnings.warn(f'"{self.savedir}/indexes/meta_{index_name}_index.pkl" den vrethike.') + self.sosimo_vasis() + print('Entaxei, to diegrapsa.') + else: + raise Exception('Den yparxei eurethrio me auto to onoma.') From 1a134459fdf0646a734f57898ef1d64c013a7044 Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 21:56:58 +0200 Subject: [PATCH 5/7] Valame kai toys alloys dyo telestes, epitelous --- miniDB/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/miniDB/database.py b/miniDB/database.py index ca0aa0b8..fcd6dc74 100644 --- a/miniDB/database.py +++ b/miniDB/database.py @@ -220,9 +220,9 @@ def select(self, columns, table_name, condition, distinct=None, order_by=None, l all written as parameters, plus the boolean variable distinct. ''' if condition is not None: - if "BETWEEN" in condition.split() or "between" in condition.split():#Does the condition contain between? + if "BETWEEN" in condition.split() or "between" in condition.split(): #Epitelous leitourgise kai me to BETWEEN, meta apo pollh prospatheia condition_column = condition.split(" ")[0] - elif "NOT" in condition.split() or "not" in condition.split():#Looking for condition with NOT operator + elif "NOT" in condition.split() or "not" in condition.split(): #I periptosi tou telesti NOT condition_column = condition.split(" ")[0] elif "AND" in condition.split() or "and" in condition.split() or "OR" in condition.split() or "or" in condition.split():#Looking for condition containing AND or OR condition_column = condition.split(" ")[0] From 21e33d72b41584a38af9e35e7aef7943c0cfae7f Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:00:46 +0200 Subject: [PATCH 6/7] Join functions, and more... by Thodoris --- miniDB/table.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/miniDB/table.py b/miniDB/table.py index 7a3f81f0..ef5a8bc1 100644 --- a/miniDB/table.py +++ b/miniDB/table.py @@ -55,7 +55,7 @@ def _update(self): #Updates the columns with the appended rows. def _cast_column(self, column_name, cast_type): ''' - Args: + Xrhsima: column_name: string. The column that will be casted. cast_type: type. Cast type (do not encapsulate in quotes). ''' @@ -108,9 +108,7 @@ def tropopoisi_seiras(self, orise_timi, orise_stili, synthiki): def diagrafi_opou(self, synthiki): ''' - Important: delete replaces the rows to be deleted with rows filled with Nones. - These rows are then appended to the insert_stack. - Args: + Xrhsima: condition: string. A condition using the following format: 'column[<,<=,==,>=,>]value' or 'value[<,<=,==,>=,>]column'. @@ -125,16 +123,15 @@ def diagrafi_opou(self, synthiki): for index in sorted(indexes_pros_diagrafi, reverse=True): if self._name[:4] != 'meta': - # if the table is not a metatable, replace the row with a row of nones self.data[index] = [None for _ in range(len(self.column_names))] else: self.data.pop(index) return indexes_pros_diagrafi - def _select_where(self, emfanisi_stilwn, condition=None, distinct=False, order_by=None, desc=True, orion=None): #Returns specific rows and columns where a condition is met + def _select_where(self, emfanisi_stilwn, condition=None, distinct=False, order_by=None, desc=True, orion=None): #To gnosto select...from...where ''' - Args: + Xrhsima: return_columns: list. The columns to be returned. condition: string. A condition using the following format: 'column[<,<=,==,>=,>]value' or @@ -217,7 +214,7 @@ def _select_where(self, emfanisi_stilwn, condition=None, distinct=False, order_b return pros_emfanisi - def epilogi_alla_me_btree(self, return_columns, b_dendro, synthiki, distinct=False, order_by=None, desc=True, orion=None):# Selecting with Btree + def epilogi_alla_me_btree(self, return_columns, b_dendro, synthiki, distinct=False, order_by=None, desc=True, orion=None): #Eurethrio b-dendro if return_columns == '*': emfanisi_stilwn = [i for i in range(len(self.column_names))] else: @@ -251,7 +248,7 @@ def epilogi_alla_me_btree(self, return_columns, b_dendro, synthiki, distinct=Fal return pros_emfanisi - def epilogi_alla_me_hash(self, return_columns, hm, synthiki, distinct=False, order_by=None, desc=True, orion=None): #Selecting with hash + def epilogi_alla_me_hash(self, return_columns, hm, synthiki, distinct=False, order_by=None, desc=True, orion=None): #I periptosi tou hash if return_columns == '*': emfanisi_stilwn = [i for i in range(len(self.column_names))] else: From 552a543c5e43ba5d0e4415e547c549f112d927e6 Mon Sep 17 00:00:00 2001 From: tasostsotras <61273108+tasostsotras@users.noreply.github.com> Date: Mon, 20 Feb 2023 22:04:29 +0200 Subject: [PATCH 7/7] Ftiaxame kai ta euretiria, epitelous telos --- miniDB/database.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/miniDB/database.py b/miniDB/database.py index fcd6dc74..2c3b96b7 100644 --- a/miniDB/database.py +++ b/miniDB/database.py @@ -422,7 +422,7 @@ def _update_meta_insert_stack_for_tb(self, onoma_pinaka, nea_stoiva): self.tables['meta_insert_stack'].tropopoisi_seiras(nea_stoiva, 'indexes', f'table_name={onoma_pinaka}') # indexes - def create_index(self, index_name, onoma_pinaka, onoma_stilis, typos_eurethriou): #Index in a specific column of the table + def create_index(self, index_name, onoma_pinaka, onoma_stilis, typos_eurethriou): #Enarxi deuterou erotimatos if onoma_pinaka not in self.tables: #Non-existing table raise Exception('Den einai dynath h dhmiourgia eurethriou se pinaka pou den yparxei.') if onoma_stilis not in self.tables[onoma_pinaka].column_names: #Non-existing column @@ -441,7 +441,7 @@ def create_index(self, index_name, onoma_pinaka, onoma_stilis, typos_eurethriou) else: raise Exception('Hdh yparxei eurethrio me auto to onoma.') - def kataskeui_euretiriou(self, onoma_pinaka, index_name, onoma_stilis): #Create Btree in the primary_key of the table specified + def kataskeui_euretiriou(self, onoma_pinaka, index_name, onoma_stilis): #Kai me b-dendro b_dendro = Btree(3) for idx, key in enumerate(self.tables[onoma_pinaka].column_by_name(onoma_stilis)): if key is None: @@ -451,7 +451,7 @@ def kataskeui_euretiriou(self, onoma_pinaka, index_name, onoma_stilis): #Create print('To eftiaxa to btree eurethrio.') return - def ftiaxe_hash_euretirio(self, onoma_pinaka, index_name, onoma_stilis): + def ftiaxe_hash_euretirio(self, onoma_pinaka, index_name, onoma_stilis): #Kai me eurethrio katakermatismou mhkos_grammhs = len(self.tables[onoma_pinaka].data) hm = {} hm[0] = {}