Merge pull request #31 from ltworf/column_types

Types per column, not per cell. Cast when loading.
This commit is contained in:
Salvo 'LtWorf' Tomaselli 2020-08-24 19:31:55 +02:00 committed by GitHub
commit 8755236f94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 247 additions and 517 deletions

View File

@ -6,6 +6,7 @@ python:
install: install:
- pip install mypy - pip install mypy
- pip install xtermcolor - pip install xtermcolor
- pip install typedload
script: script:
- make mypy - make mypy

View File

@ -1,4 +1,7 @@
3.0 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 - Relations now use frozenset internally and are immutable
- Refactored parser to use better typing - Refactored parser to use better typing
- Refactored and fixed some optimizations - Refactored and fixed some optimizations

4
debian/control vendored
View File

@ -3,7 +3,7 @@ Section: math
Priority: optional Priority: optional
Maintainer: Salvo 'LtWorf' Tomaselli <tiposchi@tiscali.it> Maintainer: Salvo 'LtWorf' Tomaselli <tiposchi@tiscali.it>
Build-Depends: debhelper-compat (= 13), debhelper (>= 13), python3, dh-python, python3-xtermcolor, pyqt5-dev-tools, 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 Standards-Version: 4.5.0
X-Python3-Version: >= 3.8 X-Python3-Version: >= 3.8
Homepage: https://ltworf.github.io/relational/ Homepage: https://ltworf.github.io/relational/
@ -12,7 +12,7 @@ Rules-Requires-Root: no
Package: python3-relational Package: python3-relational
Architecture: all Architecture: all
Section: python Section: python
Depends: ${misc:Depends}, ${python3:Depends} Depends: ${misc:Depends}, ${python3:Depends}, python3-typedload
Description: Educational tool for relational algebra (standalone module) Description: Educational tool for relational algebra (standalone module)
Relational is primarily a tool to provide a workspace for experimenting with Relational is primarily a tool to provide a workspace for experimenting with
relational algebra, an offshoot of first-order logic. relational algebra, an offshoot of first-order logic.

View File

@ -57,7 +57,7 @@ def load_relations():
print ("Loading relation %s with name %s..." % (i, relname)) 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') print('done')

View File

@ -1,5 +1,5 @@
# Relational # 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 # Relation is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -90,7 +90,14 @@ class UserInterface:
def load(self, filename: str, name: str) -> None: def load(self, filename: str, name: str) -> None:
'''Loads a relation from file, and gives it a name to '''Loads a relation from file, and gives it a name to
be used in subsequent queries.''' 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) rel = Relation.load(filename)
self.set_relation(name, rel) self.set_relation(name, rel)
@ -100,7 +107,10 @@ class UserInterface:
def store(self, filename: str, name: str) -> None: def store(self, filename: str, name: str) -> None:
'''Stores a relation to file.''' '''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]: def session_dump(self, filename: Optional[str] = None) -> Optional[str]:
''' '''
@ -161,8 +171,12 @@ class UserInterface:
if len(name) == 0: if len(name) == 0:
return None return None
if (name.endswith(".csv")): # removes the extension # Removing the extension
name = name[:-4] try:
pos = name.rindex('.')
except ValueError:
return None
name = name[:pos]
if not is_valid_relation_name(name): if not is_valid_relation_name(name):
return None return None

View File

