From 51f85e6daf736803bf34b68d4574a8137c1df57f Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sat, 15 Aug 2020 21:08:54 +0200 Subject: [PATCH 01/32] CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 8e2454e..5af3e32 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 3.0 +- Types are now inferred by column, no longer by cell - Relations now use frozenset internally and are immutable - Refactored parser to use better typing - Refactored and fixed some optimizations From c61cea7806644bee87248b83213203b0f48ed0ed Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sat, 15 Aug 2020 22:41:13 +0200 Subject: [PATCH 02/32] Remove unused code --- relational_gui/creator.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/relational_gui/creator.py b/relational_gui/creator.py index 412cd22..5b7a32f 100644 --- a/relational_gui/creator.py +++ b/relational_gui/creator.py @@ -1,5 +1,5 @@ # Relational -# Copyright (C) 2008-2015 Salvo "LtWorf" Tomaselli +# Copyright (C) 2008-2020 Salvo "LtWorf" Tomaselli # # Relational is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -142,11 +142,3 @@ def edit_relation(rel=None): Form.exec_() return Form.result_relation - - -if __name__ == '__main__': - import sys - app = QtGui.QApplication(sys.argv) - r = relation.relation( - "/home/salvo/dev/relational/trunk/samples/people.csv") - print (edit_relation(r)) From c246c0715d2068ff2348170a86965cfcc93092e7 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Tue, 18 Aug 2020 15:38:08 +0200 Subject: [PATCH 03/32] Remove useless operations --- relational/relation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index 8c8d1a6..abe2173 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -379,12 +379,12 @@ class Relation(NamedTuple): res = "" for f, attr in enumerate(self.header): - res += "%s" % (attr.ljust(2 + m_len[f])) + res += attr.ljust(2 + m_len[f]) for r in self.content: res += "\n" for col, i in enumerate(r): - res += "%s" % (i.ljust(2 + m_len[col])) + res += i.ljust(2 + m_len[col]) return res From aea763021c248e224bd6fb4e965da0ca9362a2ac Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Tue, 18 Aug 2020 16:08:14 +0200 Subject: [PATCH 04/32] Remove useless new object --- relational/relation.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index abe2173..be6c1a0 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -129,8 +129,6 @@ class Relation(NamedTuple): ''' Selection, expr must be a valid Python expression; can contain field names. ''' - header = Header(self.header) - try: c_expr = compile(expr, 'selection', 'eval') except: @@ -148,7 +146,7 @@ class Relation(NamedTuple): content.append(i) except Exception as e: raise Exception(f'Failed to evaluate {expr}\n{e}') - return Relation(header, frozenset(content)) + return Relation(self.header, frozenset(content)) def product(self, other: 'Relation') -> 'Relation': ''' From 5259921bb18b68e6292fc5d24e049e1af1ebb603 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Tue, 18 Aug 2020 16:41:39 +0200 Subject: [PATCH 05/32] Better error message --- relational/relation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relational/relation.py b/relational/relation.py index be6c1a0..0205f72 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -145,7 +145,7 @@ class Relation(NamedTuple): if eval(c_expr, attributes): content.append(i) except Exception as e: - raise Exception(f'Failed to evaluate {expr}\n{e}') + raise Exception(f'Failed to evaluate {expr} with {attributes}\n{e}') return Relation(self.header, frozenset(content)) def product(self, other: 'Relation') -> 'Relation': From f410b112af71ca0ba569332fb38e3e9082d724ff Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:11:05 +0200 Subject: [PATCH 06/32] Use the new by column type detection --- relational/relation.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index 0205f72..ee24f50 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -58,7 +58,7 @@ class Relation(NamedTuple): method. ''' header: 'Header' - content: FrozenSet[Tuple[Rstring, ...]] + content: FrozenSet[Tuple[CastValue, ...]] @staticmethod def load(filename: Union[str, Path]) -> 'Relation': @@ -78,13 +78,24 @@ class Relation(NamedTuple): Iterator for the header, and iterator for the content. ''' header = Header(header) - r_content: List[Tuple[Rstring, ...]] = [] + r_content: List[Tuple[CastValue, ...]] = [] + guessed_types = list(repeat({Rdate, float, int, str}, len(header))) + for row in content: - content_row: Tuple[Rstring, ...] = tuple(Rstring(i) for i in row) - if len(content_row) != len(header): + if len(row) != len(header): raise ValueError(f'Line {row} contains an incorrect amount of values') - r_content.append(content_row) - return Relation(header, frozenset(r_content)) + r_content.append(row) + + # Guess types + for i, value in enumerate(row): + guessed_types[i] = guessed_types[i].intersection(guess_type(value)) + + typed_content = [] + for r in r_content: + t = tuple(cast(v, guessed_types[i]) for i, v in enumerate(r)) + typed_content.append(t) + + return Relation(header, frozenset(typed_content)) def __iter__(self): From 73aa0dd2a1cb66538c745e75bd63d6ba6da87914 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:12:56 +0200 Subject: [PATCH 07/32] New code to guess types --- relational/rtypes.py | 119 +++++++++++++------------------------------ 1 file changed, 34 insertions(+), 85 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index 1bc4970..419f2f5 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -23,98 +23,47 @@ import datetime import keyword import re -from typing import Union +from typing import Union, Set, Any, Callable + RELATION_NAME_REGEXP = re.compile(r'^[_a-z][_a-z0-9]*$', re.IGNORECASE) - - -class Rstring(str): - - '''String subclass with some custom methods''' - - int_regexp = re.compile(r'^[\+\-]{0,1}[0-9]+$') - float_regexp = re.compile(r'^[\+\-]{0,1}[0-9]+(\.([0-9])+)?$') - date_regexp = re.compile( +_date_regexp = re.compile( r'^([0-9]{1,4})(\\|-|/)([0-9]{1,2})(\\|-|/)([0-9]{1,2})$' ) - - def autocast(self) -> Union[int, float, 'Rdate', 'Rstring']: - ''' - Returns the automatic cast for this - value. - ''' - try: - return self._autocast - except: - pass - - self._autocast = self # type: Union[int, float, 'Rdate', 'Rstring'] - if len(self) > 0: - if self.isInt(): - self._autocast = int(self) - elif self.isFloat(): - self._autocast = float(self) - elif self.isDate(): - self._autocast = Rdate(self) - return self._autocast - - def isInt(self) -> bool: - '''Returns true if the string represents an int number - it only considers as int numbers the strings matching - the following regexp: - r'^[\+\-]{0,1}[0-9]+$' - ''' - return Rstring.int_regexp.match(self) is not None - - def isFloat(self) -> bool: - '''Returns true if the string represents a float number - it only considers as float numbers, the strings matching - the following regexp: - r'^[\+\-]{0,1}[0-9]+(\.([0-9])+)?$' - ''' - return Rstring.float_regexp.match(self) is not None - - def isDate(self) -> bool: - '''Returns true if the string represents a date, - in the format YYYY-MM-DD. as separators '-' , '\', '/' are allowed. - As side-effect, the date object will be stored for future usage, so - no more parsings are needed - ''' - try: - return self._isdate # type: ignore - except: - pass - - r = Rstring.date_regexp.match(self) - if r is None: - self._isdate = False - self._date = None - return False - - try: # Any of the following operations can generate an exception, if it happens, we aren't dealing with a date - year = int(r.group(1)) - month = int(r.group(3)) - day = int(r.group(5)) - d = datetime.date(year, month, day) - self._isdate = True - self._date = d - return True - except: - self._isdate = False - self._date = None - return False - - def getDate(self): - '''Returns the datetime.date object or None''' - try: - return self._date - except: - self.isDate() - return self._date +CastValue = Union[str, int, float, 'Rdate'] -class Rdate (object): +def guess_type(value: str) -> Set[Callable[[Any], Any]]: + r: Set[Callable[[Any], Any]] = {str} + if _date_regexp.match(value) is not None: + r.add(Rdate) + try: + int(value) + r.add(int) + except ValueError: + pass + + try: + float(value) + r.add(float) + except ValueError: + pass + return r + + +def cast(value: str, guesses: Set) -> CastValue: + if int in guesses: + return int(value) + if Rdate in guesses: + print(repr(value), guesses) + return Rdate(value) + if float in guesses: + return float(value) + return value + + +class Rdate(datetime.date): '''Represents a date''' def __init__(self, date): From 7800a157bf1b4526aaa240fce504f3264340160c Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:24:42 +0200 Subject: [PATCH 08/32] Convert Rdate object to keep working --- relational/rtypes.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index 419f2f5..c6bf33c 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -63,19 +63,25 @@ def cast(value: str, guesses: Set) -> CastValue: return value -class Rdate(datetime.date): +class Rdate: '''Represents a date''' def __init__(self, date): '''date: A string representing a date''' - if not isinstance(date, Rstring): - date = Rstring(date) + r = _date_regexp.match(date) + if not r: + raise ValueError(f'{date} is not a valid date') - self.intdate = date.getDate() - self.day = self.intdate.day - self.month = self.intdate.month - self.weekday = self.intdate.weekday() - self.year = self.intdate.year + year = int(r.group(1)) + month = int(r.group(3)) + day = int(r.group(5)) + d = datetime.date(year, month, day) + + self.intdate = d + self.day = d.day + self.month = d.month + self.weekday = d.weekday() + self.year = d.year def __hash__(self): return self.intdate.__hash__() From 5c8cbde27ab1a1d8d6e943d1d4a4ae9b21d43c15 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:25:21 +0200 Subject: [PATCH 09/32] No autocasts needed All casting is done when loading --- relational/relation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relational/relation.py b/relational/relation.py index ee24f50..7dd8cae 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -148,7 +148,7 @@ class Relation(NamedTuple): content = [] for i in self.content: # Fills the attributes dictionary with the values of the tuple - attributes = {attr: i[j].autocast() + attributes = {attr: i[j] for j, attr in enumerate(self.header) } From 90b3163e32c7fea7907b23599cc2a78f1529b9b2 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:25:40 +0200 Subject: [PATCH 10/32] Add the --- token bare --- relational/relation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relational/relation.py b/relational/relation.py index 7dd8cae..f0770ff 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -319,7 +319,7 @@ class Relation(NamedTuple): added = True # If it didn't partecipate, adds it if not added: - item = chain(i, repeat(Rstring('---'), len(noid))) + item = chain(i, repeat('---', len(noid))) #FIXME content.append(tuple(item)) return Relation(header, frozenset(content)) From 69f7e83626ffcdd2f0e835a9a19958f4d83eafb5 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:25:52 +0200 Subject: [PATCH 11/32] Fix printing --- relational/relation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index f0770ff..709230f 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -382,7 +382,7 @@ class Relation(NamedTuple): m_len = [len(i) for i in self.header] # Maximum lenght string for f in self.content: - for col, i in enumerate(f): + for col, i in enumerate(str(val) for val in f): if len(i) > m_len[col]: m_len[col] = len(i) @@ -392,7 +392,7 @@ class Relation(NamedTuple): for r in self.content: res += "\n" - for col, i in enumerate(r): + for col, i in enumerate(str(val) for val in r): res += i.ljust(2 + m_len[col]) return res From b86c0c6a615617589fe02d7d28e35ff87ea9aa4c Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:30:15 +0200 Subject: [PATCH 12/32] Remove leftover print --- relational/rtypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index c6bf33c..a7bfa76 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -56,7 +56,6 @@ def cast(value: str, guesses: Set) -> CastValue: if int in guesses: return int(value) if Rdate in guesses: - print(repr(value), guesses) return Rdate(value) if float in guesses: return float(value) From b69fd0980bf783ddd410e6a8155745ae1631df99 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:32:22 +0200 Subject: [PATCH 13/32] Do not clash names --- relational/relation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relational/relation.py b/relational/relation.py index 709230f..c6a82ff 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -22,7 +22,7 @@ import csv from itertools import chain, repeat, product as iproduct from collections import deque -from typing import * +from typing import NamedTuple, FrozenSet, Iterable, List, Dict, Tuple from pathlib import Path from relational.rtypes import * From 774c8eed67a4264c38811cf5d9b92000b4d94e14 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:41:24 +0200 Subject: [PATCH 14/32] Compare to anything --- relational/rtypes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/relational/rtypes.py b/relational/rtypes.py index a7bfa76..7108f7b 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -93,6 +93,8 @@ class Rdate: return Rdate(res.__str__()) def __eq__(self, other): + if not isinstance(other, Rdate): + return False return self.intdate == other.intdate def __ge__(self, other): From 4dc7f131f34486afa2bc4e87de77677f146b64e2 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 08:42:55 +0200 Subject: [PATCH 15/32] Only use eq --- relational/rtypes.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index 7108f7b..80b3373 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -109,9 +109,6 @@ class Rdate: def __lt__(self, other): return self.intdate < other.intdate - def __ne__(self, other): - return self.intdate != other.intdate - def __sub__(self, other): return (self.intdate - other.intdate).days From 18d6f5ebfb4de87307e02ab1117ff2365f13003d Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Wed, 19 Aug 2020 10:49:54 +0200 Subject: [PATCH 16/32] Remove add and sub from Rdate They were not symmetrical, better to remove them. --- CHANGELOG | 1 + relational/rtypes.py | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5af3e32..29cd016 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 3.0 +- Dates can no longer be added or subtracted - Types are now inferred by column, no longer by cell - Relations now use frozenset internally and are immutable - Refactored parser to use better typing diff --git a/relational/rtypes.py b/relational/rtypes.py index 80b3373..26914dc 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -88,10 +88,6 @@ class Rdate: def __str__(self): return self.intdate.__str__() - def __add__(self, days): - res = self.intdate + datetime.timedelta(days) - return Rdate(res.__str__()) - def __eq__(self, other): if not isinstance(other, Rdate): return False @@ -109,9 +105,6 @@ class Rdate: def __lt__(self, other): return self.intdate < other.intdate - def __sub__(self, other): - return (self.intdate - other.intdate).days - def is_valid_relation_name(name: str) -> bool: '''Checks if a name is valid for a relation. From 33a2a21617ce09cf49a883a424844e30649a290d Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sun, 23 Aug 2020 10:50:51 +0200 Subject: [PATCH 17/32] Fix types --- relational/relation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index c6a82ff..87dc90b 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -73,12 +73,12 @@ class Relation(NamedTuple): return Relation.create_from(header, reader) @staticmethod - def create_from(header: Iterable[str], content: Iterable[Iterable[str]]) -> 'Relation': + def create_from(header: Iterable[str], content: Iterable[List[str]]) -> 'Relation': ''' Iterator for the header, and iterator for the content. ''' header = Header(header) - r_content: List[Tuple[CastValue, ...]] = [] + r_content = [] guessed_types = list(repeat({Rdate, float, int, str}, len(header))) for row in content: From 4c101526e62500bd68902851e30a7a0fd25cc883 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sun, 23 Aug 2020 11:16:12 +0200 Subject: [PATCH 18/32] Use None instead o a special string --- relational/relation.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index 87dc90b..832afe1 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -276,9 +276,7 @@ class Relation(NamedTuple): def outer_right(self, other: 'Relation') -> 'Relation': ''' Outer right join. Considers self as left and param as right. If the - tuple has no corrispondence, empy attributes are filled with a "---" - string. This is due to the fact that the None token would cause - problems when saving and reloading the relation. + tuple has no corrispondence, empy attributes are filled with a None. Just like natural join, it works considering shared attributes. ''' return other.outer_left(self) @@ -287,7 +285,6 @@ class Relation(NamedTuple): ''' See documentation for outer_right ''' - shared = self.header.intersection(other.header) # Creating the header with all the fields, done like that because order is @@ -319,7 +316,7 @@ class Relation(NamedTuple): added = True # If it didn't partecipate, adds it if not added: - item = chain(i, repeat('---', len(noid))) #FIXME + item = chain(i, repeat(None, len(noid))) #FIXME content.append(tuple(item)) return Relation(header, frozenset(content)) From 5869f5d421f435dbc6c8e13f704867737853337c Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sun, 23 Aug 2020 11:16:48 +0200 Subject: [PATCH 19/32] Visually show the different types --- relational_gui/guihandler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/relational_gui/guihandler.py b/relational_gui/guihandler.py index da34184..ac4d7a0 100644 --- a/relational_gui/guihandler.py +++ b/relational_gui/guihandler.py @@ -249,7 +249,13 @@ class relForm(QtWidgets.QMainWindow): for i in rel.content: item = QtWidgets.QTreeWidgetItem() for j,k in enumerate(i): - item.setText(j, k) + if k is None: + item.setBackground(j, QtGui.QBrush(QtCore.Qt.darkRed, QtCore.Qt.Dense4Pattern)) + elif isinstance(k, (int, float)): + item.setForeground(j, QtGui.QPalette().link()) + elif not isinstance(k, str): + item.setForeground(j, QtGui.QPalette().brightText()) + item.setText(j, str(k)) self.ui.table.addTopLevelItem(item) # Sets columns From ff9f0c10b6dfbeb27545b197f1ffbdfea33b4339 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Sun, 23 Aug 2020 11:25:50 +0200 Subject: [PATCH 20/32] Do not crash when editing relations containing None --- relational_gui/creator.py | 8 ++++---- relational_gui/guihandler.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/relational_gui/creator.py b/relational_gui/creator.py index 5b7a32f..42b87f5 100644 --- a/relational_gui/creator.py +++ b/relational_gui/creator.py @@ -52,13 +52,13 @@ class creatorForm(QtWidgets.QDialog): for i in rel.content: self.table.insertRow(self.table.rowCount()) - for j in range(len(i)): + for j, value in enumerate(i): + if value is None: + raise Exception('Relation contains a None value and cannot be edited from the GUI') item = QtWidgets.QTableWidgetItem() - item.setText(i[j]) + item.setText(str(value)) self.table.setItem(self.table.rowCount() - 1, j, item) - pass - def setup_empty(self): self.table.insertColumn(0) self.table.insertColumn(0) diff --git a/relational_gui/guihandler.py b/relational_gui/guihandler.py index ac4d7a0..a77b1c5 100644 --- a/relational_gui/guihandler.py +++ b/relational_gui/guihandler.py @@ -312,9 +312,15 @@ class relForm(QtWidgets.QMainWindow): def editRelation(self): from relational_gui import creator for i in self.ui.lstRelations.selectedItems(): - result = creator.edit_relation( - self.user_interface.get_relation(i.text()) - ) + try: + result = creator.edit_relation( + self.user_interface.get_relation(i.text()) + ) + except Exception as e: + QtWidgets.QMessageBox.warning( + self, QtWidgets.QApplication.translate("Form", "Error"), str(e) + ) + return if result != None: self.user_interface.set_relation(i.text(), result) self.updateRelations() From 2e5d18f418412dae43c91ade72da2bcb74ec22aa Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 17:38:16 +0200 Subject: [PATCH 21/32] Switch to dataclass NamedTuple has some bug that makes dumping broken --- relational/relation.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index 832afe1..51e3e5a 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -22,7 +22,8 @@ import csv from itertools import chain, repeat, product as iproduct from collections import deque -from typing import NamedTuple, FrozenSet, Iterable, List, Dict, Tuple +from typing import FrozenSet, Iterable, List, Dict, Tuple +from dataclasses import dataclass from pathlib import Path from relational.rtypes import * @@ -33,8 +34,8 @@ __all__ = [ 'Header', ] - -class Relation(NamedTuple): +@dataclass(repr=True, unsafe_hash=False, frozen=True) +class Relation: ''' This object defines a relation (as a group of consistent tuples) and operations. From c4bc74d61253734df2c3adee37ee6839b5070665 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 17:55:13 +0200 Subject: [PATCH 22/32] Add a new json format --- driver.py | 4 +-- relational/maintenance.py | 19 +++++++++--- relational/relation.py | 62 +++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/driver.py b/driver.py index df92aa2..acdb381 100755 --- a/driver.py +++ b/driver.py @@ -57,7 +57,7 @@ def load_relations(): print ("Loading relation %s with name %s..." % (i, relname)) - rels[relname] = relation.Relation.load('%s%s' % (examples_path, i)) + rels[relname] = relation.Relation.load_csv('%s%s' % (examples_path, i)) print('done') @@ -239,7 +239,7 @@ def run_test(testname): o_result = None try: - result_rel = relation.Relation.load('%s%s.result' % (tests_path, testname)) + result_rel = relation.Relation.load_csv('%s%s.result' % (tests_path, testname)) query = readfile('%s%s.query' % (tests_path, testname)).strip() o_query = optimizer.optimize_all(query, rels) diff --git a/relational/maintenance.py b/relational/maintenance.py index 5afa527..d50c3b4 100644 --- a/relational/maintenance.py +++ b/relational/maintenance.py @@ -90,8 +90,15 @@ class UserInterface: def load(self, filename: str, name: str) -> None: '''Loads a relation from file, and gives it a name to - be used in subsequent queries.''' - rel = Relation.load(filename) + be used in subsequent queries. + + Files ending with .csv are loaded as csv, the others are + loaded as json. + ''' + if filename.endswith('.csv'): + rel = Relation.load_csv(filename) + else: + rel = Relation.load(filename) self.set_relation(name, rel) def unload(self, name: str) -> None: @@ -161,8 +168,12 @@ class UserInterface: if len(name) == 0: return None - if (name.endswith(".csv")): # removes the extension - name = name[:-4] + # Removing the extension + try: + pos = name.rindex('.') + except ValueError: + return None + name = name[:pos] if not is_valid_relation_name(name): return None diff --git a/relational/relation.py b/relational/relation.py index 51e3e5a..01a3a4d 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -19,7 +19,6 @@ # This module provides a classes to represent relations and to perform # relational operations on them. -import csv from itertools import chain, repeat, product as iproduct from collections import deque from typing import FrozenSet, Iterable, List, Dict, Tuple @@ -62,21 +61,62 @@ class Relation: content: FrozenSet[Tuple[CastValue, ...]] @staticmethod - def load(filename: Union[str, Path]) -> 'Relation': + def load_csv(filename: Union[str, Path]) -> 'Relation': ''' Load a relation object from a csv file. The 1st row is the header and the other rows are the content. + + Types will be inferred automatically ''' + import csv with open(filename) as fp: reader = csv.reader(fp) # Creating a csv reader header = Header(next(reader)) # read 1st line return Relation.create_from(header, reader) + @staticmethod + def load(filename: Union[str, Path]) -> 'Relation': + ''' + Load a relation object from a json file. + ''' + with open(filename) as fp: + from json import load as jload + from typedload import load + return load(jload(fp), Relation) + + def save(self, filename: Union[Path, str]) -> None: + ''' + Saves the relation in a file. + Will save using the json format + ''' + with open(filename, 'w') as fp: + from json import dump as jdump + from typedload import dump + jdump(dump(self), fp) + + def save_csv(self, filename: Union[Path, str]) -> None: + ''' + Saves the relation in a file. Will save using the csv + format as defined in RFC4180. + ''' + import csv + with open(filename, 'w') as fp: + writer = csv.writer(fp) # Creating csv writer + + # It wants an iterable containing iterables + head = (self.header,) + writer.writerows(head) + + # Writing content, already in the correct format + writer.writerows(self.content) + @staticmethod def create_from(header: Iterable[str], content: Iterable[List[str]]) -> 'Relation': ''' Iterator for the header, and iterator for the content. + + This will infer types. ''' header = Header(header) r_content = [] @@ -105,22 +145,6 @@ class Relation: def __contains__(self, key): return key in self.content - def save(self, filename: Union[Path, str]) -> None: - ''' - Saves the relation in a file. Will save using the csv - format as defined in RFC4180. - ''' - - with open(filename, 'w') as fp: - writer = csv.writer(fp) # Creating csv writer - - # It wants an iterable containing iterables - head = (self.header,) - writer.writerows(head) - - # Writing content, already in the correct format - writer.writerows(self.content) - def _rearrange(self, other: 'Relation') -> 'Relation': '''If two relations share the same attributes in a different order, this method will use projection to make them have the same attributes' order. @@ -317,7 +341,7 @@ class Relation: added = True # If it didn't partecipate, adds it if not added: - item = chain(i, repeat(None, len(noid))) #FIXME + item = chain(i, repeat(None, len(noid))) content.append(tuple(item)) return Relation(header, frozenset(content)) From 4d81858c8b0cec4f533c8c48d0a099da55ebc368 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:01:08 +0200 Subject: [PATCH 23/32] Make Rdate into something typedload can handle --- relational/rtypes.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index 26914dc..14e6b97 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -24,6 +24,7 @@ import datetime import keyword import re from typing import Union, Set, Any, Callable +from dataclasses import dataclass RELATION_NAME_REGEXP = re.compile(r'^[_a-z][_a-z0-9]*$', re.IGNORECASE) @@ -56,17 +57,30 @@ def cast(value: str, guesses: Set) -> CastValue: if int in guesses: return int(value) if Rdate in guesses: - return Rdate(value) + return Rdate.create(value) if float in guesses: return float(value) return value +@dataclass(frozen=True) class Rdate: '''Represents a date''' + year: int + month: int + day: int - def __init__(self, date): - '''date: A string representing a date''' + @property + def intdate(self) -> datetime.date: + return datetime.date(self.year, self.month, self.day) + + @property + def weekday(self) -> int: + return self.intdate.weekday() + + @staticmethod + def create(date: str) -> 'Rdate': + '''date: A string representing a date YYYY-MM-DD''' r = _date_regexp.match(date) if not r: raise ValueError(f'{date} is not a valid date') @@ -74,25 +88,11 @@ class Rdate: year = int(r.group(1)) month = int(r.group(3)) day = int(r.group(5)) - d = datetime.date(year, month, day) - - self.intdate = d - self.day = d.day - self.month = d.month - self.weekday = d.weekday() - self.year = d.year - - def __hash__(self): - return self.intdate.__hash__() + return Rdate(year, month, day) def __str__(self): return self.intdate.__str__() - def __eq__(self, other): - if not isinstance(other, Rdate): - return False - return self.intdate == other.intdate - def __ge__(self, other): return self.intdate >= other.intdate From dac2d47246c7a6bed2d0eb443aa5525f0d51e0f2 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:02:39 +0200 Subject: [PATCH 24/32] Depend on typedload --- .travis.yml | 1 + debian/control | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 20d628d..d8f4fe7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ python: install: - pip install mypy - pip install xtermcolor + - pip install typedload script: - make mypy diff --git a/debian/control b/debian/control index d18b47f..6dc6df2 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: math Priority: optional Maintainer: Salvo 'LtWorf' Tomaselli Build-Depends: debhelper-compat (= 13), debhelper (>= 13), python3, dh-python, python3-xtermcolor, pyqt5-dev-tools, - python3-distutils + python3-distutils, python3-typedload Standards-Version: 4.5.0 X-Python3-Version: >= 3.8 Homepage: https://ltworf.github.io/relational/ @@ -12,7 +12,7 @@ Rules-Requires-Root: no Package: python3-relational Architecture: all Section: python -Depends: ${misc:Depends}, ${python3:Depends} +Depends: ${misc:Depends}, ${python3:Depends}, python3-typedload Description: Educational tool for relational algebra (standalone module) Relational is primarily a tool to provide a workspace for experimenting with relational algebra, an offshoot of first-order logic. From d592cc30507e8ec195f501b4fd9a35fb470527a7 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:37:19 +0200 Subject: [PATCH 25/32] Fix json loading With some small amount of error detection. --- relational/relation.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/relational/relation.py b/relational/relation.py index 01a3a4d..14c63fc 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -21,7 +21,7 @@ from itertools import chain, repeat, product as iproduct from collections import deque -from typing import FrozenSet, Iterable, List, Dict, Tuple +from typing import FrozenSet, Iterable, List, Dict, Tuple, Optional from dataclasses import dataclass from pathlib import Path @@ -83,7 +83,15 @@ class Relation: with open(filename) as fp: from json import load as jload from typedload import load - return load(jload(fp), Relation) + loaded = jload(fp) + header = Header(loaded['header']) + content = [] + for row in loaded['content']: + if len(row) != len(header): + raise ValueError(f'Line {row} contains an incorrect amount of values') + t_row: Tuple[Optional[Union[int, float, str, Rdate]], ...] = load(row, Tuple[Optional[Union[int, float, str, Rdate]], ...]) + content.append(t_row) + return Relation(header, frozenset(content)) def save(self, filename: Union[Path, str]) -> None: ''' From 4d280e02b03b57020119b7a5857b735eb5682cac Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:37:55 +0200 Subject: [PATCH 26/32] Keep tests in json format So the results are typed --- driver.py | 2 +- tests_dir/a_join_a.result | 10 +--------- tests_dir/a_joinf_a.result | 10 +--------- tests_dir/a_joinl_a.result | 10 +--------- tests_dir/a_joinr_a.result | 10 +--------- tests_dir/c_not_python.result | 5 +---- tests_dir/c_programmers.result | 7 +------ tests_dir/dates.result | 7 +------ tests_dir/dates_sel.result | 4 +--- tests_dir/fixed_len_name.result | 13 +----------- tests_dir/full_join.result | 20 +------------------ tests_dir/intersection1.result | 3 +-- tests_dir/intersection2.result | 3 +-- tests_dir/java_and_perl.result | 4 +--- tests_dir/join.result | 18 +---------------- tests_dir/left_join.result | 19 +----------------- tests_dir/max_rating_in_age_range.result | 3 +-- tests_dir/maxdate.result | 3 +-- tests_dir/name_age.result | 10 +--------- tests_dir/older_than_boss.result | 8 +------- tests_dir/par1.result | 2 +- tests_dir/par2.result | 2 +- tests_dir/par3.result | 2 +- tests_dir/par4.result | 2 +- tests_dir/people.result | 10 +--------- tests_dir/people_fjoin_personroom.result | 10 +--------- tests_dir/people_join_personroom.result | 10 +--------- tests_dir/people_join_rooms.result | 14 +------------ ...ple_join_select_args_on_both_tables.result | 3 +-- tests_dir/people_rename.result | 10 +--------- tests_dir/people_rename_select.result | 6 +----- ...phones_of_people_with_personal_room.result | 3 +-- tests_dir/php.result | 3 +-- tests_dir/python_and_c.result | 4 +--- tests_dir/quot1.result | 2 +- tests_dir/redoundant_union_select.result | 3 +-- tests_dir/right_join.result | 19 +----------------- tests_dir/select_join.result | 3 +-- tests_dir/select_join_opt.result | 4 +--- tests_dir/select_people_join_rooms.query | 2 +- tests_dir/select_people_join_rooms.result | 6 +----- tests_dir/skill_of_best_person.result | 5 +---- tests_dir/subtraction1.result | 9 +-------- tests_dir/subtraction2.result | 2 +- tests_dir/swap_fields.result | 10 +--------- tests_dir/union1.result | 5 +---- tests_dir/union2.result | 10 +--------- tests_dir/union3.result | 10 +--------- tests_dir/union4.result | 10 +--------- tests_dir/union_and_select.result | 5 +---- tests_dir/union_join.result | 9 +-------- tests_dir/union_not_select.result | 4 +--- tests_dir/union_or_select.result | 4 +--- tests_dir/union_projection.result | 9 +-------- tests_dir/younger.result | 3 +-- 55 files changed, 55 insertions(+), 329 deletions(-) diff --git a/driver.py b/driver.py index acdb381..19f8d06 100755 --- a/driver.py +++ b/driver.py @@ -239,7 +239,7 @@ def run_test(testname): o_result = None try: - result_rel = relation.Relation.load_csv('%s%s.result' % (tests_path, testname)) + result_rel = relation.Relation.load('%s%s.result' % (tests_path, testname)) query = readfile('%s%s.query' % (tests_path, testname)).strip() o_query = optimizer.optimize_all(query, rels) diff --git a/tests_dir/a_join_a.result b/tests_dir/a_join_a.result index 7081477..d99d273 100644 --- a/tests_dir/a_join_a.result +++ b/tests_dir/a_join_a.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/a_joinf_a.result b/tests_dir/a_joinf_a.result index 7081477..d99d273 100644 --- a/tests_dir/a_joinf_a.result +++ b/tests_dir/a_joinf_a.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/a_joinl_a.result b/tests_dir/a_joinl_a.result index 7081477..d99d273 100644 --- a/tests_dir/a_joinl_a.result +++ b/tests_dir/a_joinl_a.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/a_joinr_a.result b/tests_dir/a_joinr_a.result index 7081477..d99d273 100644 --- a/tests_dir/a_joinr_a.result +++ b/tests_dir/a_joinr_a.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/c_not_python.result b/tests_dir/c_not_python.result index 1899391..ac17b22 100644 --- a/tests_dir/c_not_python.result +++ b/tests_dir/c_not_python.result @@ -1,4 +1 @@ -name -eve -john -duncan +{"content": [["eve"], ["john"], ["duncan"]], "header": ["name"]} \ No newline at end of file diff --git a/tests_dir/c_programmers.result b/tests_dir/c_programmers.result index 34d340e..ffb1e0f 100644 --- a/tests_dir/c_programmers.result +++ b/tests_dir/c_programmers.result @@ -1,6 +1 @@ -id,name,chief,age,skill -2,john,1,30,C -7,alia,1,28,C -5,duncan,4,30,C -0,jack,0,22,C -4,eve,0,25,C +{"content": [[5, "duncan", 4, 30, "C"], [0, "jack", 0, 22, "C"], [4, "eve", 0, 25, "C"], [2, "john", 1, 30, "C"], [7, "alia", 1, 28, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/dates.result b/tests_dir/dates.result index ae3bab8..0eb7080 100644 --- a/tests_dir/dates.result +++ b/tests_dir/dates.result @@ -1,6 +1 @@ -"date" -"2008-12-12" -"2007-08-12" -"1985-05-09" -"1988-4-21" -"1992-7-27" \ No newline at end of file +{"content": [[{"day": 12, "year": 2008, "month": 12}], [{"day": 9, "year": 1985, "month": 5}], [{"day": 21, "year": 1988, "month": 4}], [{"day": 27, "year": 1992, "month": 7}], [{"day": 12, "year": 2007, "month": 8}]], "header": ["date"]} \ No newline at end of file diff --git a/tests_dir/dates_sel.result b/tests_dir/dates_sel.result index e6ff89a..79308c1 100644 --- a/tests_dir/dates_sel.result +++ b/tests_dir/dates_sel.result @@ -1,3 +1 @@ -date -2008-12-12 -2007-08-12 +{"content": [[{"day": 12, "year": 2008, "month": 12}], [{"day": 12, "year": 2007, "month": 8}]], "header": ["date"]} \ No newline at end of file diff --git a/tests_dir/fixed_len_name.result b/tests_dir/fixed_len_name.result index dccd9b3..9577ecd 100644 --- a/tests_dir/fixed_len_name.result +++ b/tests_dir/fixed_len_name.result @@ -1,12 +1 @@ -id,name,chief,age,skill -2,john,1,30,C -7,alia,1,28,C -1,carl,0,20,C++ -2,john,1,30,PHP -7,alia,1,28,Python -3,dean,1,33,C++ -7,alia,1,28,PHP -0,jack,0,22,Python -1,carl,0,20,Python -0,jack,0,22,C -1,carl,0,20,System Admin +{"content": [[3, "dean", 1, 33, "C++"], [2, "john", 1, 30, "PHP"], [1, "carl", 0, 20, "C++"], [1, "carl", 0, 20, "Python"], [2, "john", 1, 30, "C"], [0, "jack", 0, 22, "Python"], [7, "alia", 1, 28, "Python"], [7, "alia", 1, 28, "PHP"], [1, "carl", 0, 20, "System Admin"], [0, "jack", 0, 22, "C"], [7, "alia", 1, 28, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/full_join.result b/tests_dir/full_join.result index 79fb045..436320a 100644 --- a/tests_dir/full_join.result +++ b/tests_dir/full_join.result @@ -1,19 +1 @@ -id,skill,name,chief,age -4,C++,eve,0,25 -4,C,eve,0,25 -7,C,alia,1,28 -9,Java,---,---,--- -0,Python,jack,0,22 -5,C,duncan,4,30 -4,Perl,eve,0,25 -2,C,john,1,30 -1,Python,carl,0,20 -5,Perl,duncan,4,30 -6,---,paul,4,30 -2,PHP,john,1,30 -0,C,jack,0,22 -7,Python,alia,1,28 -1,System Admin,carl,0,20 -7,PHP,alia,1,28 -1,C++,carl,0,20 -3,C++,dean,1,33 +{"content": [[7, "C", "alia", 1, 28], [2, "PHP", "john", 1, 30], [4, "C++", "eve", 0, 25], [7, "Python", "alia", 1, 28], [6, null, "paul", 4, 30], [0, "Python", "jack", 0, 22], [2, "C", "john", 1, 30], [1, "Python", "carl", 0, 20], [1, "System Admin", "carl", 0, 20], [3, "C++", "dean", 1, 33], [5, "Perl", "duncan", 4, 30], [5, "C", "duncan", 4, 30], [7, "PHP", "alia", 1, 28], [1, "C++", "carl", 0, 20], [0, "C", "jack", 0, 22], [9, "Java", null, null, null], [4, "C", "eve", 0, 25], [4, "Perl", "eve", 0, 25]], "header": ["id", "skill", "name", "chief", "age"]} diff --git a/tests_dir/intersection1.result b/tests_dir/intersection1.result index 09dd8f0..a81c6b3 100644 --- a/tests_dir/intersection1.result +++ b/tests_dir/intersection1.result @@ -1,2 +1 @@ -id,name,chief,age -4,eve,0,25 +{"content": [[4, "eve", 0, 25]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/intersection2.result b/tests_dir/intersection2.result index 09dd8f0..a81c6b3 100644 --- a/tests_dir/intersection2.result +++ b/tests_dir/intersection2.result @@ -1,2 +1 @@ -id,name,chief,age -4,eve,0,25 +{"content": [[4, "eve", 0, 25]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/java_and_perl.result b/tests_dir/java_and_perl.result index 02755db..b7c8870 100644 --- a/tests_dir/java_and_perl.result +++ b/tests_dir/java_and_perl.result @@ -1,3 +1 @@ -name -duncan -eve +{"content": [["eve"], ["duncan"]], "header": ["name"]} \ No newline at end of file diff --git a/tests_dir/join.result b/tests_dir/join.result index 07cd4a7..e9f6d38 100644 --- a/tests_dir/join.result +++ b/tests_dir/join.result @@ -1,17 +1 @@ -id,name,chief,age,skill -2,john,1,30,C -7,alia,1,28,C -4,eve,0,25,C++ -4,eve,0,25,C -5,duncan,4,30,Perl -1,carl,0,20,C++ -7,alia,1,28,PHP -7,alia,1,28,Python -3,dean,1,33,C++ -1,carl,0,20,Python -2,john,1,30,PHP -0,jack,0,22,Python -0,jack,0,22,C -1,carl,0,20,System Admin -4,eve,0,25,Perl -5,duncan,4,30,C +{"content": [[2, "john", 1, 30, "PHP"], [5, "duncan", 4, 30, "Perl"], [2, "john", 1, 30, "C"], [4, "eve", 0, 25, "Perl"], [1, "carl", 0, 20, "System Admin"], [3, "dean", 1, 33, "C++"], [4, "eve", 0, 25, "C++"], [1, "carl", 0, 20, "C++"], [1, "carl", 0, 20, "Python"], [0, "jack", 0, 22, "Python"], [7, "alia", 1, 28, "Python"], [5, "duncan", 4, 30, "C"], [7, "alia", 1, 28, "PHP"], [4, "eve", 0, 25, "C"], [0, "jack", 0, 22, "C"], [7, "alia", 1, 28, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/left_join.result b/tests_dir/left_join.result index 75b24c4..fc40eaa 100644 --- a/tests_dir/left_join.result +++ b/tests_dir/left_join.result @@ -1,18 +1 @@ -id,name,chief,age,skill -2,john,1,30,C -7,alia,1,28,C -4,eve,0,25,C++ -6,paul,4,30,--- -5,duncan,4,30,Perl -1,carl,0,20,C++ -7,alia,1,28,PHP -4,eve,0,25,C -7,alia,1,28,Python -3,dean,1,33,C++ -1,carl,0,20,Python -2,john,1,30,PHP -0,jack,0,22,Python -0,jack,0,22,C -1,carl,0,20,System Admin -4,eve,0,25,Perl -5,duncan,4,30,C +{"content": [[2, "john", 1, 30, "PHP"], [5, "duncan", 4, 30, "Perl"], [2, "john", 1, 30, "C"], [6, "paul", 4, 30, null], [4, "eve", 0, 25, "Perl"], [1, "carl", 0, 20, "System Admin"], [3, "dean", 1, 33, "C++"], [4, "eve", 0, 25, "C++"], [1, "carl", 0, 20, "C++"], [1, "carl", 0, 20, "Python"], [0, "jack", 0, 22, "Python"], [7, "alia", 1, 28, "Python"], [5, "duncan", 4, 30, "C"], [7, "alia", 1, 28, "PHP"], [4, "eve", 0, 25, "C"], [0, "jack", 0, 22, "C"], [7, "alia", 1, 28, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/max_rating_in_age_range.result b/tests_dir/max_rating_in_age_range.result index 5d1f9f3..91eed6b 100644 --- a/tests_dir/max_rating_in_age_range.result +++ b/tests_dir/max_rating_in_age_range.result @@ -1,2 +1 @@ -id,name,chief,age,rating -1,carl,0,20,6 +{"content": [[1, "carl", 0, 20, 6]], "header": ["id", "name", "chief", "age", "rating"]} \ No newline at end of file diff --git a/tests_dir/maxdate.result b/tests_dir/maxdate.result index bde2e23..3a82e18 100644 --- a/tests_dir/maxdate.result +++ b/tests_dir/maxdate.result @@ -1,2 +1 @@ -date -2008-12-12 +{"content": [[{"day": 12, "year": 2008, "month": 12}]], "header": ["date"]} \ No newline at end of file diff --git a/tests_dir/name_age.result b/tests_dir/name_age.result index c9cc9bf..606e89a 100644 --- a/tests_dir/name_age.result +++ b/tests_dir/name_age.result @@ -1,9 +1 @@ -name,age -eve,25 -dean,33 -carl,20 -paul,30 -john,30 -jack,22 -duncan,30 -alia,28 +{"content": [["eve", 25], ["duncan", 30], ["paul", 30], ["carl", 20], ["alia", 28], ["dean", 33], ["jack", 22], ["john", 30]], "header": ["name", "age"]} \ No newline at end of file diff --git a/tests_dir/older_than_boss.result b/tests_dir/older_than_boss.result index 92af55b..555a259 100644 --- a/tests_dir/older_than_boss.result +++ b/tests_dir/older_than_boss.result @@ -1,7 +1 @@ -name,age,chief_name,chief_age -dean,33,carl,20 -alia,28,carl,20 -paul,30,eve,25 -eve,25,jack,22 -john,30,carl,20 -duncan,30,eve,25 +{"content": [["dean", 33, "carl", 20], ["duncan", 30, "eve", 25], ["john", 30, "carl", 20], ["alia", 28, "carl", 20], ["eve", 25, "jack", 22], ["paul", 30, "eve", 25]], "header": ["name", "age", "chief_name", "chief_age"]} \ No newline at end of file diff --git a/tests_dir/par1.result b/tests_dir/par1.result index 9d637aa..f2109d1 100644 --- a/tests_dir/par1.result +++ b/tests_dir/par1.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/par2.result b/tests_dir/par2.result index 9d637aa..f2109d1 100644 --- a/tests_dir/par2.result +++ b/tests_dir/par2.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/par3.result b/tests_dir/par3.result index 9d637aa..f2109d1 100644 --- a/tests_dir/par3.result +++ b/tests_dir/par3.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/par4.result b/tests_dir/par4.result index 9d637aa..f2109d1 100644 --- a/tests_dir/par4.result +++ b/tests_dir/par4.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/people.result b/tests_dir/people.result index 7081477..d99d273 100644 --- a/tests_dir/people.result +++ b/tests_dir/people.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/people_fjoin_personroom.result b/tests_dir/people_fjoin_personroom.result index 2501391..49c0aa9 100644 --- a/tests_dir/people_fjoin_personroom.result +++ b/tests_dir/people_fjoin_personroom.result @@ -1,9 +1 @@ -id,name,chief,age,room -0,jack,0,22,1 -1,carl,0,20,4 -2,john,1,30,2 -3,dean,1,33,2 -4,eve,0,25,5 -5,duncan,4,30,1 -6,paul,4,30,5 -7,alia,1,28,1 +{"content": [[5, "duncan", 4, 30, 1], [4, "eve", 0, 25, 5], [0, "jack", 0, 22, 1], [2, "john", 1, 30, 2], [1, "carl", 0, 20, 4], [6, "paul", 4, 30, 5], [7, "alia", 1, 28, 1], [3, "dean", 1, 33, 2]], "header": ["id", "name", "chief", "age", "room"]} \ No newline at end of file diff --git a/tests_dir/people_join_personroom.result b/tests_dir/people_join_personroom.result index 2501391..49c0aa9 100644 --- a/tests_dir/people_join_personroom.result +++ b/tests_dir/people_join_personroom.result @@ -1,9 +1 @@ -id,name,chief,age,room -0,jack,0,22,1 -1,carl,0,20,4 -2,john,1,30,2 -3,dean,1,33,2 -4,eve,0,25,5 -5,duncan,4,30,1 -6,paul,4,30,5 -7,alia,1,28,1 +{"content": [[5, "duncan", 4, 30, 1], [4, "eve", 0, 25, 5], [0, "jack", 0, 22, 1], [2, "john", 1, 30, 2], [1, "carl", 0, 20, 4], [6, "paul", 4, 30, 5], [7, "alia", 1, 28, 1], [3, "dean", 1, 33, 2]], "header": ["id", "name", "chief", "age", "room"]} \ No newline at end of file diff --git a/tests_dir/people_join_rooms.result b/tests_dir/people_join_rooms.result index 108efc3..1e59835 100644 --- a/tests_dir/people_join_rooms.result +++ b/tests_dir/people_join_rooms.result @@ -1,13 +1 @@ -id,name,chief,age,room,phone -0,jack,0,22,1,1516 -1,carl,0,20,4,1041 -2,john,1,30,2,1617 -3,dean,1,33,2,1617 -4,eve,0,25,5,9212 -5,duncan,4,30,1,1516 -6,paul,4,30,5,9212 -7,alia,1,28,1,1516 ----,---,---,---,"0","1515" ----,---,---,---,"3","1601" ----,---,---,---,"6","1424" ----,---,---,---,"7","1294" +{"content": [[0, "jack", 0, 22, 1, 1516], [null, null, null, null, 6, 1424], [null, null, null, null, 3, 1601], [7, "alia", 1, 28, 1, 1516], [2, "john", 1, 30, 2, 1617], [3, "dean", 1, 33, 2, 1617], [5, "duncan", 4, 30, 1, 1516], [4, "eve", 0, 25, 5, 9212], [1, "carl", 0, 20, 4, 1041], [null, null, null, null, 0, 1515], [null, null, null, null, 7, 1294], [6, "paul", 4, 30, 5, 9212]], "header": ["id", "name", "chief", "age", "room", "phone"]} diff --git a/tests_dir/people_join_select_args_on_both_tables.result b/tests_dir/people_join_select_args_on_both_tables.result index 57c4154..5e5d414 100644 --- a/tests_dir/people_join_select_args_on_both_tables.result +++ b/tests_dir/people_join_select_args_on_both_tables.result @@ -1,2 +1 @@ -id,name,chief,age,skill -0,jack,0,22,C +{"content": [[0, "jack", 0, 22, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/people_rename.result b/tests_dir/people_rename.result index 6e4a1ba..5b734ec 100644 --- a/tests_dir/people_rename.result +++ b/tests_dir/people_rename.result @@ -1,9 +1 @@ -id,n,chief,a -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "n", "chief", "a"]} \ No newline at end of file diff --git a/tests_dir/people_rename_select.result b/tests_dir/people_rename_select.result index 83a690c..00e30c9 100644 --- a/tests_dir/people_rename_select.result +++ b/tests_dir/people_rename_select.result @@ -1,5 +1 @@ -i,name,chief,age -0,jack,0,22 -2,john,1,30 -4,eve,0,25 -6,paul,4,30 +{"content": [[4, "eve", 0, 25], [6, "paul", 4, 30], [2, "john", 1, 30], [0, "jack", 0, 22]], "header": ["i", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/phones_of_people_with_personal_room.result b/tests_dir/phones_of_people_with_personal_room.result index ebd829a..d6d6901 100644 --- a/tests_dir/phones_of_people_with_personal_room.result +++ b/tests_dir/phones_of_people_with_personal_room.result @@ -1,2 +1 @@ -phone,name -1041,carl +{"content": [[1041, "carl"]], "header": ["phone", "name"]} \ No newline at end of file diff --git a/tests_dir/php.result b/tests_dir/php.result index 15b2bff..b6d5f4d 100644 --- a/tests_dir/php.result +++ b/tests_dir/php.result @@ -1,2 +1 @@ -id,name,chief,age,skill -7,alia,1,28,PHP +{"content": [[7, "alia", 1, 28, "PHP"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/python_and_c.result b/tests_dir/python_and_c.result index 6eb3d0c..ad43f00 100644 --- a/tests_dir/python_and_c.result +++ b/tests_dir/python_and_c.result @@ -1,3 +1 @@ -name -jack -alia +{"content": [["alia"], ["jack"]], "header": ["name"]} \ No newline at end of file diff --git a/tests_dir/quot1.result b/tests_dir/quot1.result index 9d637aa..f2109d1 100644 --- a/tests_dir/quot1.result +++ b/tests_dir/quot1.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/redoundant_union_select.result b/tests_dir/redoundant_union_select.result index 9408a8d..a575600 100644 --- a/tests_dir/redoundant_union_select.result +++ b/tests_dir/redoundant_union_select.result @@ -1,2 +1 @@ -id,name,chief,age -2,john,1,30 +{"content": [[2, "john", 1, 30]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/right_join.result b/tests_dir/right_join.result index e8ef0bd..19cbcd1 100644 --- a/tests_dir/right_join.result +++ b/tests_dir/right_join.result @@ -1,18 +1 @@ -id,skill,name,chief,age -4,C++,eve,0,25 -2,C,john,1,30 -1,Python,carl,0,20 -4,C,eve,0,25 -1,C++,carl,0,20 -7,C,alia,1,28 -9,Java,---,---,--- -2,PHP,john,1,30 -0,Python,jack,0,22 -4,Perl,eve,0,25 -5,C,duncan,4,30 -7,Python,alia,1,28 -1,System Admin,carl,0,20 -5,Perl,duncan,4,30 -7,PHP,alia,1,28 -0,C,jack,0,22 -3,C++,dean,1,33 +{"content": [[7, "C", "alia", 1, 28], [2, "PHP", "john", 1, 30], [4, "C++", "eve", 0, 25], [7, "Python", "alia", 1, 28], [0, "Python", "jack", 0, 22], [2, "C", "john", 1, 30], [1, "Python", "carl", 0, 20], [1, "System Admin", "carl", 0, 20], [3, "C++", "dean", 1, 33], [5, "Perl", "duncan", 4, 30], [5, "C", "duncan", 4, 30], [7, "PHP", "alia", 1, 28], [1, "C++", "carl", 0, 20], [0, "C", "jack", 0, 22], [9, "Java", null, null, null], [4, "C", "eve", 0, 25], [4, "Perl", "eve", 0, 25]], "header": ["id", "skill", "name", "chief", "age"]} diff --git a/tests_dir/select_join.result b/tests_dir/select_join.result index 51a573d..ec573ee 100644 --- a/tests_dir/select_join.result +++ b/tests_dir/select_join.result @@ -1,2 +1 @@ -id,skill,name,chief,age -3,C++,dean,1,33 +{"content": [[3, "C++", "dean", 1, 33]], "header": ["id", "skill", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/select_join_opt.result b/tests_dir/select_join_opt.result index 8a76bff..8bc0c50 100644 --- a/tests_dir/select_join_opt.result +++ b/tests_dir/select_join_opt.result @@ -1,3 +1 @@ -id,name,chief,age,skill -0,jack,0,22,C -4,eve,0,25,C +{"content": [[4, "eve", 0, 25, "C"], [0, "jack", 0, 22, "C"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/select_people_join_rooms.query b/tests_dir/select_people_join_rooms.query index 2336072..3a22f62 100644 --- a/tests_dir/select_people_join_rooms.query +++ b/tests_dir/select_people_join_rooms.query @@ -1 +1 @@ -σid=='---' (people⧓person_room ⧓ rooms) +σ id is None (people⧓person_room ⧓ rooms) diff --git a/tests_dir/select_people_join_rooms.result b/tests_dir/select_people_join_rooms.result index 82eb158..64924d3 100644 --- a/tests_dir/select_people_join_rooms.result +++ b/tests_dir/select_people_join_rooms.result @@ -1,5 +1 @@ -id,name,chief,age,room,phone ----,---,---,---,"0","1515" ----,---,---,---,"3","1601" ----,---,---,---,"6","1424" ----,---,---,---,"7","1294" +{"content": [[null, null, null, null, 0, 1515], [null, null, null, null, 7, 1294], [null, null, null, null, 3, 1601], [null, null, null, null, 6, 1424]], "header": ["id", "name", "chief", "age", "room", "phone"]} \ No newline at end of file diff --git a/tests_dir/skill_of_best_person.result b/tests_dir/skill_of_best_person.result index 5d07f7d..a671f53 100644 --- a/tests_dir/skill_of_best_person.result +++ b/tests_dir/skill_of_best_person.result @@ -1,4 +1 @@ -name,age,skill -eve,25,Perl -eve,25,C -eve,25,C++ +{"content": [["eve", 25, "C++"], ["eve", 25, "C"], ["eve", 25, "Perl"]], "header": ["name", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/subtraction1.result b/tests_dir/subtraction1.result index 6dc5c97..bd1f905 100644 --- a/tests_dir/subtraction1.result +++ b/tests_dir/subtraction1.result @@ -1,8 +1 @@ -id,name,chief,age -3,dean,1,33 -6,paul,4,30 -2,john,1,30 -0,jack,0,22 -7,alia,1,28 -1,carl,0,20 -5,duncan,4,30 +{"content": [[3, "dean", 1, 33], [5, "duncan", 4, 30], [1, "carl", 0, 20], [0, "jack", 0, 22], [2, "john", 1, 30], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/subtraction2.result b/tests_dir/subtraction2.result index 9d637aa..f2109d1 100644 --- a/tests_dir/subtraction2.result +++ b/tests_dir/subtraction2.result @@ -1 +1 @@ -id,name,chief,age +{"content": [], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/swap_fields.result b/tests_dir/swap_fields.result index 7081477..d99d273 100644 --- a/tests_dir/swap_fields.result +++ b/tests_dir/swap_fields.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union1.result b/tests_dir/union1.result index dea402b..4d5f28e 100644 --- a/tests_dir/union1.result +++ b/tests_dir/union1.result @@ -1,4 +1 @@ -id,name,chief,age -7,alia,1,28 -4,eve,0,25 -0,jack,0,22 +{"content": [[4, "eve", 0, 25], [0, "jack", 0, 22], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union2.result b/tests_dir/union2.result index 0c5da4e..d99d273 100644 --- a/tests_dir/union2.result +++ b/tests_dir/union2.result @@ -1,9 +1 @@ -id,name,chief,age -3,dean,1,33 -6,paul,4,30 -2,john,1,30 -0,jack,0,22 -7,alia,1,28 -1,carl,0,20 -4,eve,0,25 -5,duncan,4,30 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union3.result b/tests_dir/union3.result index 0c5da4e..d99d273 100644 --- a/tests_dir/union3.result +++ b/tests_dir/union3.result @@ -1,9 +1 @@ -id,name,chief,age -3,dean,1,33 -6,paul,4,30 -2,john,1,30 -0,jack,0,22 -7,alia,1,28 -1,carl,0,20 -4,eve,0,25 -5,duncan,4,30 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union4.result b/tests_dir/union4.result index 7081477..d99d273 100644 --- a/tests_dir/union4.result +++ b/tests_dir/union4.result @@ -1,9 +1 @@ -id,name,chief,age -0,jack,0,22 -1,carl,0,20 -2,john,1,30 -3,dean,1,33 -4,eve,0,25 -5,duncan,4,30 -6,paul,4,30 -7,alia,1,28 +{"content": [[1, "carl", 0, 20], [2, "john", 1, 30], [3, "dean", 1, 33], [5, "duncan", 4, 30], [0, "jack", 0, 22], [4, "eve", 0, 25], [6, "paul", 4, 30], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union_and_select.result b/tests_dir/union_and_select.result index 98379d8..c79100e 100644 --- a/tests_dir/union_and_select.result +++ b/tests_dir/union_and_select.result @@ -1,4 +1 @@ -id,skill -0,C -2,C -4,C +{"content": [[2, "C"], [0, "C"], [4, "C"]], "header": ["id", "skill"]} \ No newline at end of file diff --git a/tests_dir/union_join.result b/tests_dir/union_join.result index df2f9b0..e28ef51 100644 --- a/tests_dir/union_join.result +++ b/tests_dir/union_join.result @@ -1,8 +1 @@ -id,name,chief,age,skill -4,eve,0,25,C -5,duncan,4,30,C -2,john,1,30,C -7,alia,1,28,C -4,eve,0,25,Perl -5,duncan,4,30,Perl -0,jack,0,22,C +{"content": [[4, "eve", 0, 25, "C"], [5, "duncan", 4, 30, "C"], [0, "jack", 0, 22, "C"], [5, "duncan", 4, 30, "Perl"], [2, "john", 1, 30, "C"], [7, "alia", 1, 28, "C"], [4, "eve", 0, 25, "Perl"]], "header": ["id", "name", "chief", "age", "skill"]} \ No newline at end of file diff --git a/tests_dir/union_not_select.result b/tests_dir/union_not_select.result index b92ce90..a2aff9a 100644 --- a/tests_dir/union_not_select.result +++ b/tests_dir/union_not_select.result @@ -1,3 +1 @@ -id,skill -5,C -7,C +{"content": [[7, "C"], [5, "C"]], "header": ["id", "skill"]} \ No newline at end of file diff --git a/tests_dir/union_or_select.result b/tests_dir/union_or_select.result index 5386f2f..35201cb 100644 --- a/tests_dir/union_or_select.result +++ b/tests_dir/union_or_select.result @@ -1,3 +1 @@ -id,name,chief,age -3,dean,1,33 -1,carl,0,20 +{"content": [[3, "dean", 1, 33], [1, "carl", 0, 20]], "header": ["id", "name", "chief", "age"]} \ No newline at end of file diff --git a/tests_dir/union_projection.result b/tests_dir/union_projection.result index 7e0ab5d..1a1d483 100644 --- a/tests_dir/union_projection.result +++ b/tests_dir/union_projection.result @@ -1,8 +1 @@ -name,skill -jack,C -eve,Perl -duncan,Perl -duncan,C -john,C -alia,C -eve,C +{"content": [["jack", "C"], ["eve", "C"], ["duncan", "C"], ["alia", "C"], ["eve", "Perl"], ["duncan", "Perl"], ["john", "C"]], "header": ["name", "skill"]} \ No newline at end of file diff --git a/tests_dir/younger.result b/tests_dir/younger.result index f41e0cf..6527b54 100644 --- a/tests_dir/younger.result +++ b/tests_dir/younger.result @@ -1,2 +1 @@ -id,age,chief,name -1,20,0,carl +{"content": [[1, 20, 0, "carl"]], "header": ["id", "age", "chief", "name"]} \ No newline at end of file From 0920559a82d12a51a75af169a115aa6257ad9dd9 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:38:10 +0200 Subject: [PATCH 27/32] Make mypy happy --- relational/rtypes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index 14e6b97..d493fd2 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -23,7 +23,7 @@ import datetime import keyword import re -from typing import Union, Set, Any, Callable +from typing import Union, Set, Any, Callable, Type from dataclasses import dataclass @@ -34,8 +34,8 @@ _date_regexp = re.compile( CastValue = Union[str, int, float, 'Rdate'] -def guess_type(value: str) -> Set[Callable[[Any], Any]]: - r: Set[Callable[[Any], Any]] = {str} +def guess_type(value: str) -> Set[Union[Callable[[Any], Any], Type['Rdate']]]: + r: Set[Union[Callable[[Any], Any], Type['Rdate']]] = {str} if _date_regexp.match(value) is not None: r.add(Rdate) From 177ca5a3eec9a18e2af9ae7648be8fed37ff7697 Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:42:18 +0200 Subject: [PATCH 28/32] Values can be None --- relational/rtypes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relational/rtypes.py b/relational/rtypes.py index d493fd2..29f9aae 100644 --- a/relational/rtypes.py +++ b/relational/rtypes.py @@ -23,7 +23,7 @@ import datetime import keyword import re -from typing import Union, Set, Any, Callable, Type +from typing import Union, Set, Any, Callable, Type, Optional from dataclasses import dataclass @@ -31,7 +31,7 @@ RELATION_NAME_REGEXP = re.compile(r'^[_a-z][_a-z0-9]*$', re.IGNORECASE) _date_regexp = re.compile( r'^([0-9]{1,4})(\\|-|/)([0-9]{1,2})(\\|-|/)([0-9]{1,2})$' ) -CastValue = Union[str, int, float, 'Rdate'] +CastValue = Optional[Union[str, int, float, 'Rdate']] def guess_type(value: str) -> Set[Union[Callable[[Any], Any], Type['Rdate']]]: From 5a271dce1d8a0bbc115e7051983aeede58f31e0c Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:42:50 +0200 Subject: [PATCH 29/32] Usual mypy workaround https://github.com/ltworf/typedload/issues/132 https://github.com/python/mypy/issues/9003 --- relational/relation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relational/relation.py b/relational/relation.py index 14c63fc..b20e8e4 100644 --- a/relational/relation.py +++ b/relational/relation.py @@ -89,7 +89,7 @@ class Relation: for row in loaded['content']: if len(row) != len(header): raise ValueError(f'Line {row} contains an incorrect amount of values') - t_row: Tuple[Optional[Union[int, float, str, Rdate]], ...] = load(row, Tuple[Optional[Union[int, float, str, Rdate]], ...]) + t_row: Tuple[Optional[Union[int, float, str, Rdate]], ...] = load(row, Tuple[Optional[Union[int, float, str, Rdate]], ...]) # type: ignore content.append(t_row) return Relation(header, frozenset(content)) From a110f3f2c55500769d85409e8552fc309fafa3ec Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 18:55:51 +0200 Subject: [PATCH 30/32] GUI manage json files --- relational/maintenance.py | 5 ++++- relational_gui/guihandler.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/relational/maintenance.py b/relational/maintenance.py index d50c3b4..a526e7c 100644 --- a/relational/maintenance.py +++ b/relational/maintenance.py @@ -107,7 +107,10 @@ class UserInterface: def store(self, filename: str, name: str) -> None: '''Stores a relation to file.''' - raise Exception('Not implemented') + if filename.endswith('.csv'): + self.relations[name].save_csv(filename) + else: + self.relations[name].save(filename) def session_dump(self, filename: Optional[str] = None) -> Optional[str]: ''' diff --git a/relational_gui/guihandler.py b/relational_gui/guihandler.py index a77b1c5..b590126 100644 --- a/relational_gui/guihandler.py +++ b/relational_gui/guihandler.py @@ -292,13 +292,13 @@ class relForm(QtWidgets.QMainWindow): filename = QtWidgets.QFileDialog.getSaveFileName( self, QtWidgets.QApplication.translate("Form", "Save Relation"), "", - QtWidgets.QApplication.translate("Form", "Relations (*.csv)") + QtWidgets.QApplication.translate("Form", "Json relations (*.json);;CSV relations (*.csv)") )[0] if (len(filename) == 0): # Returns if no file was selected return relname = self.ui.lstRelations.selectedItems()[0].text() - self.user_interface.relations[relname].save(filename) + self.user_interface.store(filename, relname) def unloadRelation(self): for i in self.ui.lstRelations.selectedItems(): @@ -422,7 +422,7 @@ class relForm(QtWidgets.QMainWindow): "", QtWidgets.QApplication.translate( "Form", - "Relations (*.csv);;Text Files (*.txt);;All Files (*)" + "Relations (*.json *.csv);;Text Files (*.txt);;All Files (*)" ) ) filenames = f[0] From ae2e838df2f0b52d0bc869abc56dcf68b24973ae Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 19:04:04 +0200 Subject: [PATCH 31/32] Fix cli for the double format --- relational/maintenance.py | 2 +- relational_readline/linegui.py | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/relational/maintenance.py b/relational/maintenance.py index a526e7c..954438a 100644 --- a/relational/maintenance.py +++ b/relational/maintenance.py @@ -1,5 +1,5 @@ # Relational -# Copyright (C) 2008-2017 Salvo "LtWorf" Tomaselli +# Copyright (C) 2008-2020 Salvo "LtWorf" Tomaselli # # Relation is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/relational_readline/linegui.py b/relational_readline/linegui.py index 8655cac..6471bc3 100644 --- a/relational_readline/linegui.py +++ b/relational_readline/linegui.py @@ -106,8 +106,7 @@ class SimpleCompleter: repr(text), state, repr(response)) return response - -relations = {} +ui = maintenance.UserInterface() completer = SimpleCompleter( ['SURVEY', 'LIST', 'LOAD ', 'UNLOAD ', 'HELP ', 'QUIT', 'SAVE ', '_PRODUCT ', '_UNION ', '_INTERSECTION ', '_DIFFERENCE ', '_JOIN ', '_LJOIN ', '_RJOIN ', '_FJOIN ', '_PROJECTION ', '_RENAME_TO ', '_SELECTION ', '_RENAME ', '_DIVISION ']) @@ -137,7 +136,7 @@ def load_relation(filename: str, defname: Optional[str]) -> Optional[str]: "%s is not a valid relation name" % defname, ERROR_COLOR), file=sys.stderr) return None try: - relations[defname] = relation.Relation.load(filename) + ui.load(filename, defname) completer.add_completion(defname) printtty(colorize("Loaded relation %s" % defname, COLOR_GREEN)) @@ -204,7 +203,7 @@ def exec_line(command: str) -> None: elif command.startswith('HELP'): help(command) elif command == 'LIST': # Lists all the loaded relations - for i in relations: + for i in ui.relations: if not i.startswith('_'): print(i) elif command == 'SURVEY': @@ -225,9 +224,10 @@ def exec_line(command: str) -> None: pars = command.split(' ') if len(pars) < 2: print(colorize("Missing parameter", ERROR_COLOR)) - return - if pars[1] in relations: - del relations[pars[1]] + elif len(pars) > 2: + print(colorize("Too many parameter", ERROR_COLOR)) + if pars[1] in ui.relations: + ui.unload(pars[1]) completer.remove_completion(pars[1]) else: print(colorize("No such relation %s" % pars[1], ERROR_COLOR)) @@ -240,11 +240,8 @@ def exec_line(command: str) -> None: filename = pars[1] defname = pars[2] - if defname not in relations: - print(colorize("No such relation %s" % defname, ERROR_COLOR)) - return try: - relations[defname].save(filename) + ui.store(filename, defname) except Exception as e: print(colorize(e, ERROR_COLOR)) else: @@ -298,7 +295,7 @@ def exec_query(command: str) -> None: # Execute query try: pyquery = parser.parse(query) - result = pyquery(relations) + result = pyquery(ui.relations) printtty(colorize("-> query: %s" % pyquery, COLOR_GREEN)) @@ -306,7 +303,7 @@ def exec_query(command: str) -> None: print() print(result) - relations[relname] = result + ui.relations[relname] = result completer.add_completion(relname) except Exception as e: From a6898697a656feb87e11bcd81069446b8ddbac3a Mon Sep 17 00:00:00 2001 From: Salvo 'LtWorf' Tomaselli Date: Mon, 24 Aug 2020 19:04:52 +0200 Subject: [PATCH 32/32] CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 29cd016..649b8e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,5 @@ 3.0 +- By default relations are saved as json. This allows to keep the type - Dates can no longer be added or subtracted - Types are now inferred by column, no longer by cell - Relations now use frozenset internally and are immutable