Merge pull request #25 from ltworf/mypy

Mypy
master
Salvo 'LtWorf' Tomaselli 2020-08-12 19:20:00 +07:00 committed by GitHub
commit 8f46eae9f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 178 deletions

@ -8,4 +8,5 @@ install:
- pip install xtermcolor - pip install xtermcolor
script: script:
- make mypy
- make test - make test

@ -15,6 +15,9 @@ relational_gui/rel_edit.py:
relational_gui/resources.py: relational_gui/resources.py:
pyrcc5 relational_gui/resources.qrc > relational_gui/resources.py pyrcc5 relational_gui/resources.qrc > relational_gui/resources.py
.PHONY: mypy
mypy:
mypy relational
.PHONY: test .PHONY: test
test: test:

@ -30,29 +30,19 @@
from io import StringIO from io import StringIO
from tokenize import generate_tokens from tokenize import generate_tokens
from typing import Tuple, Dict from typing import Tuple, Dict, List
from relational.relation import Relation from relational.relation import Relation
from relational import parser from relational import parser
from relational.parser import Binary, Unary, Node, PRODUCT, \
DIFFERENCE, UNION, INTERSECTION, DIVISION, JOIN, \
JOIN_LEFT, JOIN_RIGHT, JOIN_FULL, PROJECTION, \
SELECTION, RENAME, ARROW
sel_op = ( sel_op = (
'//=', '**=', 'and', 'not', 'in', '//', '**', '<<', '>>', '==', '!=', '>=', '<=', '+=', '-=', '//=', '**=', 'and', 'not', 'in', '//', '**', '<<', '>>', '==', '!=', '>=', '<=', '+=', '-=',
'*=', '/=', '%=', 'or', '+', '-', '*', '/', '&', '|', '^', '~', '<', '>', '%', '=', '(', ')', ',', '[', ']') '*=', '/=', '%=', 'or', '+', '-', '*', '/', '&', '|', '^', '~', '<', '>', '%', '=', '(', ')', ',', '[', ']')
PRODUCT = parser.PRODUCT
DIFFERENCE = parser.DIFFERENCE
UNION = parser.UNION
INTERSECTION = parser.INTERSECTION
DIVISION = parser.DIVISION
JOIN = parser.JOIN
JOIN_LEFT = parser.JOIN_LEFT
JOIN_RIGHT = parser.JOIN_RIGHT
JOIN_FULL = parser.JOIN_FULL
PROJECTION = parser.PROJECTION
SELECTION = parser.SELECTION
RENAME = parser.RENAME
ARROW = parser.ARROW
def find_duplicates(node, dups=None): def find_duplicates(node, dups=None):
''' '''
@ -64,41 +54,6 @@ def find_duplicates(node, dups=None):
dups[str(node)] = node dups[str(node)] = node
def replace_leaves(node, context):
'''
Node is a parsed tree
context is a dictionary containing
parsed trees as values.
If a name appearing in node appears
also in context, the parse tree is
modified to replace the node with the
subtree found in context.
'''
if node.kind == parser.UNARY:
replace_leaves(node.child, context)
elif node.kind == parser.BINARY:
replace_leaves(node.left, context)
replace_leaves(node.right, context)
elif node.name in context:
replace_node(node, context[node.name])
def replace_node(replace, replacement):
'''This function replaces "replace" node with the node "with",
the father of the node will now point to the with node'''
replace.name = replacement.name
replace.kind = replacement.kind
if replace.kind == parser.UNARY:
replace.child = replacement.child
replace.prop = replacement.prop
elif replace.kind == parser.BINARY:
replace.right = replacement.right
replace.left = replacement.left
def duplicated_select(n: parser.Node) -> Tuple[parser.Node, int]: def duplicated_select(n: parser.Node) -> Tuple[parser.Node, int]:
'''This function locates and deletes things like '''This function locates and deletes things like
σ a ( σ a(C)) and the ones like σ a ( σ b(C)) σ a ( σ a(C)) and the ones like σ a ( σ b(C))
@ -107,7 +62,7 @@ def duplicated_select(n: parser.Node) -> Tuple[parser.Node, int]:
in and in and
''' '''
changes = 0 changes = 0
while n.name == SELECTION and n.child.name == SELECTION: while isinstance(n, Unary) and n.name == SELECTION and isinstance(n.child, Unary) and n.child.name == SELECTION:
changes += 1 changes += 1
prop = n.prop prop = n.prop
@ -117,7 +72,7 @@ def duplicated_select(n: parser.Node) -> Tuple[parser.Node, int]:
# This adds parenthesis if they are needed # This adds parenthesis if they are needed
if n.child.prop.startswith('(') or n.prop.startswith('('): if n.child.prop.startswith('(') or n.prop.startswith('('):
prop = '(%s)' % prop prop = '(%s)' % prop
n = parser.Unary( n = Unary(
SELECTION, SELECTION,
prop, prop,
n.child.child, n.child.child,
@ -136,38 +91,45 @@ def futile_union_intersection_subtraction(n: parser.Node) -> Tuple[parser.Node,
σ k (R) R --> σ k (R) σ k (R) R --> σ k (R)
''' '''
changes = 0 if not isinstance(n, Binary):
return n, 0
# Union and intersection of the same thing # Union and intersection of the same thing
if n.name in (UNION, INTERSECTION, JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL) and n.left == n.right: if n.name in (UNION, INTERSECTION, JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL) and n.left == n.right:
return n.left, 1 return n.left, 1
# selection and union of the same thing # selection and union of the same thing
elif (n.name == UNION): elif n.name == UNION:
if n.left.name == SELECTION and n.left.child == n.right: if n.left.name == SELECTION and isinstance(n.left, Unary) and n.left.child == n.right:
return n.right, 1 return n.right, 1
elif n.right.name == SELECTION and n.right.child == n.left: elif n.right.name == SELECTION and isinstance(n.right, Unary) and n.right.child == n.left:
return n.left, 1 return n.left, 1
# selection and intersection of the same thing # selection and intersection of the same thing
elif n.name == INTERSECTION: elif n.name == INTERSECTION:
if n.left.name == SELECTION and n.left.child == n.right: if n.left.name == SELECTION and isinstance(n.left, Unary) and n.left.child == n.right:
return n.left, 1 return n.left, 1
elif n.right.name == SELECTION and n.right.child == n.left: elif n.right.name == SELECTION and \
isinstance(n.right, Unary) and \
n.right.child == n.left:
return n.right, 1 return n.right, 1
# Subtraction and selection of the same thing # Subtraction and selection of the same thing
elif n.name == DIFFERENCE and \ elif n.name == DIFFERENCE and \
isinstance(n, Binary) and \
n.right.name == SELECTION and \ n.right.name == SELECTION and \
isinstance(n.right, Unary) and \
n.right.child == n.left: n.right.child == n.left:
return parser.Unary( return Unary(
SELECTION, SELECTION,
'(not (%s))' % n.right.prop, '(not (%s))' % n.right.prop,
n.right.child), 1 n.right.child), 1
# Subtraction of the same thing or with selection on the left child # Subtraction of the same thing or with selection on the left child
elif n.name == DIFFERENCE and (n.left == n.right or (n.left.name == SELECTION and n.left.child == n.right)): elif n.name == DIFFERENCE and \
return parser.Unary( isinstance(n, Binary) and \
(n.left == n.right or (n.left.name == SELECTION and isinstance(n.left, Unary) and n.left.child == n.right)):
return Unary(
SELECTION, SELECTION,
'False', 'False',
n.get_left_leaf() n.get_left_leaf()
@ -182,11 +144,12 @@ def down_to_unions_subtractions_intersections(n: parser.Node) -> Tuple[parser.No
''' '''
changes = 0 changes = 0
_o = (UNION, DIFFERENCE, INTERSECTION) _o = (UNION, DIFFERENCE, INTERSECTION)
if n.name == SELECTION and n.child.name in _o: if isinstance(n, Unary) and n.name == SELECTION and n.child.name in _o:
l = parser.Unary(SELECTION, n.prop, n.child.left) assert isinstance(n.child, Binary)
r = parser.Unary(SELECTION, n.prop, n.child.right) l = Unary(SELECTION, n.prop, n.child.left)
r = Unary(SELECTION, n.prop, n.child.right)
return parser.Binary(n.child.name, l, r), 1 return Binary(n.child.name, l, r), 1
return n, 0 return n, 0
@ -194,8 +157,8 @@ def duplicated_projection(n: parser.Node) -> Tuple[parser.Node, int]:
'''This function locates thing like π i ( π j (R)) and replaces '''This function locates thing like π i ( π j (R)) and replaces
them with π i (R)''' them with π i (R)'''
if n.name == PROJECTION and n.child.name == PROJECTION: if isinstance(n, Unary) and n.name == PROJECTION and isinstance(n.child, Unary) and n.child.name == PROJECTION:
return parser.Unary( return Unary(
PROJECTION, PROJECTION,
n.prop, n.prop,
n.child.child), 1 n.child.child), 1
@ -205,14 +168,14 @@ def duplicated_projection(n: parser.Node) -> Tuple[parser.Node, int]:
def selection_inside_projection(n: parser.Node) -> Tuple[parser.Node, int]: def selection_inside_projection(n: parser.Node) -> Tuple[parser.Node, int]:
'''This function locates things like σ j (π k(R)) and '''This function locates things like σ j (π k(R)) and
converts them into π k(σ j (R))''' converts them into π k(σ j (R))'''
if n.name == SELECTION and n.child.name == PROJECTION: if isinstance(n, Unary) and n.name == SELECTION and isinstance(n.child, Unary) and n.child.name == PROJECTION:
child = parser.Unary( child = Unary(
SELECTION, SELECTION,
n.prop, n.prop,
n.child.child n.child.child
) )
return parser.Unary(PROJECTION, n.child.prop, child), 0 return Unary(PROJECTION, n.child.prop, child), 0
return n, 0 return n, 0
@ -222,12 +185,17 @@ def swap_union_renames(n: parser.Node) -> Tuple[parser.Node, int]:
and replaces them with and replaces them with
ρ ab(R Q). ρ ab(R Q).
Does the same with subtraction and intersection''' Does the same with subtraction and intersection'''
if n.name in (DIFFERENCE, UNION, INTERSECTION) and n.left.name == RENAME and n.right.name == RENAME: if n.name in (DIFFERENCE, UNION, INTERSECTION) and \
isinstance(n, Binary) and \
n.left.name == RENAME and \
isinstance(n.left, Unary) and\
n.right.name == RENAME and \
isinstance(n.right, Unary):
l_vars = n.left.get_rename_prop() l_vars = n.left.get_rename_prop()
r_vars = n.right.get_rename_prop() r_vars = n.right.get_rename_prop()
if r_vars == l_vars: if r_vars == l_vars:
child = parser.Binary(n.name, n.left.child, n.right.child) child = Binary(n.name, n.left.child, n.right.child)
return parser.Unary(RENAME, n.left.prop, child), 1 return Unary(RENAME, n.left.prop, child), 1
return n, 0 return n, 0
@ -239,7 +207,7 @@ def futile_renames(n: parser.Node) -> Tuple[parser.Node, int]:
or removes the operation entirely if they all get removed or removes the operation entirely if they all get removed
''' '''
if n.name == RENAME: if isinstance(n, Unary) and n.name == RENAME:
renames = n.get_rename_prop() renames = n.get_rename_prop()
changes = False changes = False
for k, v in renames.items(): for k, v in renames.items():
@ -261,11 +229,14 @@ def subsequent_renames(n: parser.Node) -> Tuple[parser.Node, int]:
into into
ρ ... (A) ρ ... (A)
''' '''
if n.name == RENAME and n.child.name == RENAME: if isinstance(n, Unary) and \
n.name == RENAME and \
isinstance(n.child, Unary) and \
n.child.name == RENAME:
# Located two nested renames. # Located two nested renames.
prop = n.prop + ',' + n.child.prop prop = n.prop + ',' + n.child.prop
child = n.child.child child = n.child.child
n = parser.Unary(RENAME, prop, child) n = Unary(RENAME, prop, child)
# Creating a dictionary with the attributes # Creating a dictionary with the attributes
renames = n.get_rename_prop() renames = n.get_rename_prop()
@ -292,11 +263,11 @@ def subsequent_renames(n: parser.Node) -> Tuple[parser.Node, int]:
return n, 0 return n, 0
class level_string(str): class LevelString(str):
level = 0 level = 0
def tokenize_select(expression): def tokenize_select(expression: str) -> List[LevelString]:
'''This function returns the list of tokens present in a '''This function returns the list of tokens present in a
selection. The expression can contain parenthesis. selection. The expression can contain parenthesis.
It will use a subclass of str with the attribute level, which It will use a subclass of str with the attribute level, which
@ -304,8 +275,6 @@ def tokenize_select(expression):
g = generate_tokens(StringIO(str(expression)).readline) g = generate_tokens(StringIO(str(expression)).readline)
l = list(token[1] for token in g) l = list(token[1] for token in g)
l.remove('')
# Changes the 'a','.','method' token group into a single 'a.method' token # Changes the 'a','.','method' token group into a single 'a.method' token
try: try:
while True: while True:
@ -316,17 +285,21 @@ def tokenize_select(expression):
except: except:
pass pass
r = []
level = 0 level = 0
for i in range(len(l)): for i in l:
l[i] = level_string(l[i]) if not i:
l[i].level = level continue
value = LevelString(i)
value.level = level
if l[i] == '(': if value == '(':
level += 1 level += 1
elif l[i] == ')': elif value == ')':
level -= 1 level -= 1
r.append(value)
return l return r
def swap_rename_projection(n: parser.Node) -> Tuple[parser.Node, int]: def swap_rename_projection(n: parser.Node) -> Tuple[parser.Node, int]:
@ -340,7 +313,10 @@ def swap_rename_projection(n: parser.Node) -> Tuple[parser.Node, int]:
Will also eliminate fields in the rename that are cut in the projection. Will also eliminate fields in the rename that are cut in the projection.
''' '''
if n.name == PROJECTION and n.child.name == RENAME: if isinstance(n, Unary) and \
n.name == PROJECTION and \
isinstance(n.child, Unary) and \
n.child.name == RENAME:
# π index,name(ρ id➡index(R)) # π index,name(ρ id➡index(R))
renames = n.child.get_rename_prop() renames = n.child.get_rename_prop()
projections = set(n.get_projection_prop()) projections = set(n.get_projection_prop())
@ -356,16 +332,16 @@ def swap_rename_projection(n: parser.Node) -> Tuple[parser.Node, int]:
if i not in projections: if i not in projections:
del renames[i] del renames[i]
child = parser.Unary(PROJECTION,'' , n.child.child) child = Unary(PROJECTION,'' , n.child.child)
child.set_projection_prop(projections) child.set_projection_prop(list(projections))
n = parser.Unary(RENAME, '', child) n = Unary(RENAME, '', child)
n.set_rename_prop(renames) n.set_rename_prop(renames)
return n, 1 return n, 1
return n, 0 return n, 0
def swap_rename_select(n: parser.Node) -> int: def swap_rename_select(n: parser.Node) -> Tuple[parser.Node, int]:
'''This function locates things like '''This function locates things like
σ k(ρ j(R)) σ k(ρ j(R))
and replaces them with and replaces them with
@ -373,7 +349,10 @@ def swap_rename_select(n: parser.Node) -> int:
Renaming the attributes used in the Renaming the attributes used in the
selection, so the operation is still valid.''' selection, so the operation is still valid.'''
if n.name == SELECTION and n.child.name == RENAME: if isinstance(n, Unary) and \
n.name == SELECTION and \
isinstance(n.child, Unary) and \
n.child.name == RENAME:
# This is an inverse mapping for the rename # This is an inverse mapping for the rename
renames = {v: k for k, v in n.child.get_rename_prop().items()} renames = {v: k for k, v in n.child.get_rename_prop().items()}
@ -384,26 +363,28 @@ def swap_rename_select(n: parser.Node) -> int:
for i in range(len(tokens)): for i in range(len(tokens)):
splitted = tokens[i].split('.', 1) splitted = tokens[i].split('.', 1)
if splitted[0] in renames: if splitted[0] in renames:
tokens[i] = renames[splitted[0]] tokens[i] = LevelString(renames[splitted[0]])
if len(splitted) > 1: if len(splitted) > 1:
tokens[i] += '.' + splitted[1] tokens[i] = LevelString(tokens[i] + '.' + splitted[1])
child = parser.Unary(SELECTION, ' '.join(tokens), n.child.child) child = Unary(SELECTION, ' '.join(tokens), n.child.child)
return parser.Unary(RENAME, n.child.prop, child), 1 return Unary(RENAME, n.child.prop, child), 1
return n, 0 return n, 0
def select_union_intersect_subtract(n: parser.Node) -> int: def select_union_intersect_subtract(n: parser.Node) -> Tuple[parser.Node, int]:
'''This function locates things like '''This function locates things like
σ i(a) σ q(a) σ i(a) σ q(a)
and replaces them with and replaces them with
σ (i OR q) (a) σ (i OR q) (a)
Removing a O() operation like the union''' Removing a O() operation like the union'''
if n.name in {UNION, INTERSECTION, DIFFERENCE} and \ if isinstance(n, Binary) and \
n.name in {UNION, INTERSECTION, DIFFERENCE} and \
isinstance(n.left, Unary) and \
n.left.name == SELECTION and \ n.left.name == SELECTION and \
isinstance(n.right, Unary) and \
n.right.name == SELECTION and \ n.right.name == SELECTION and \
n.left.child == n.right.child: n.left.child == n.right.child:
d = {UNION: 'or', INTERSECTION: 'and', DIFFERENCE: 'and not'} d = {UNION: 'or', INTERSECTION: 'and', DIFFERENCE: 'and not'}
op = d[n.name] op = d[n.name]
@ -423,7 +404,7 @@ def select_union_intersect_subtract(n: parser.Node) -> int:
prop = t_str % (n.left.prop, op, n.right.prop) prop = t_str % (n.left.prop, op, n.right.prop)
else: else:
prop = '%s %s %s' % (n.left.prop, op, n.right.prop) prop = '%s %s %s' % (n.left.prop, op, n.right.prop)
return parser.Unary(SELECTION, prop, n.left.child), 1 return Unary(SELECTION, prop, n.left.child), 1
return n, 0 return n, 0
@ -432,18 +413,23 @@ def union_and_product(n: parser.Node) -> Tuple[parser.Node, int]:
A * B A * C = A * (B C) A * B A * C = A * (B C)
Same thing with inner join Same thing with inner join
''' '''
if n.name == UNION and n.left.name in {PRODUCT, JOIN} and n.left.name == n.right.name: if isinstance(n, Binary) and \
n.name == UNION and \
isinstance(n.left, Binary) and \
n.left.name in {PRODUCT, JOIN} and \
isinstance(n.right, Binary) and \
n.left.name == n.right.name:
if n.left.left == n.right.left or n.left.left == n.right.right: if n.left.left == n.right.left or n.left.left == n.right.right:
l = n.left.right l = n.left.right
r = n.right.left if n.left.left == n.right.right else n.right.right r = n.right.left if n.left.left == n.right.right else n.right.right
newchild = parser.Binary(UNION, l, r) newchild = Binary(UNION, l, r)
return parser.Binary(n.left.name, n.left.left, newchild), 1 return Binary(n.left.name, n.left.left, newchild), 1
elif n.left.right == n.right.left or n.left.left == n.right.right: elif n.left.right == n.right.left or n.left.left == n.right.right:
l = n.left.left l = n.left.left
r = n.right.left if n.right.left == n.right.right else n.right.right r = n.right.left if n.right.left == n.right.right else n.right.right
newchild = parser.Binary(UNION, l, r) newchild = Binary(UNION, l, r)
return parser.Binary(n.left.name, n.left.right, newchild), 1 return Binary(n.left.name, n.left.right, newchild), 1
return n, 0 return n, 0
@ -459,37 +445,41 @@ def projection_and_union(n: parser.Node, rels: Dict[str, Relation]) -> Tuple[par
''' '''
changes = 0 changes = 0
if n.name == UNION and \ if n.name == UNION and \
isinstance(n, Binary) and \
n.left.name == PROJECTION and \ n.left.name == PROJECTION and \
isinstance(n.left, Unary) and \
n.right.name == PROJECTION and \ n.right.name == PROJECTION and \
isinstance(n.right, Unary) and \
set(n.left.child.result_format(rels)) == set(n.right.child.result_format(rels)): set(n.left.child.result_format(rels)) == set(n.right.child.result_format(rels)):
child = Binary(UNION, n.left.child, n.right.child)
child = parser.Binary(UNION, n.left.child, n.right.child) return Unary(PROJECTION, n.right.prop, child), 0
return parser.Unary(PROJECTION, n.right.prop, child), 0
return n, 0 return n, 0
def selection_and_product(n: parser.Node, rels: Dict[str, Relation]) -> parser.Node: def selection_and_product(n: parser.Node, rels: Dict[str, Relation]) -> Tuple[parser.Node, int]:
'''This function locates things like σ k (R*Q) and converts them into '''This function locates things like σ k (R*Q) and converts them into
σ l (σ j (R) * σ i (Q)). Where j contains only attributes belonging to R, σ l (σ j (R) * σ i (Q)). Where j contains only attributes belonging to R,
i contains attributes belonging to Q and l contains attributes belonging to both''' i contains attributes belonging to Q and l contains attributes belonging to both'''
if n.name == SELECTION and n.child.name in (PRODUCT, JOIN): if isinstance(n, Unary) and n.name == SELECTION and \
isinstance(n.child, Binary) and \
n.child.name in (PRODUCT, JOIN):
l_attr = n.child.left.result_format(rels) l_attr = n.child.left.result_format(rels)
r_attr = n.child.right.result_format(rels) r_attr = n.child.right.result_format(rels)
tokens = tokenize_select(n.prop) tokens = tokenize_select(n.prop)
groups = [] groups: List[List[LevelString]] = []
temp = [] temp: List[LevelString] = []
for i in tokens: for k in tokens:
if i == 'and' and i.level == 0: if k == 'and' and k.level == 0:
groups.append(temp) groups.append(temp)
temp = [] temp = []
else: else:
temp.append(i) temp.append(k)
if len(temp) != 0: if len(temp):
groups.append(temp) groups.append(temp)
temp = [] del temp
left = [] left = []
right = [] right = []
@ -500,10 +490,10 @@ def selection_and_product(n: parser.Node, rels: Dict[str, Relation]) -> parser.N
r_fields = False # has fields in left? r_fields = False # has fields in left?
for j in set(i).difference(sel_op): for j in set(i).difference(sel_op):
j = j.split('.')[0] t = j.split('.')[0]
if j in l_attr: # Field in left if t in l_attr: # Field in left
l_fields = True l_fields = True
if j in r_attr: # Field in right if t in r_attr: # Field in right
r_fields = True r_fields = True
if l_fields and not r_fields: if l_fields and not r_fields:
@ -518,7 +508,7 @@ def selection_and_product(n: parser.Node, rels: Dict[str, Relation]) -> parser.N
l_prop = ' and '.join((' '.join(i) for i in left)) l_prop = ' and '.join((' '.join(i) for i in left))
if '(' in l_prop: if '(' in l_prop:
l_prop = '(%s)' % l_prop l_prop = '(%s)' % l_prop
l_node = parser.Unary(SELECTION, l_prop, n.child.left) l_node: Node = Unary(SELECTION, l_prop, n.child.left)
else: else:
l_node = n.child.left l_node = n.child.left
@ -527,18 +517,18 @@ def selection_and_product(n: parser.Node, rels: Dict[str, Relation]) -> parser.N
r_prop = ' and '.join((' '.join(i) for i in right)) r_prop = ' and '.join((' '.join(i) for i in right))
if '(' in r_prop: if '(' in r_prop:
r_prop = '(%s)' % r_prop r_prop = '(%s)' % r_prop
r_node = parser.Unary(SELECTION, r_prop, n.child.right) r_node: Node = Unary(SELECTION, r_prop, n.child.right)
else: else:
r_node = n.child.right r_node = n.child.right
b_node = parser.Binary(n.child.name, l_node, r_node) b_node = Binary(n.child.name, l_node, r_node)
# Changing main selection # Changing main selection
if both: if both:
both_prop = ' and '.join((' '.join(i) for i in both)) both_prop = ' and '.join((' '.join(i) for i in both))
if '(' in both_prop: if '(' in both_prop:
both_prop = '(%s)' % both_prop both_prop = '(%s)' % both_prop
r = parser.Unary(SELECTION, both_prop, b_node) r = Unary(SELECTION, both_prop, b_node)
return r, len(left) + len(right) return r, len(left) + len(right)
else: # No need for general select else: # No need for general select
return b_node, 1 return b_node, 1
@ -550,7 +540,7 @@ def useless_projection(n: parser.Node, rels: Dict[str, Relation]) -> Tuple[parse
''' '''
Removes projections that are over all the fields Removes projections that are over all the fields
''' '''
if n.name == PROJECTION and \ if isinstance(n, Unary) and n.name == PROJECTION and \
set(n.child.result_format(rels)) == set(i.strip() for i in n.prop.split(',')): set(n.child.result_format(rels)) == set(i.strip() for i in n.prop.split(',')):
return n.child, 1 return n.child, 1

@ -36,6 +36,7 @@ def optimize_program(code, rels: Dict[str, Relation]):
Optimize an entire program, composed by multiple expressions Optimize an entire program, composed by multiple expressions
and assignments. and assignments.
''' '''
raise NotImplementedError()
lines = code.split('\n') lines = code.split('\n')
context = {} context = {}
@ -71,24 +72,19 @@ def optimize_all(expression: Union[str, Node], rels: Dict[str, Relation], specif
else: else:
raise (TypeError("expression must be a string or a node")) raise (TypeError("expression must be a string or a node"))
if isinstance(debug, list):
dbg = True
else:
dbg = False
total = 1 total = 1
while total != 0: while total != 0:
total = 0 total = 0
if specific: if specific:
for i in optimizations.specific_optimizations: for i in optimizations.specific_optimizations:
n, c = recursive_scan(i, n, rels) n, c = recursive_scan(i, n, rels)
if c != 0 and dbg: if c != 0 and isinstance(debug, list):
debug.append(str(n)) debug.append(str(n))
total += c total += c
if general: if general:
for i in optimizations.general_optimizations: for j in optimizations.general_optimizations:
n, c = recursive_scan(i, n, None) n, c = recursive_scan(j, n, None)
if c != 0 and dbg: if c != 0 and isinstance(debug, list):
debug.append(str(n)) debug.append(str(n))
total += c total += c
if tostr: if tostr:

@ -24,7 +24,7 @@
# #
# Language definition here: # Language definition here:
# http://ltworf.github.io/relational/grammar.html # http://ltworf.github.io/relational/grammar.html
from typing import Optional, Union, List, Any, Dict from typing import Optional, Union, List, Any, Dict, Literal
from dataclasses import dataclass from dataclasses import dataclass
from relational import rtypes from relational import rtypes
@ -43,6 +43,7 @@ SELECTION = 'σ'
RENAME = 'ρ' RENAME = 'ρ'
ARROW = '' ARROW = ''
b_operators = (PRODUCT, DIFFERENCE, UNION, INTERSECTION, DIVISION, b_operators = (PRODUCT, DIFFERENCE, UNION, INTERSECTION, DIVISION,
JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL) # List of binary operators JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL) # List of binary operators
u_operators = (PROJECTION, SELECTION, RENAME) # List of unary operators u_operators = (PROJECTION, SELECTION, RENAME) # List of unary operators
@ -142,29 +143,28 @@ class Node:
if isinstance(self, Variable): #FIXME this is ugly if isinstance(self, Variable): #FIXME this is ugly
return list(rels[self.name].header) return list(rels[self.name].header)
elif isinstance(self, Binary) and self.name in (DIFFERENCE, UNION, INTERSECTION): elif isinstance(self, Binary):
if self.name in (DIFFERENCE, UNION, INTERSECTION):
return self.left.result_format(rels) return self.left.result_format(rels)
elif isinstance(self, Binary) and self.name == DIVISION: elif self.name == DIVISION:
return list(set(self.left.result_format(rels)) - set(self.right.result_format(rels))) return list(set(self.left.result_format(rels)) - set(self.right.result_format(rels)))
elif self.name == PROJECTION:
return self.get_projection_prop()
elif self.name == PRODUCT: elif self.name == PRODUCT:
return self.left.result_format(rels) + self.right.result_format(rels) return self.left.result_format(rels) + self.right.result_format(rels)
elif self.name in (JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL):
return list(set(self.left.result_format(rels)).union(set(self.right.result_format(rels))))
elif isinstance(self, Unary):
if self.name == PROJECTION:
return self.get_projection_prop()
elif self.name == SELECTION: elif self.name == SELECTION:
return self.child.result_format(rels) return self.child.result_format(rels)
elif self.name == RENAME: elif self.name == RENAME:
_vars = {} _vars = self.get_rename_prop()
for i in self.prop.split(','):
q = i.split(ARROW)
_vars[q[0].strip()] = q[1].strip()
_fields = self.child.result_format(rels) _fields = self.child.result_format(rels)
for i in range(len(_fields)): for i in range(len(_fields)):
if _fields[i] in _vars: if _fields[i] in _vars:
_fields[i] = _vars[_fields[i]] _fields[i] = _vars[_fields[i]]
return _fields return _fields
elif self.name in (JOIN, JOIN_LEFT, JOIN_RIGHT, JOIN_FULL):
return list(set(self.left.result_format(rels)).union(set(self.right.result_format(rels))))
raise ValueError('What kind of alien object is this?') raise ValueError('What kind of alien object is this?')
def __eq__(self, other): #FIXME def __eq__(self, other): #FIXME
@ -194,6 +194,7 @@ class Variable(Node):
@dataclass @dataclass
class Binary(Node): class Binary(Node):
name: str
left: Node left: Node
right: Node right: Node
@ -214,6 +215,7 @@ class Binary(Node):
@dataclass @dataclass
class Unary(Node): class Unary(Node):
name: str
prop: str prop: str
child: Node child: Node
@ -281,6 +283,7 @@ def parse_tokens(expression: List[Union[list, str]]) -> Node:
# The list contains only 1 string. Means it is the name of a relation # The list contains only 1 string. Means it is the name of a relation
if len(expression) == 1: if len(expression) == 1:
assert isinstance(expression[0], str)
if not rtypes.is_valid_relation_name(expression[0]): if not rtypes.is_valid_relation_name(expression[0]):
raise ParserException( raise ParserException(
f'{expression[0]!r} is not a valid relation name') f'{expression[0]!r} is not a valid relation name')
@ -306,7 +309,7 @@ def parse_tokens(expression: List[Union[list, str]]) -> Node:
if len(expression[i + 1:]) == 0: if len(expression[i + 1:]) == 0:
raise ParserException( raise ParserException(
f'Expected right operand for {expression[i]!r}') f'Expected right operand for {expression[i]!r}')
return Binary(expression[i], parse_tokens(expression[:i]), parse_tokens(expression[i + 1:])) return Binary(expression[i], parse_tokens(expression[:i]), parse_tokens(expression[i + 1:])) # type: ignore
'''Searches for unary operators, parsing from right to left''' '''Searches for unary operators, parsing from right to left'''
for i in range(len(expression) - 1, -1, -1): for i in range(len(expression) - 1, -1, -1):
if expression[i] in u_operators: # Unary operator if expression[i] in u_operators: # Unary operator
@ -315,9 +318,9 @@ def parse_tokens(expression: List[Union[list, str]]) -> Node:
f'Expected more tokens in {expression[i]!r}') f'Expected more tokens in {expression[i]!r}')
return Unary( return Unary(
expression[i], expression[i], # type: ignore
prop=expression[1 + i].strip(), prop=expression[1 + i].strip(), # type: ignore
child=parse_tokens(expression[2 + i]) child=parse_tokens(expression[2 + i]) # type: ignore
) )
raise ParserException(f'Parse error on {expression!r}') raise ParserException(f'Parse error on {expression!r}')

@ -22,12 +22,13 @@
import csv import csv
from itertools import chain, repeat from itertools import chain, repeat
from collections import deque from collections import deque
from typing import List, Union, Set from typing import *
from pathlib import Path
from relational.rtypes import * from relational.rtypes import *
class Relation (object): 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.
@ -51,13 +52,14 @@ class Relation (object):
An empty relation needs a header, and can be filled using the insert() An empty relation needs a header, and can be filled using the insert()
method. method.
''' '''
__hash__ = None # type: None def __hash__(self):
raise NotImplementedError()
def __init__(self, filename : str = '') -> None: def __init__(self, filename: Optional[Union[str, Path]] = None) -> None:
self._readonly = False self._readonly = False
self.content = set() # type: Set[tuple] self.content: Set[tuple] = set()
if len(filename) == 0: # Empty relation if filename is None: # Empty relation
self.header = Header([]) self.header = Header([])
return return
with open(filename) as fp: with open(filename) as fp:
@ -73,7 +75,7 @@ class Relation (object):
self._readonly = True self._readonly = True
copy._readonly = True copy._readonly = True
def _make_writable(self, copy_content : bool = True) -> None: def _make_writable(self, copy_content: bool = True) -> None:
'''If this relation is marked as readonly, this '''If this relation is marked as readonly, this
method will copy the content to make it writable too method will copy the content to make it writable too
@ -92,7 +94,7 @@ class Relation (object):
def __contains__(self, key): def __contains__(self, key):
return key in self.content return key in self.content
def save(self, filename: str) -> None: def save(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.
@ -169,7 +171,7 @@ class Relation (object):
newt.content.add(i + j) newt.content.add(i + j)
return newt return newt
def projection(self, * attributes) -> 'Relation': def projection(self, *attributes) -> 'Relation':
''' '''
Can be called in two different ways: Can be called in two different ways:
a.projection('field1','field2') a.projection('field1','field2')
@ -200,7 +202,7 @@ class Relation (object):
newt.content.add(tuple(row)) newt.content.add(tuple(row))
return newt return newt
def rename(self, params: 'Relation') -> 'Relation': def rename(self, params: Dict[str, str]) -> 'Relation':
''' '''
Takes a dictionary. Takes a dictionary.
@ -505,7 +507,7 @@ class Header(tuple):
def __repr__(self): def __repr__(self):
return "Header(%s)" % super(Header, self).__repr__() return "Header(%s)" % super(Header, self).__repr__()
def rename(self, params) -> 'Header': def rename(self, params: Dict[str, str]) -> 'Header':
'''Returns a new header, with renamed fields. '''Returns a new header, with renamed fields.
params is a dictionary of {old:new} names params is a dictionary of {old:new} names
@ -525,15 +527,15 @@ class Header(tuple):
'''Returns how many attributes this header has in common with a given one''' '''Returns how many attributes this header has in common with a given one'''
return len(set(self).intersection(set(other))) return len(set(self).intersection(set(other)))
def union(self, other) -> set: def union(self, other: 'Header') -> Set[str]:
'''Returns the union of the sets of attributes with another header.''' '''Returns the union of the sets of attributes with another header.'''
return set(self).union(set(other)) return set(self).union(set(other))
def intersection(self, other) -> set: def intersection(self, other: 'Header') -> Set[str]:
'''Returns the set of common attributes with another header.''' '''Returns the set of common attributes with another header.'''
return set(self).intersection(set(other)) return set(self).intersection(set(other))
def getAttributesId(self, param) -> List[int]: def getAttributesId(self, param: Iterable[str]) -> List[int]:
'''Returns a list with numeric index corresponding to field's name''' '''Returns a list with numeric index corresponding to field's name'''
try: try:
return [self.index(i) for i in param] return [self.index(i) for i in param]