@ -19,10 +19,10 @@
# This module provides a classes to represent relations and to perform # This module provides a classes to represent relations and to perform
# relational operations on them. # relational operations on them.
import csv
from itertools import chain, repeat, product as iproduct from itertools import chain, repeat, product as iproduct
from collections import deque from collections import deque
from typing import * from typing import FrozenSet, Iterable, List, Dict, Tuple, Optional
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from relational.rtypes import * from relational.rtypes import *
@ -33,8 +33,8 @@ __all__ = [
'Header', 'Header',
] ]
@dataclass(repr=True, unsafe_hash=False, frozen=True)
class Relation(NamedTuple): class Relation:
''' '''
This object defines a relation (as a group of consistent tuples) and operations. This object defines a relation (as a group of consistent tuples) and operations.
@ -58,47 +58,57 @@ class Relation(NamedTuple):
method. method.
''' '''
header: 'Header' header: 'Header'
content: FrozenSet[Tuple[Rstring, ...]] content: FrozenSet[Tuple[CastValue, ...]]
@staticmethod @staticmethod
def load(filename: Union[str, Path]) -> 'Relation': def load_csv(filename: Union[str, Path]) -> 'Relation':
''' '''
Load a relation object from a csv file. Load a relation object from a csv file.
The 1st row is the header and the other rows are the content. 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: with open(filename) as fp:
reader = csv.reader(fp) # Creating a csv reader reader = csv.reader(fp) # Creating a csv reader
header = Header(next(reader)) # read 1st line header = Header(next(reader)) # read 1st line
return Relation.create_from(header, reader) return Relation.create_from(header, reader)
@staticmethod @staticmethod
def create_from(header: Iterable[str], content: Iterable[Iterable[str]]) -> 'Relation': def load(filename: Union[str, Path]) -> 'Relation':
''' '''
Iterator for the header, and iterator for the content. Load a relation object from a json file.
''' '''
header = Header(header) with open(filename) as fp:
r_content: List[Tuple[Rstring, ...]] = [] from json import load as jload
for row in content: from typedload import load
content_row: Tuple[Rstring, ...] = tuple(Rstring(i) for i in row) loaded = jload(fp)
if len(content_row) != len(header): 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') raise ValueError(f'Line {row} contains an incorrect amount of values')
r_content.append(content_row) t_row: Tuple[Optional[Union[int, float, str, Rdate]], ...] = load(row, Tuple[Optional[Union[int, float, str, Rdate]], ...]) # type: ignore
return Relation(header, frozenset(r_content)) content.append(t_row)
return Relation(header, frozenset(content))
def __iter__(self):
return iter(self.content)
def __contains__(self, key):
return key in self.content
def save(self, filename: Union[Path, str]) -> None: 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 Saves the relation in a file. Will save using the csv
format as defined in RFC4180. format as defined in RFC4180.
''' '''
import csv
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
writer = csv.writer(fp) # Creating csv writer writer = csv.writer(fp) # Creating csv writer
@ -109,6 +119,40 @@ class Relation(NamedTuple):
# Writing content, already in the correct format # Writing content, already in the correct format
writer.writerows(self.content) 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 = []
guessed_types = list(repeat({Rdate, float, int, str}, len(header)))
for row in content:
if len(row) != len(header):
raise ValueError(f'Line {row} contains an incorrect amount of values')
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):
return iter(self.content)
def __contains__(self, key):
return key in self.content
def _rearrange(self, other: 'Relation') -> 'Relation': def _rearrange(self, other: 'Relation') -> 'Relation':
'''If two relations share the same attributes in a different order, this method '''If two relations share the same attributes in a different order, this method
will use projection to make them have the same attributes' order. will use projection to make them have the same attributes' order.
@ -129,8 +173,6 @@ class Relation(NamedTuple):
''' '''
Selection, expr must be a valid Python expression; can contain field names. Selection, expr must be a valid Python expression; can contain field names.
''' '''
header = Header(self.header)
try: try:
c_expr = compile(expr, 'selection', 'eval') c_expr = compile(expr, 'selection', 'eval')
except: except:
@ -139,7 +181,7 @@ class Relation(NamedTuple):
content = [] content = []
for i in self.content: for i in self.content:
# Fills the attributes dictionary with the values of the tuple # 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) for j, attr in enumerate(self.header)
} }
@ -147,8 +189,8 @@ class Relation(NamedTuple):
if eval(c_expr, attributes): if eval(c_expr, attributes):
content.append(i) content.append(i)
except Exception as e: 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(header, frozenset(content)) return Relation(self.header, frozenset(content))
def product(self, other: 'Relation') -> 'Relation': def product(self, other: 'Relation') -> 'Relation':
''' '''
@ -267,9 +309,7 @@ class Relation(NamedTuple):
def outer_right(self, other: 'Relation') -> 'Relation': def outer_right(self, other: 'Relation') -> 'Relation':
''' '''
Outer right join. Considers self as left and param as right. If the Outer right join. Considers self as left and param as right. If the
tuple has no corrispondence, empy attributes are filled with a "---" tuple has no corrispondence, empy attributes are filled with a None.
string. This is due to the fact that the None token would cause
problems when saving and reloading the relation.
Just like natural join, it works considering shared attributes. Just like natural join, it works considering shared attributes.
''' '''
return other.outer_left(self) return other.outer_left(self)
@ -278,7 +318,6 @@ class Relation(NamedTuple):
''' '''
See documentation for outer_right See documentation for outer_right
''' '''
shared = self.header.intersection(other.header) shared = self.header.intersection(other.header)
# Creating the header with all the fields, done like that because order is # Creating the header with all the fields, done like that because order is
@ -310,7 +349,7 @@ class Relation(NamedTuple):
added = True added = True
# If it didn't partecipate, adds it # If it didn't partecipate, adds it
if not added: if not added:
item = chain(i, repeat(Rstring('---'), len(noid))) item = chain(i, repeat(None, len(noid)))
content.append(tuple(item)) content.append(tuple(item))
return Relation(header, frozenset(content)) return Relation(header, frozenset(content))
@ -373,18 +412,18 @@ class Relation(NamedTuple):
m_len = [len(i) for i in self.header] # Maximum lenght string m_len = [len(i) for i in self.header] # Maximum lenght string
for f in self.content: 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]: if len(i) > m_len[col]:
m_len[col] = len(i) m_len[col] = len(i)
res = "" res = ""
for f, attr in enumerate(self.header): 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: for r in self.content:
res += "\n" res += "\n"
for col, i in enumerate(r): for col, i in enumerate(str(val) for val in r):
res += "%s" % (i.ljust(2 + m_len[col])) res += i.ljust(2 + m_len[col])
return res return res

View File

@ -23,124 +23,76 @@
import datetime import datetime
import keyword import keyword
import re import re
from typing import Union from typing import Union, Set, Any, Callable, Type, Optional
from dataclasses import dataclass
RELATION_NAME_REGEXP = re.compile(r'^[_a-z][_a-z0-9]*$', re.IGNORECASE) RELATION_NAME_REGEXP = re.compile(r'^[_a-z][_a-z0-9]*$', re.IGNORECASE)
_date_regexp = re.compile(
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(
r'^([0-9]{1,4})(\\|-|/)([0-9]{1,2})(\\|-|/)([0-9]{1,2})$' r'^([0-9]{1,4})(\\|-|/)([0-9]{1,2})(\\|-|/)([0-9]{1,2})$'
) )
CastValue = Optional[Union[str, int, float, 'Rdate']]
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)
def autocast(self) -> Union[int, float, 'Rdate', 'Rstring']:
'''
Returns the automatic cast for this
value.
'''
try: try:
return self._autocast int(value)
except: r.add(int)
except ValueError:
pass 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: try:
return self._isdate # type: ignore float(value)
except: r.add(float)
except ValueError:
pass pass
return r
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 def cast(value: str, guesses: Set) -> CastValue:
if int in guesses:
return int(value)
if Rdate in guesses:
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
@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')
year = int(r.group(1)) year = int(r.group(1))
month = int(r.group(3)) month = int(r.group(3))
day = int(r.group(5)) day = int(r.group(5))
d = datetime.date(year, month, day) return Rdate(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
class Rdate (object):
'''Represents a date'''
def __init__(self, date):
'''date: A string representing a date'''
if not isinstance(date, Rstring):
date = Rstring(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
def __hash__(self):
return self.intdate.__hash__()
def __str__(self): def __str__(self):
return self.intdate.__str__() return self.intdate.__str__()
def __add__(self, days):
res = self.intdate + datetime.timedelta(days)
return Rdate(res.__str__())
def __eq__(self, other):
return self.intdate == other.intdate
def __ge__(self, other): def __ge__(self, other):
return self.intdate >= other.intdate return self.intdate >= other.intdate
@ -153,12 +105,6 @@ class Rdate (object):
def __lt__(self, other): def __lt__(self, other):
return self.intdate < other.intdate return self.intdate < other.intdate
def __ne__(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: def is_valid_relation_name(name: str) -> bool:
'''Checks if a name is valid for a relation. '''Checks if a name is valid for a relation.

View File

@ -1,5 +1,5 @@
# Relational # 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 # Relational is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -52,13 +52,13 @@ class creatorForm(QtWidgets.QDialog):
for i in rel.content: for i in rel.content:
self.table.insertRow(self.table.rowCount()) 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 = QtWidgets.QTableWidgetItem()
item.setText(i[j]) item.setText(str(value))
self.table.setItem(self.table.rowCount() - 1, j, item) self.table.setItem(self.table.rowCount() - 1, j, item)
pass
def setup_empty(self): def setup_empty(self):
self.table.insertColumn(0) self.table.insertColumn(0)
self.table.insertColumn(0) self.table.insertColumn(0)
@ -142,11 +142,3 @@ def edit_relation(rel=None):
Form.exec_() Form.exec_()
return Form.result_relation 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))

View File

@ -249,7 +249,13 @@ class relForm(QtWidgets.QMainWindow):
for i in rel.content: for i in rel.content:
item = QtWidgets.QTreeWidgetItem() item = QtWidgets.QTreeWidgetItem()
for j,k in enumerate(i): 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) self.ui.table.addTopLevelItem(item)
# Sets columns # Sets columns
@ -286,13 +292,13 @@ class relForm(QtWidgets.QMainWindow):
filename = QtWidgets.QFileDialog.getSaveFileName( filename = QtWidgets.QFileDialog.getSaveFileName(
self, QtWidgets.QApplication.translate("Form", "Save Relation"), self, QtWidgets.QApplication.translate("Form", "Save Relation"),
"", "",
QtWidgets.QApplication.translate("Form", "Relations (*.csv)") QtWidgets.QApplication.translate("Form", "Json relations (*.json);;CSV relations (*.csv)")
)[0] )[0]
if (len(filename) == 0): # Returns if no file was selected if (len(filename) == 0): # Returns if no file was selected
return return
relname = self.ui.lstRelations.selectedItems()[0].text() relname = self.ui.lstRelations.selectedItems()[0].text()
self.user_interface.relations[relname].save(filename) self.user_interface.store(filename, relname)
def unloadRelation(self): def unloadRelation(self):
for i in self.ui.lstRelations.selectedItems(): for i in self.ui.lstRelations.selectedItems():
@ -306,9 +312,15 @@ class relForm(QtWidgets.QMainWindow):
def editRelation(self): def editRelation(self):
from relational_gui import creator from relational_gui import creator
for i in self.ui.lstRelations.selectedItems(): for i in self.ui.lstRelations.selectedItems():
try:
result = creator.edit_relation( result = creator.edit_relation(
self.user_interface.get_relation(i.text()) 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: if result != None:
self.user_interface.set_relation(i.text(), result) self.user_interface.set_relation(i.text(), result)
self.updateRelations() self.updateRelations()
@ -410,7 +422,7 @@ class relForm(QtWidgets.QMainWindow):
"", "",
QtWidgets.QApplication.translate( QtWidgets.QApplication.translate(
"Form", "Form",
"Relations (*.csv);;Text Files (*.txt);;All Files (*)" "Relations (*.json *.csv);;Text Files (*.txt);;All Files (*)"
) )
) )
filenames = f[0] filenames = f[0]

