Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
cubicweb
RQL
Commits
f73670128b5f
Commit
71aceee6
authored
Jul 21, 2021
by
Nsukami Patrick
Browse files
feat: Remove comparison feature
See:
cubicweb#29
parent
f9186afc380d
Pipeline
#72505
failed with stages
in 1 minute and 32 seconds
Changes
3
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
rql/__init__.py
View file @
f7367012
...
...
@@ -209,15 +209,6 @@ class RQLHelper:
if
rewritten
and
select
.
solutions
:
select
.
clean_solutions
()
def
compare
(
self
,
rqlstring1
,
rqlstring2
):
"""Compare 2 RQL requests.
Return True if both requests would return the same results.
"""
from
rql.compare
import
compare_tree
return
compare_tree
(
self
.
parse
(
rqlstring1
),
self
.
parse
(
rqlstring2
))
def
copy_uid_node
(
select
,
node
,
vconsts
):
node
=
node
.
copy
(
select
)
...
...
rql/compare.py
deleted
100644 → 0
View file @
f9186afc
# copyright 2004-2021 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
#
# rql is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# rql is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with rql. If not, see <http://www.gnu.org/licenses/>.
"""Comparing syntax trees.
"""
__docformat__
=
"restructuredtext en"
from
rql.nodes
import
VariableRef
,
Variable
,
Function
,
Relation
,
Comparison
def
compare_tree
(
request1
,
request2
):
"""Compares 2 RQL requests.
:rtype: bool
:return: True if both requests would return the same results.
"""
return
make_canon_dict
(
request1
)
==
make_canon_dict
(
request2
)
def
make_canon_dict
(
rql_tree
,
verbose
=
0
):
"""Return a canonical representation of the request as a dictionnary."""
allvars
=
{}
canon
=
{
"all_variables"
:
allvars
,
"selected"
:
[],
"restriction"
:
{},
}
canon
=
RQLCanonizer
().
visit
(
rql_tree
,
canon
)
# forge variable name
for
var
,
name_parts
in
allvars
.
values
():
name_parts
.
sort
()
var
.
name
=
":"
.
join
(
name_parts
)
sort
(
canon
)
if
verbose
:
print
(
"CANON FOR"
,
rql_tree
)
from
pprint
import
pprint
pprint
(
canon
)
return
canon
def
sort
(
canon_dict
):
"""Remove the all_variables entry and sort other entries in place."""
del
canon_dict
[
"all_variables"
]
canon_dict
[
"selection"
].
sort
()
for
values
in
canon_dict
[
"restriction"
].
values
():
values
.
sort
()
class
SkipChildren
(
Exception
):
"""Signal indicating to ignore the current child."""
class
RQLCanonizer
:
"""Build a dictionnary which represents a RQL syntax tree."""
def
visit
(
self
,
node
,
canon
):
try
:
node
.
accept
(
self
,
canon
)
except
SkipChildren
:
return
canon
for
c
in
node
.
children
:
self
.
visit
(
c
,
canon
)
return
canon
def
visit_select
(
self
,
select
,
canon
):
allvars
=
canon
[
"all_variables"
]
for
var
in
select
.
defined_vars
.
values
():
allvars
[
var
]
=
(
Variable
(
var
.
name
),
[])
canon
[
"selection"
]
=
[]
selected
=
select
.
selected
for
i
in
range
(
len
(
selected
)):
node
=
selected
[
i
]
if
isinstance
(
node
,
VariableRef
):
node
=
node
.
variable
allvars
[
node
][
1
].
append
(
str
(
i
))
canon
[
"selection"
].
append
(
allvars
[
node
][
0
])
else
:
# Function
canon
[
"selection"
].
append
(
node
)
for
var
in
node
.
iget_nodes
(
VariableRef
):
var
.
parent
.
replace
(
var
,
allvars
[
var
.
variable
][
0
])
def
visit_group
(
self
,
group
,
canon
):
canon
[
"group"
]
=
group
def
visit_sort
(
self
,
sort
,
canon
):
canon
[
"sort"
]
=
sort
def
visit_sortterm
(
self
,
sortterm
,
canon
):
pass
def
visit_and
(
self
,
et
,
canon
):
pass
def
visit_or
(
self
,
ou
,
canon
):
canon_dict
=
{}
keys
=
[]
for
expr
in
ou
.
get_nodes
(
Relation
):
key
=
"%s%s"
%
(
expr
.
r_type
,
expr
.
_not
)
canon_dict
.
setdefault
(
key
,
[]).
append
(
expr
)
keys
.
append
(
key
)
keys
.
sort
()
r_type
=
":"
.
join
(
keys
)
r_list
=
canon
[
"restriction"
].
setdefault
(
r_type
,
[])
done
=
{}
for
key
in
keys
:
if
key
in
done
:
continue
done
[
key
]
=
None
for
expr
in
canon_dict
[
key
]:
self
.
manage_relation
(
expr
,
canon
,
r_list
)
raise
SkipChildren
()
def
manage_relation
(
self
,
relation
,
canon
,
r_list
):
lhs
,
rhs
=
relation
.
get_parts
()
# handle special case of the IN function
func
=
rhs
.
children
[
0
]
if
isinstance
(
func
,
Function
)
and
func
.
name
==
"IN"
:
if
not
relation
.
_not
:
base_key
=
"%s%s"
%
(
relation
.
r_type
,
relation
.
_not
)
if
not
canon
[
"restriction"
][
base_key
]:
del
canon
[
"restriction"
][
base_key
]
key
=
":"
.
join
([
base_key
]
*
len
(
func
.
children
))
r_list
=
canon
[
"restriction"
].
setdefault
(
key
,
[])
for
e
in
func
.
children
:
eq_expr
=
Relation
(
relation
.
r_type
,
relation
.
_not
)
eq_expr
.
append
(
lhs
)
eq_expr
.
append
(
Comparison
(
"="
,
e
))
self
.
manage_relation
(
eq_expr
,
canon
,
r_list
)
# restaure parent attribute to avoid problem later
e
.
parent
=
func
lhs
.
parent
=
relation
return
# build a canonical representation for this relation
lhs_expr_reminder
=
make_lhs_reminder
(
lhs
,
canon
)
rhs_expr_reminder
=
make_rhs_reminder
(
rhs
,
canon
)
reminder
=
(
lhs_expr_reminder
,
rhs_expr_reminder
)
# avoid duplicate
if
reminder
in
r_list
:
return
r_list
.
append
(
reminder
)
# make a string which represents this relation (we'll use it later
# to build variables' name)
expr_reminder
=
relation
.
r_type
lhs_vars
=
lhs
.
get_nodes
(
VariableRef
)
if
not
lhs_vars
:
expr_reminder
=
"%s_%s"
%
(
lhs
,
expr_reminder
)
rhs_vars
=
rhs
.
get_nodes
(
VariableRef
)
if
not
rhs_vars
:
expr_reminder
=
"%s_%s"
%
(
expr_reminder
,
rhs
)
for
var
in
lhs_vars
+
rhs_vars
:
var
=
var
.
variable
canon
[
"all_variables"
][
var
][
1
].
append
(
expr_reminder
)
def
visit_relation
(
self
,
relation
,
canon
):
key
=
"%s%s"
%
(
relation
.
r_type
,
relation
.
_not
)
r_list
=
canon
[
"restriction"
].
setdefault
(
key
,
[])
self
.
manage_relation
(
relation
,
canon
,
r_list
)
def
visit_comparison
(
self
,
comparison
,
canon
):
"""do nothing for this node type"""
def
visit_mathexpression
(
self
,
mathexpression
,
canon
):
"""do nothing for this node type"""
def
visit_function
(
self
,
function
,
canon
):
"""do nothing for this node type"""
def
visit_variableref
(
self
,
varref
,
canon
):
varref
.
parent
.
replace
(
varref
,
canon
[
"all_variables"
][
varref
.
variable
][
0
])
def
visit_constant
(
self
,
constante
,
canon
):
"""do nothing for this node type"""
def
visit_union
(
self
,
*
args
):
raise
NotImplementedError
(
"union comparison not implemented"
)
def
make_lhs_reminder
(
lhs
,
canon
):
"""Return a reminder for a relation's left hand side
(i.e a VariableRef object).
"""
try
:
lhs
=
canon
[
"all_variables"
][
lhs
.
variable
][
0
]
except
(
KeyError
,
IndexError
):
pass
return
(
"="
,
lhs
)
def
make_rhs_reminder
(
rhs
,
canon
):
"""Return a reminder for a relation's right hand side
(i.e a Comparison object).
"""
child
=
rhs
.
children
[
0
]
try
:
child
=
canon
[
"all_variables"
][
child
.
variable
][
0
]
except
AttributeError
:
pass
return
(
rhs
.
operator
,
child
)
test/unittest_compare.py
deleted
100644 → 0
View file @
f9186afc
# copyright 2004-2021 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of rql.
#
# rql is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# rql is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with rql. If not, see <http://www.gnu.org/licenses/>.
import
unittest
from
logilab.common.testlib
import
TestCase
,
SkipTest
from
rql
import
RQLHelper
from
unittest_analyze
import
RelationSchema
,
EntitySchema
,
DummySchema
as
BaseSchema
class
DummySchema
(
BaseSchema
):
def
__init__
(
self
):
super
(
DummySchema
,
self
).
__init__
()
for
etype
in
[
"Note"
,
]:
self
.
_types
[
etype
]
=
EntitySchema
(
etype
)
relations
=
[
(
"a_faire_par"
,
((
"Note"
,
(
"Person"
,)),)),
(
"creation_date"
,
((
"Note"
,
(
"Date"
,)),)),
]
for
rel_name
,
rel_ent
in
relations
:
self
.
_relations
[
rel_name
]
=
RelationSchema
(
rel_ent
)
self
.
_relations
[
"nom"
]
=
self
.
_relations
[
"name"
]
self
.
_relations
[
"prenom"
]
=
self
.
_relations
[
"firstname"
]
class
RQLCompareClassTest
(
TestCase
):
"""Compare RQL strings"""
@
classmethod
def
setUpClass
(
cls
):
raise
SkipTest
(
"broken"
)
def
setUp
(
self
):
self
.
h
=
RQLHelper
(
DummySchema
(),
None
)
def
_compareEquivalent
(
self
,
r1
,
r2
):
"""fails if the RQL strings r1 and r2 are equivalent"""
self
.
skipTest
(
"broken"
)
self
.
failUnless
(
self
.
h
.
compare
(
r1
,
r2
),
"r1: %s
\n
r2: %s"
%
(
r1
,
r2
))
def
_compareNotEquivalent
(
self
,
r1
,
r2
):
"""fails if the RQL strings r1 and r2 are not equivalent"""
self
.
failIf
(
self
.
h
.
compare
(
r1
,
r2
),
"r1: %s
\n
r2: %s"
%
(
r1
,
r2
))
# equivalent queries ##################################################
def
test_same_request_simple
(
self
):
r
=
"Any X WHERE X is Note ;"
self
.
_compareEquivalent
(
r
,
r
)
def
test_same_request_diff_names
(
self
):
r1
=
"Any X ;"
r2
=
"Any Y ;"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_diff_names_simple
(
self
):
r1
=
"Any X WHERE X is Note ;"
r2
=
"Any Y WHERE Y is Note ;"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_any
(
self
):
r1
=
"Any X WHERE X is Note ;"
r2
=
"Note X ;"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_any_diff_names
(
self
):
r1
=
"Any X WHERE X is Note ;"
r2
=
"Note Y ;"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_complex
(
self
):
r
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
self
.
_compareEquivalent
(
r
,
r
)
def
test_same_request_comma_and
(
self
):
r1
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
r2
=
(
"Any N, N2 WHERE N is Note AND N2 is Note AND N a_faire_par P1 "
"AND P1 nom 'jphc' AND N2 a_faire_par P2 AND P2 nom 'ocy' ;"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_diff_names_complex
(
self
):
r1
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
r2
=
(
"Any Y, X WHERE X is Note, Y is Note, X a_faire_par A1, "
"A1 nom 'ocy', Y a_faire_par A2, A2 nom 'jphc' ;"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_diff_order
(
self
):
r1
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
r2
=
(
"Any N, N2 WHERE N2 is Note, N is Note, N a_faire_par P1, "
"N2 a_faire_par P2, P2 nom 'ocy', P1 nom 'jphc' ;"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_diff_order_diff_names
(
self
):
r1
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
r2
=
(
"Any Y, X WHERE X is Note, X a_faire_par P1, P1 nom 'ocy', "
"Y is Note, Y a_faire_par P2, P2 nom 'jphc' ;"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_with_comparison
(
self
):
r1
=
"Note N WHERE N a_faire_par P, P nom 'jphc', K creation_date > today-10;"
r2
=
"Note K WHERE K a_faire_par Y, K creation_date > today-10, Y nom 'jphc';"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_in_or
(
self
):
r1
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc' or P nom 'ludal';"
)
r2
=
"Note K WHERE K a_faire_par Y, K creation_date > today-10, Y nom in ('jphc', 'ludal');"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_reverse_or
(
self
):
r1
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc' or P nom 'ludal';"
)
r2
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'ludal' or P nom 'jphc';"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_reverse_or2
(
self
):
r1
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P prenom 'jphc' or P nom 'ludal';"
)
r2
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'ludal' or P prenom 'jphc';"
)
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_duplicate_expr
(
self
):
r1
=
"Note N WHERE N creation_date D, N creation_date D;"
r2
=
"Note N WHERE N creation_date D;"
self
.
_compareEquivalent
(
r1
,
r2
)
def
test_same_request_not_in_or
(
self
):
r1
=
(
"Note K WHERE K a_faire_par Y, K creation_date > today-10, "
"not Y nom in ('jphc', 'ludal');"
)
r2
=
(
"Note K WHERE K a_faire_par Y, K creation_date > today-10, "
"not Y nom 'jphc' and not Y nom 'ludal';"
)
self
.
_compareEquivalent
(
r1
,
r2
)
# non equivalent queries ##################################################
def
test_diff_request
(
self
):
r1
=
(
"Any N, N2 WHERE N is Note, N2 is Note, N a_faire_par P1, "
"P1 nom 'jphc', N2 a_faire_par P2, P2 nom 'ocy' ;"
)
r2
=
"Any X WHERE X is Note ;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_and_or
(
self
):
r1
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc' or P nom 'ludal';"
)
r2
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc', P nom 'ludal';"
)
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_and_or2
(
self
):
r1
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc' or P prenom 'ludal';"
)
r2
=
(
"Note N WHERE N creation_date > today-10, N a_faire_par P, "
"P nom 'jphc', P prenom 'ludal';"
)
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_non_selected_var
(
self
):
r1
=
"Any X, D WHERE X is Note, X creation_date D ;"
r2
=
"Any X WHERE X is Note, X creation_date D ;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_aggregat
(
self
):
r1
=
"Any X, D WHERE X is Note, X creation_date D ;"
r2
=
"Any X, MAX(D) WHERE X is Note, X creation_date D ;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_group
(
self
):
r1
=
"Any X GROUPBY X WHERE X is Note;"
r2
=
"Any X WHERE X is Note;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_sort
(
self
):
r1
=
"Any X ORDERBY X WHERE X is Note;"
r2
=
"Any X WHERE X is Note;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_not
(
self
):
r1
=
"Any X WHERE NOT X is Note ;"
r2
=
"Any X WHERE X is Note;"
self
.
_compareNotEquivalent
(
r1
,
r2
)
def
test_diff_request_not_in_or
(
self
):
r1
=
(
"Note K WHERE K a_faire_par Y, K creation_date > today-10, "
"not Y nom in ('jphc', 'ludal');"
)
r2
=
(
"Note K WHERE K a_faire_par Y, K creation_date > today-10, "
"not Y nom 'jphc' or not Y nom 'ludal';"
)
self
.
_compareNotEquivalent
(
r1
,
r2
)
if
__name__
==
"__main__"
:
unittest
.
main
()
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment