commit
8f46eae9f5
@ -8,4 +8,5 @@ install:
|
|||||||
- pip install xtermcolor
|
- pip install xtermcolor
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
- make mypy
|
||||||
- make test
|
- make test
|
||||||
|
3
Makefile
3
Makefile
@ -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
|
||||||
ρ a➡b(R ∪ Q).
|
ρ a➡b(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(n²) operation like the union'''
|
Removing a O(n²) 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]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user