View File

@ -106,8 +106,7 @@ class SimpleCompleter:
repr(text), state, repr(response)) repr(text), state, repr(response))
return response return response
ui = maintenance.UserInterface()
relations = {}
completer = SimpleCompleter( completer = SimpleCompleter(
['SURVEY', 'LIST', 'LOAD ', 'UNLOAD ', 'HELP ', 'QUIT', 'SAVE ', '_PRODUCT ', '_UNION ', '_INTERSECTION ', ['SURVEY', 'LIST', 'LOAD ', 'UNLOAD ', 'HELP ', 'QUIT', 'SAVE ', '_PRODUCT ', '_UNION ', '_INTERSECTION ',
'_DIFFERENCE ', '_JOIN ', '_LJOIN ', '_RJOIN ', '_FJOIN ', '_PROJECTION ', '_RENAME_TO ', '_SELECTION ', '_RENAME ', '_DIVISION ']) '_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) "%s is not a valid relation name" % defname, ERROR_COLOR), file=sys.stderr)
return None return None
try: try:
relations[defname] = relation.Relation.load(filename) ui.load(filename, defname)
completer.add_completion(defname) completer.add_completion(defname)
printtty(colorize("Loaded relation %s" % defname, COLOR_GREEN)) printtty(colorize("Loaded relation %s" % defname, COLOR_GREEN))
@ -204,7 +203,7 @@ def exec_line(command: str) -> None:
elif command.startswith('HELP'): elif command.startswith('HELP'):
help(command) help(command)
elif command == 'LIST': # Lists all the loaded relations elif command == 'LIST': # Lists all the loaded relations
for i in relations: for i in ui.relations:
if not i.startswith('_'): if not i.startswith('_'):
print(i) print(i)
elif command == 'SURVEY': elif command == 'SURVEY':
@ -225,9 +224,10 @@ def exec_line(command: str) -> None:
pars = command.split(' ') pars = command.split(' ')
if len(pars) < 2: if len(pars) < 2:
print(colorize("Missing parameter", ERROR_COLOR)) print(colorize("Missing parameter", ERROR_COLOR))
return elif len(pars) > 2:
if pars[1] in relations: print(colorize("Too many parameter", ERROR_COLOR))
del relations[pars[1]] if pars[1] in ui.relations:
ui.unload(pars[1])
completer.remove_completion(pars[1]) completer.remove_completion(pars[1])
else: else:
print(colorize("No such relation %s" % pars[1], ERROR_COLOR)) print(colorize("No such relation %s" % pars[1], ERROR_COLOR))
@ -240,11 +240,8 @@ def exec_line(command: str) -> None:
filename = pars[1] filename = pars[1]
defname = pars[2] defname = pars[2]
if defname not in relations:
print(colorize("No such relation %s" % defname, ERROR_COLOR))
return
try: try:
relations[defname].save(filename) ui.store(filename, defname)
except Exception as e: except Exception as e:
print(colorize(e, ERROR_COLOR)) print(colorize(e, ERROR_COLOR))
else: else:
@ -298,7 +295,7 @@ def exec_query(command: str) -> None:
# Execute query # Execute query
try: try:
pyquery = parser.parse(query) pyquery = parser.parse(query)
result = pyquery(relations) result = pyquery(ui.relations)
printtty(colorize("-> query: %s" % pyquery, COLOR_GREEN)) printtty(colorize("-> query: %s" % pyquery, COLOR_GREEN))
@ -306,7 +303,7 @@ def exec_query(command: str) -> None:
print() print()
print(result) print(result)
relations[relname] = result ui.relations[relname] = result
completer.add_completion(relname) completer.add_completion(relname)
except Exception as e: except Exception as e:

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,4 +1 @@
name {"content": [["eve"], ["john"], ["duncan"]], "header": ["name"]}
eve
john
duncan

View File

@ -1,6 +1 @@
id,name,chief,age,skill {"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"]}
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

View File

@ -1,6 +1 @@
"date" {"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"]}
"2008-12-12"
"2007-08-12"
"1985-05-09"
"1988-4-21"
"1992-7-27"

