Merge pull request #31 from ltworf/column_types

Types per column, not per cell. Cast when loading.
master
Salvo 'LtWorf' Tomaselli 2020-08-24 19:31:55 +07: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

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

@ -1,4 +1,7 @@
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
- Refactored parser to use better typing
- Refactored and fixed some optimizations

4
debian/control vendored

@ -3,7 +3,7 @@ Section: math
Priority: optional
Maintainer: Salvo 'LtWorf' Tomaselli <tiposchi@tiscali.it>
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.

@ -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')

@ -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
@ -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:
@ -100,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]:
'''
@ -161,8 +171,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

@ -19,10 +19,10 @@
# 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 *
from typing import FrozenSet, Iterable, List, Dict, Tuple, Optional
from dataclasses import dataclass
from pathlib import Path
from relational.rtypes import *
@ -33,8 +33,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.
@ -58,47 +58,57 @@ class Relation(NamedTuple):
method.
'''
header: 'Header'
content: FrozenSet[Tuple[Rstring, ...]]
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 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)
r_content: List[Tuple[Rstring, ...]] = []
for row in content:
content_row: Tuple[Rstring, ...] = tuple(Rstring(i) for i in row)
if len(content_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))
def __iter__(self):
return iter(self.content)
def __contains__(self, key):
return key in self.content
with open(filename) as fp:
from json import load as jload
from typedload import load
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]], ...]) # type: ignore
content.append(t_row)
return Relation(header, frozenset(content))
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
@ -109,6 +119,40 @@ class Relation(NamedTuple):
# 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 = []
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':
'''If two relations share the same attributes in a different order, this method
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.
'''
header = Header(self.header)
try:
c_expr = compile(expr, 'selection', 'eval')
except:
@ -139,7 +181,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)
}
@ -147,8 +189,8 @@ class Relation(NamedTuple):
if eval(c_expr, attributes):
content.append(i)
except Exception as e:
raise Exception(f'Failed to evaluate {expr}\n{e}')
return Relation(header, frozenset(content))
raise Exception(f'Failed to evaluate {expr} with {attributes}\n{e}')
return Relation(self.header, frozenset(content))
def product(self, other: 'Relation') -> 'Relation':
'''
@ -267,9 +309,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)
@ -278,7 +318,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
@ -310,7 +349,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(None, len(noid)))
content.append(tuple(item))
return Relation(header, frozenset(content))
@ -373,18 +412,18 @@ 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)
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]))
for col, i in enumerate(str(val) for val in r):
res += i.ljust(2 + m_len[col])
return res

@ -23,124 +23,76 @@
import datetime
import keyword
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)
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 = Optional[Union[str, int, float, 'Rdate']]
class Rdate (object):
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)
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:
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'''
if not isinstance(date, Rstring):
date = Rstring(date)
@property
def intdate(self) -> datetime.date:
return datetime.date(self.year, self.month, self.day)
self.intdate = date.getDate()
self.day = self.intdate.day
self.month = self.intdate.month
self.weekday = self.intdate.weekday()
self.year = self.intdate.year
@property
def weekday(self) -> int:
return self.intdate.weekday()
def __hash__(self):
return self.intdate.__hash__()
@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))
month = int(r.group(3))
day = int(r.group(5))
return Rdate(year, month, day)
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):
return self.intdate == other.intdate
def __ge__(self, other):
return self.intdate >= other.intdate
@ -153,12 +105,6 @@ class Rdate (object):
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
def is_valid_relation_name(name: str) -> bool:
'''Checks if a name is valid for a relation.

@ -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
@ -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)
@ -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))

@ -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
@ -286,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():
@ -306,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()
@ -410,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]

@ -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:

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

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

@ -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"]}

@ -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"]}

@ -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"]}

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

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

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

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

@ -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"]}

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

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

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

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

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

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

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

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

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

@ -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"]}

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

@ -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"]}

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

@ -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"]}

@ -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"]}

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