diff --git a/mdb.py b/mdb.py index a981e5be..4a261cba 100644 --- a/mdb.py +++ b/mdb.py @@ -7,8 +7,8 @@ import shutil sys.path.append('miniDB') -from database import Database -from table import Table +from miniDB.database import Database +from miniDB.table import Table # art font is "big" art = ''' _ _ _____ ____ @@ -39,6 +39,7 @@ def in_paren(qsplit, ind): 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. @@ -49,6 +50,8 @@ def create_query_plan(query, keywords, action): ql = [val for val in query.split(' ') if val !=''] + + kw_in_query = [] kw_positions = [] i=0 @@ -97,19 +100,30 @@ def create_query_plan(query, keywords, action): args = dic['create table'][dic['create table'].index('('):dic['create table'].index(')')+1] dic['create table'] = dic['create table'].removesuffix(args).strip() arg_nopk = args.replace('primary key', '')[1:-1] + #arg_nouk = arg_nopk.replace('unique', '')[1:-1] arglist = [val.strip().split(' ') for val in arg_nopk.split(',')] dic['column_names'] = ','.join([val[0] for val in arglist]) dic['column_types'] = ','.join([val[1] for val in arglist]) if 'primary key' in args: arglist = args[1:-1].split(' ') + #print(arglist) dic['primary key'] = arglist[arglist.index('primary')-2] + #print(dic['primary key']) else: dic['primary key'] = None + if 'unique' in args: + arglist = args[1:-1].split(' ') + #print(arglist) + dic['unique_cols'] = arglist[arglist.index('unique')-2] + #print(dic['unique_cols']) + else: + dic['unique_cols'] = None if action=='import': dic = {'import table' if key=='import' else key: val for key, val in dic.items()} if action=='insert into': + if dic['values'][0] == '(' and dic['values'][-1] == ')': dic['values'] = dic['values'][1:-1] else: @@ -121,6 +135,11 @@ def create_query_plan(query, keywords, action): else: dic['force'] = False + if action == 'create index': + dic['on'] = ql[3] + dic['column'] = ql[5] + dic['using'] = ql[8] + return dic @@ -175,7 +194,7 @@ def interpret(query): 'unlock table': ['unlock table', 'force'], 'delete from': ['delete from', 'where'], 'update table': ['update table', 'set', 'where'], - 'create index': ['create index', 'on', 'using'], + 'create index': ['create index', 'on', 'column', 'using'], # add column name 'drop index': ['drop index'], 'create view' : ['create view', 'as'] } diff --git a/miniDB/btree.py b/miniDB/btree.py index f0676209..47c28636 100644 --- a/miniDB/btree.py +++ b/miniDB/btree.py @@ -297,9 +297,9 @@ def find(self, operator, value): # if the element exist, append to list, else pass and return try: results.append(target_node.ptrs[target_node.values.index(value)]) - # print('Found') + #print('Found') except: - # print('Not found') + #print('Not found') pass # for all other ops, the code is the same, only the operations themselves and the sibling indexes change diff --git a/miniDB/database.py b/miniDB/database.py index a3ac6be7..1cc64e6f 100644 --- a/miniDB/database.py +++ b/miniDB/database.py @@ -15,6 +15,7 @@ from btree import Btree from misc import split_condition from table import Table +from hash import Hash # readline.clear_history() @@ -23,6 +24,7 @@ class Database: ''' Main Database class, containing tables. ''' + def __init__(self, name, load=True, verbose = True): self.tables = {} @@ -54,7 +56,7 @@ def __init__(self, name, load=True, verbose = True): self.create_table('meta_length', 'table_name,no_of_rows', 'str,int') self.create_table('meta_locks', 'table_name,pid,mode', 'str,int,str') self.create_table('meta_insert_stack', 'table_name,indexes', 'str,list') - self.create_table('meta_indexes', 'table_name,index_name', 'str,str') + self.create_table('meta_indexes', 'table_name,index_name,column_name,index_type', 'str,str,str,str') # add column_name and index_type self.save_database() def save_database(self): @@ -101,7 +103,7 @@ def _update(self): self._update_meta_insert_stack() - def create_table(self, name, column_names, column_types, primary_key=None, load=None): + def create_table(self, name, column_names, column_types, primary_key=None, unique_cols=None, load=None): # add UNIQUE constraint ''' 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 @@ -110,10 +112,12 @@ def create_table(self, name, column_names, column_types, primary_key=None, load= column_names: list. Names of columns. column_types: list. Types of columns. primary_key: string. The primary key (if it exists). + unique_cols: list. Columns with UNIQUE constraint 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(','), + unique_cols=unique_cols.split(',') if unique_cols is not None else None ,primary_key=primary_key, load=load)}) # add unique_cols parameter # 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 @@ -160,7 +164,7 @@ def drop_table(self, table_name): self.save_database() - def import_table(self, table_name, filename, column_types=None, primary_key=None): + def import_table(self, table_name, filename, column_types=None, primary_key=None, unique_cols=None): ''' Creates table from CSV file. @@ -177,7 +181,7 @@ def import_table(self, table_name, filename, column_types=None, primary_key=None 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) + self.create_table(name=table_name, column_names=colnames, column_types=column_types, unique_cols=unique_cols, primary_key=primary_key) lock_ownership = self.lock_table(table_name, mode='x') first_line = False continue @@ -357,21 +361,82 @@ def select(self, columns, table_name, condition, distinct=None, order_by=None, \ if isinstance(table_name,Table): return table_name._select_where(columns, condition, distinct, order_by, desc, limit) + condition_column = '' if condition is not None: - condition_column = split_condition(condition)[0] + if 'or' in condition.split(): + condition_column = condition.split(" ")[0] + elif 'and' in condition.split(): + 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): return - if self._has_index(table_name) and condition_column==self.tables[table_name].column_names[self.tables[table_name].pk_idx]: + 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) + index_type = self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name('index_type')[0] + idx = self._load_idx(index_name) + if index_type == 'btree': + try: + table = self.tables[table_name]._select_where_with_btree(columns, idx, condition, distinct, order_by, desc, limit) + except: + try: + table = self.tables[table_name]._select_where_with_hash(columns, idx, condition, distinct, order_by, desc, limit) + except: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + elif index_type == 'hash': + try: + table = self.tables[table_name]._select_where_with_hash(columns, idx, condition, distinct, order_by, desc, limit) + except: + try: + table = self.tables[table_name]._select_where_with_btree(columns, idx, condition, distinct, order_by, desc, limit) + except: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + else: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + + if self._has_index(table_name) and condition_column in self.tables[table_name].unique_cols and condition_column != '': + index_name = self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name('index_name')[0] + index_type = self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name('index_type')[0] + idx = self._load_idx(index_name) + if index_type == 'btree': + try: + table = self.tables[table_name]._select_where_with_btree(columns, idx, condition, distinct, order_by, desc, limit) + except: + try: + table = self.tables[table_name]._select_where_with_hash(columns, idx, condition, distinct, order_by, desc, limit) + except: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + elif index_type == 'hash': + try: + table = self.tables[table_name]._select_where_with_hash(columns, idx, condition, distinct, order_by, desc, limit) + except: + try: + table = self.tables[table_name]._select_where_with_btree(columns, idx, condition, distinct, order_by, desc, limit) + except: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) else: table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + '''if self._has_index(table_name) and any(condition_column in x for x in self.tables[table_name].unique_cols) and condition_column != '': + index_name = self.select('*', 'meta_indexes', f'table_name={table_name}', return_object=True).column_by_name('index_name')[0] + print(index_name) + bt = self._load_idx(index_name) + print("ok") + #table = self.tables[table_name]._select_where_with_btree(columns, bt, condition, distinct, order_by, desc, limit) + #table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + try: + table = self.tables[table_name]._select_where_with_btree(columns, bt, condition, distinct, order_by, desc, limit) + print("Success") + # table.show() + #return table.show() + except: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit) + else: + table = self.tables[table_name]._select_where(columns, condition, distinct, order_by, desc, limit)''' + #return table # self.unlock_table(table_name) if save_as is not None: table._name = save_as @@ -650,7 +715,7 @@ def _update_meta_insert_stack_for_tb(self, table_name, new_stack): # indexes - def create_index(self, index_name, table_name, index_type='btree'): + def create_index(self, index_name, table_name, column_name, index_type): # add column_name constraint ''' 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). @@ -658,38 +723,74 @@ def create_index(self, index_name, table_name, index_type='btree'): 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() + column_name: string. Name of column with 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 column_name is not None: + if column_name in self.tables[table_name].unique_cols or column_name == self.tables[table_name].pk: + 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, column_name, 'btree']) + # crate the actual index + self._construct_index(table_name, index_name, column_name, index_type) + self.save_database() + elif index_type=="hash": + logging.info('Creating hash index') + self.tables['meta_indexes']._insert([table_name, index_name, column_name, 'hash']) + # crate the actual index + self._construct_index(table_name, index_name, column_name, index_type) + self.save_database() + else: + raise Exception('Cannot create index. Another index with the same name already exists.') + else: + raise Exception('Cannot create index. Table has no unique or primary key.') else: - raise Exception('Cannot create index. Another index with the same name already exists.') + 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.') - def _construct_index(self, table_name, index_name): + def _construct_index(self, table_name, index_name, column_name, index_type): # add column_name and index_type constraint ''' - Construct a btree on a table and save. + Construct a btree or a hash on a table and save. 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 + column_name: string. Name of column with index + index_type: string. Type of index + ''' + + if index_type == 'btree': + 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)): + if key is None: + continue + bt.insert(key, idx)''' + if column_name in self.tables[table_name].unique_cols or column_name == self.tables[table_name].pk: + for idx, key in enumerate(self.tables[table_name].column_by_name(column_name)): + if key is None: + continue + bt.insert(key, idx) + bt.show() + print("Index: " + index_name + " created on " + column_name + " column" ) + # save the btree + self._save_index(index_name, bt) + elif index_type == 'hash': + hs = Hash(2) + if column_name in self.tables[table_name].unique_cols or column_name == self.tables[table_name].pk: + for idx, key in enumerate(self.tables[table_name].column_by_name(column_name)): + if key is None: + continue + hs.insert(key, idx) + hs.show() + print("Index: " + index_name + " created on " + column_name + " column" ) + self._save_index(index_name, hs) - # 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)): - if key is None: - continue - bt.insert(key, idx) - # save the btree - self._save_index(index_name, bt) def _has_index(self, table_name): diff --git a/miniDB/hash.py b/miniDB/hash.py new file mode 100644 index 00000000..3c918eb7 --- /dev/null +++ b/miniDB/hash.py @@ -0,0 +1,83 @@ + +'''The following code defines a hash table data structure +that uses the LSB variant hash function to generate a hash value for a given key. +The hash table is implemented using a list of buckets, +where each bucket contains a list of key-value pairs.''' + +class Hash: + def __init__(self, size): + self.size = size + self.table = [None] * size + + def hash_function(self, value): + """Calculate the hash value for a given value.""" + # convert the value to an integer using the built-in hash function + value_hash = hash(value) + # apply the LSB variant hash function using bitwise AND + hash_val = value_hash & (2**self.size - 1) + # handle the case where the hash value is larger than the size of the hash table + if hash_val >= self.size: + hash_val = hash_val % self.size + return hash_val + + def insert(self, key, value): + index = self.hash_function(key) + if self.table[index] is None: + self.table[index] = Bucket() + self.table[index].insert(key, value) + + def delete(self, key): + index = self.hash_function(key) + if self.table[index] is None: + return False + else: + return self.table[index].delete(key) + + def select(self, key): + index = self.hash_function(key) + if self.table[index] is None: + return None + else: + return self.table[index].select(key) + + def show(self): + for i in range(self.size): + bucket = self.table[i] + if bucket is None: + print(f'[{i}]') + else: + print(f'[{i}]', end=' ') + for k, v in bucket.items: + print(f'({k}: {v})', end=' ') + print() + + def get(self, key): + index = self.hash_function(key) + return self.table[index].get(key) + + +class Bucket: + def __init__(self): + self.items = [] + + def insert(self, key, value): + self.items.append((key, value)) + + def delete(self, key): + for i, (k, v) in enumerate(self.items): + if k == key: + del self.items[i] + return True + return False + + def select(self, key): + for k, v in self.items: + if k == key: + return v + return None + + def get(self, index): + if index < len(self.items): + return self.items[index][1] + else: + return None diff --git a/miniDB/misc.py b/miniDB/misc.py index aefada74..cf50eb15 100644 --- a/miniDB/misc.py +++ b/miniDB/misc.py @@ -8,7 +8,8 @@ def get_op(op, a, b): '<': operator.lt, '>=': operator.ge, '<=': operator.le, - '=': operator.eq} + '=': operator.eq, + 'not': operator.ne} try: return ops[op](a,b) @@ -20,8 +21,21 @@ def split_condition(condition): '<=': operator.le, '=': operator.eq, '>': operator.gt, - '<': operator.lt} + '<': operator.lt, + 'not': operator.ne} + # NOT operator + if 'not' in condition: + condition=condition.replace('not','') # remove NOT from condition + for op_key in ops.keys(): + if op_key in condition: + condition=condition.replace(op_key,not_op(op_key)) # reverse the condition + + # BETWEEN operator + if 'between' in condition: + splt=condition.split() + return splt[0].strip() # return only column name + for op_key in ops.keys(): splt=condition.split(op_key) if len(splt)>1: @@ -37,6 +51,16 @@ def split_condition(condition): return left, op_key, right + +def not_op(op): + return { + '>' : '<=', + '<' : '>=', + '<=' : '>', + '>=' : '<', + '=': '<>' + }.get(op) + def reverse_op(op): ''' Reverse the operator given diff --git a/miniDB/table.py b/miniDB/table.py index f5c7d937..96ffc170 100644 --- a/miniDB/table.py +++ b/miniDB/table.py @@ -18,6 +18,7 @@ class Table: - column names (list of strings) - column types (list of functions like str/int etc) - primary (name of the primary key column) + - unique (name of unique column) OR @@ -26,7 +27,7 @@ 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): + def __init__(self, name=None, column_names=None, column_types=None, primary_key=None, load=None, unique_cols=None): # add UNIQUE constraint if load is not None: # if load is a dict, replace the object dict with it (replaces the object with the specified one) @@ -60,8 +61,18 @@ def __init__(self, name=None, column_names=None, column_types=None, primary_key= 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. - + #print("UNIQUE:") + #print( unique_cols) + if (unique_cols is not None): + self.unique_cols = unique_cols + #self.uk_idx = self.column_names.index(unique_cols) + else: + self.unique_cols = [] + #self.uk_idx = None + self.uk = unique_cols # if primary key is set, keep its index as an attribute + #print("PK:") + #print(primary_key) if primary_key is not None: self.pk_idx = self.column_names.index(primary_key) else: @@ -115,17 +126,31 @@ def _insert(self, row, insert_stack=[]): for i in range(len(row)): # for each value, cast and replace it in row. - try: + '''try: row[i] = self.column_types[i](row[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) + print(exc)''' + + if self._name[:4] != 'meta': # check whether the current column being evaluated is a metadata column + is_unique = self.column_names[i] in self.unique_cols # If the column has the UNIQUE constraint + # print(is_unique) + is_duplicate = str(row[i]) in [str(val) for val in self.column_by_name(self.column_names[i])] # Check if the value is already in the table + #print(is_duplicate) + if (is_unique and is_duplicate): + err_msg = f'ERROR -> Value "{str(row[i])}" already exists in column "{self.column_names[i]}" that has the UNIQUE constraint.' + print(err_msg) + raise ValueError(err_msg) + + row[i] = self.column_types[i](row[i]) + # 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): + print(f'ERROR -> Value {row[i]} already exists in primary key column.') 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.') @@ -233,11 +258,51 @@ def _select_where(self, return_columns, condition=None, distinct=False, order_by # if condition is None, return all rows # if not, return the rows with values where condition is met for value 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)] + if 'between' in condition: + splt=condition.split() + condition1 = splt[0].strip() + '>=' + splt[2].strip() + column_name, operator1, value1 = self._parse_condition(condition1) + condition2 = splt[0].strip() + '<=' + splt[4].strip() + column_name, operator2, value2 = self._parse_condition(condition2) + column = self.column_by_name(column_name) + rows = [ind for ind, x in enumerate(column) if get_op(operator1, x, value1) and get_op(operator2, x, value2)] + elif 'and' in condition: + cond1 = condition.split("and") + condition = cond1[0] + column_name, operator, value = self._parse_condition(condition) + column = self.column_by_name(column_name) + rows1 = [ind for ind, x in enumerate(column) if get_op(operator,x,value)] + + condition1 = cond1[1] + column_name1, operator1, value1 = self._parse_condition(condition1) + column1 = self.column_by_name(column_name1) + rows2 = [ind for ind, x in enumerate(column1) if get_op(operator1, x, value1)] + rows = set(rows1).intersection(rows2) + elif 'or' in condition.split(): + c1 = condition.split("or") + condition = c1[0] + column_name, operator, value = self._parse_condition(condition) + column = self.column_by_name(column_name) + rows1 = [ind for ind, x in enumerate(column) if get_op(operator, x, value)] + + condition1 = c1[1] + column_name1, operator1, value1 = self._parse_condition(condition1) + column1 = self.column_by_name(column_name1) + rows2 = [ind for ind, x in enumerate(column1) if get_op(operator1, x, value1)] + + rows3 = [rows1,rows2] + rows = [] + for i in rows3: + for j in i: + if not (j in rows): + rows.append(j) + + else: + 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: - rows = [i for i in range(len(self.data))] + 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) 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()} @@ -269,6 +334,63 @@ def _select_where(self, return_columns, condition=None, distinct=False, order_by return s_table + def _select_where_with_hash(self, return_columns, hs, condition, distinct=False, order_by=None, desc=True, limit=None): + # if * return all columns, else find the column indexes for the columns specified + if return_columns == '*': + return_cols = [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) + rows = [] + # Check if the operator is = + if operator != "=": + column = self.column_by_name(column_name) + opsseq = 0 + for ind, x in enumerate(column): + opsseq+=1 + if get_op(operator, x, value): + rows.append(ind) + '''else: + ind = hs.get(value) + rows.append(ind)''' + + column = self.column_by_name(column_name) + opsseq = 0 + for ind, x in enumerate(column): + opsseq+=1 + if get_op(operator, x, value): + rows.append(ind) + + # same as simple select from now on + + try: + k = int(limit) + except TypeError: + k = None + + # same as simple select from now on + rows = rows[:k] + 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 + + if order_by: + s_table.order_by(order_by, desc) + + if isinstance(limit,str): + s_table.data = [row for row in s_table.data if row is not None][:int(limit)] + + + print("Hash select") + return s_table + def _select_where_with_btree(self, return_columns, bt, condition, distinct=False, order_by=None, desc=True, limit=None): @@ -282,8 +404,8 @@ def _select_where_with_btree(self, return_columns, bt, condition, distinct=False 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') + '''if column_name != self.column_names[self.pk_idx] or column_name not in self.unique_cols: + print('Column is not PK or UNIQUE. 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) @@ -320,8 +442,9 @@ def _select_where_with_btree(self, return_columns, bt, condition, distinct=False s_table.order_by(order_by, desc) if isinstance(limit,str): - s_table.data = [row for row in s_table.data if row is not None][:int(limit)] + s_table.data = [row for row in s_table.data if any(row)][:int(limit)] + print("Btree select") return s_table def order_by(self, column_name, desc=True):