View File

@ -1,3 +1 @@
date {"content": [[{"day": 12, "year": 2008, "month": 12}], [{"day": 12, "year": 2007, "month": 8}]], "header": ["date"]}
2008-12-12
2007-08-12

View File

@ -1,12 +1 @@
id,name,chief,age,skill {"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"]}
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

View File

@ -1,19 +1 @@
id,skill,name,chief,age {"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"]}
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

View File

@ -1,2 +1 @@
id,name,chief,age {"content": [[4, "eve", 0, 25]], "header": ["id", "name", "chief", "age"]}
4,eve,0,25

View File

@ -1,2 +1 @@
id,name,chief,age {"content": [[4, "eve", 0, 25]], "header": ["id", "name", "chief", "age"]}
4,eve,0,25

View File

@ -1,3 +1 @@
name {"content": [["eve"], ["duncan"]], "header": ["name"]}
duncan
eve

View File

@ -1,17 +1 @@
id,name,chief,age,skill {"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"]}
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

View File

@ -1,18 +1 @@
id,name,chief,age,skill {"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"]}
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

View File

@ -1,2 +1 @@
id,name,chief,age,rating {"content": [[1, "carl", 0, 20, 6]], "header": ["id", "name", "chief", "age", "rating"]}
1,carl,0,20,6

View File

@ -1,2 +1 @@
date {"content": [[{"day": 12, "year": 2008, "month": 12}]], "header": ["date"]}
2008-12-12

View File

@ -1,9 +1 @@
name,age {"content": [["eve", 25], ["duncan", 30], ["paul", 30], ["carl", 20], ["alia", 28], ["dean", 33], ["jack", 22], ["john", 30]], "header": ["name", "age"]}
eve,25
dean,33
carl,20
paul,30
john,30
jack,22
duncan,30
alia,28

View File

@ -1,7 +1 @@
name,age,chief_name,chief_age {"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"]}
dean,33,carl,20
alia,28,carl,20
paul,30,eve,25
eve,25,jack,22
john,30,carl,20
duncan,30,eve,25

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age,room {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age,room {"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"]}
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

View File

@ -1,13 +1 @@
id,name,chief,age,room,phone {"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"]}
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"

View File

@ -1,2 +1 @@
id,name,chief,age,skill {"content": [[0, "jack", 0, 22, "C"]], "header": ["id", "name", "chief", "age", "skill"]}
0,jack,0,22,C

View File

@ -1,9 +1 @@
id,n,chief,a {"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"]}
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

View File

@ -1,5 +1 @@
i,name,chief,age {"content": [[4, "eve", 0, 25], [6, "paul", 4, 30], [2, "john", 1, 30], [0, "jack", 0, 22]], "header": ["i", "name", "chief", "age"]}
0,jack,0,22
2,john,1,30
4,eve,0,25
6,paul,4,30

View File

@ -1,2 +1 @@
phone,name {"content": [[1041, "carl"]], "header": ["phone", "name"]}
1041,carl

View File

@ -1,2 +1 @@
id,name,chief,age,skill {"content": [[7, "alia", 1, 28, "PHP"]], "header": ["id", "name", "chief", "age", "skill"]}
7,alia,1,28,PHP

View File

@ -1,3 +1 @@
name {"content": [["alia"], ["jack"]], "header": ["name"]}
jack
alia

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1,2 +1 @@
id,name,chief,age {"content": [[2, "john", 1, 30]], "header": ["id", "name", "chief", "age"]}
2,john,1,30

View File

@ -1,18 +1 @@
id,skill,name,chief,age {"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"]}
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

View File

@ -1,2 +1 @@
id,skill,name,chief,age {"content": [[3, "C++", "dean", 1, 33]], "header": ["id", "skill", "name", "chief", "age"]}
3,C++,dean,1,33

View File

@ -1,3 +1 @@
id,name,chief,age,skill {"content": [[4, "eve", 0, 25, "C"], [0, "jack", 0, 22, "C"]], "header": ["id", "name", "chief", "age", "skill"]}
0,jack,0,22,C
4,eve,0,25,C

View File

@ -1 +1 @@
σid=='---' (people⧓person_room ⧓ rooms) σ id is None (people⧓person_room ⧓ rooms)

View File

@ -1,5 +1 @@
id,name,chief,age,room,phone {"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"]}
---,---,---,---,"0","1515"
---,---,---,---,"3","1601"
---,---,---,---,"6","1424"
---,---,---,---,"7","1294"

View File

@ -1,4 +1 @@
name,age,skill {"content": [["eve", 25, "C++"], ["eve", 25, "C"], ["eve", 25, "Perl"]], "header": ["name", "age", "skill"]}
eve,25,Perl
eve,25,C
eve,25,C++

View File

@ -1,8 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1 +1 @@
id,name,chief,age {"content": [], "header": ["id", "name", "chief", "age"]}

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,4 +1 @@
id,name,chief,age {"content": [[4, "eve", 0, 25], [0, "jack", 0, 22], [7, "alia", 1, 28]], "header": ["id", "name", "chief", "age"]}
7,alia,1,28
4,eve,0,25
0,jack,0,22

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,9 +1 @@
id,name,chief,age {"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"]}
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

View File

@ -1,4 +1 @@
id,skill {"content": [[2, "C"], [0, "C"], [4, "C"]], "header": ["id", "skill"]}
0,C
2,C
4,C

View File

@ -1,8 +1 @@
id,name,chief,age,skill {"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"]}
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

View File

@ -1,3 +1 @@
id,skill {"content": [[7, "C"], [5, "C"]], "header": ["id", "skill"]}
5,C
7,C

View File

@ -1,3 +1 @@
id,name,chief,age {"content": [[3, "dean", 1, 33], [1, "carl", 0, 20]], "header": ["id", "name", "chief", "age"]}
3,dean,1,33
1,carl,0,20

View File

@ -1,8 +1 @@
name,skill {"content": [["jack", "C"], ["eve", "C"], ["duncan", "C"], ["alia", "C"], ["eve", "Perl"], ["duncan", "Perl"], ["john", "C"]], "header": ["name", "skill"]}
jack,C
eve,Perl
duncan,Perl
duncan,C
john,C
alia,C
eve,C

View File

@ -1,2 +1 @@
id,age,chief,name {"content": [[1, 20, 0, "carl"]], "header": ["id", "age", "chief", "name"]}
1,20,0,carl