Exemplo n.º 1
0
class testEdgeByIndexScanFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)

    def setUp(self):
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("social", redis_con)
        self.populate_graph(redis_graph)
        self.build_indices()

    def tearDown(self):
        self.env.cmd('flushall')

    def populate_graph(self, redis_graph):
        nodes = {}

        # Create entities
        node_id = 0
        for p in people:
            node = Node(label="person",
                        properties={
                            "name": p,
                            "created_at": node_id
                        })
            redis_graph.add_node(node)
            nodes[p] = node
            node_id = node_id + 1

        # Fully connected graph
        edge_id = 0
        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src],
                                "knows",
                                nodes[dest],
                                properties={"created_at": edge_id * 2})
                    redis_graph.add_edge(edge)
                    edge = Edge(nodes[src],
                                "friend",
                                nodes[dest],
                                properties={
                                    "created_at": edge_id * 2 + 1,
                                    "updated_at": edge_id * 3
                                })
                    redis_graph.add_edge(edge)
                    edge_id = edge_id + 1

        redis_graph.commit()

    def build_indices(self):
        global redis_graph
        redis_graph.query("CREATE INDEX ON :person(age)")
        redis_graph.query(
            "CREATE INDEX FOR ()-[f:friend]-() ON (f.created_at)")
        redis_graph.query("CREATE INDEX FOR ()-[f:knows]-() ON (f.created_at)")

    # Validate that Cartesian products using index and label scans succeed
    def test01_cartesian_product_mixed_scans(self):
        query = "MATCH ()-[f:friend]->(), ()-[k:knows]->() WHERE f.created_at >= 0 RETURN f.created_at, k.created_at ORDER BY f.created_at, k.created_at"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Conditional Traverse', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH ()-[f:friend]->(), ()-[k:knows]->() RETURN f.created_at, k.created_at ORDER BY f.created_at, k.created_at"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Edge By Index Scan', plan)
        self.env.assertIn('Conditional Traverse', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set,
                              unindexed_result.result_set)

    # Validate that Cartesian products using just index scans succeed
    def test02_cartesian_product_index_scans_only(self):
        query = "MATCH ()-[f:friend]->(), ()-[k:knows]->() WHERE f.created_at >= 0 AND k.created_at >= 0 RETURN f.created_at, k.created_at ORDER BY f.created_at, k.created_at"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Edge By Index Scan'), 2)
        self.env.assertNotIn('Conditional Traverse', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH ()-[f:friend]->(), ()-[k:knows]->() RETURN f.created_at, k.created_at ORDER BY f.created_at, k.created_at"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Edge By Index Scan', plan)
        self.env.assertIn('Conditional Traverse', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set,
                              unindexed_result.result_set)

    # Validate that the appropriate bounds are respected when a Cartesian product uses the same index in two streams
    def test03_cartesian_product_reused_index(self):
        redis_graph.query(
            "CREATE INDEX FOR ()-[f:friend]-() ON (f.updated_at)")
        query = "MATCH ()-[a:friend]->(), ()-[b:friend]->() WHERE a.created_at >= 80 AND b.updated_at >= 120 RETURN a.created_at, b.updated_at"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Edge By Index Scan'), 2)
        self.env.assertNotIn('Conditional Traverse', plan)

        expected_result = [[81, 120], [83, 120], [81, 123], [83, 123]]
        result = redis_graph.query(query)

        self.env.assertEquals(result.result_set, expected_result)

    # Validate index utilization when filtering on a numeric field with the `IN` keyword.
    def test04_test_in_operator_numerics(self):
        # Validate the transformation of IN to multiple OR expressions.
        query = "MATCH ()-[f:friend]-() WHERE f.created_at IN [1,2,3] RETURN f"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        # Validate that nested arrays are not scanned in index.
        query = "MATCH ()-[f:friend]-() WHERE f.created_at IN [[1,2],3] RETURN f"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Edge By Index Scan', plan)
        self.env.assertIn('Conditional Traverse', plan)

        # Validate the transformation of IN to multiple OR, over a range.
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at IN range(0,30) RETURN DISTINCT n.name ORDER BY n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        expected_result = [['Ailon'], ['Alon'], ['Roi']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of IN to empty index iterator.
        query = "MATCH ()-[f:friend]-() WHERE f.created_at IN [] RETURN f.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of IN OR IN to empty index iterators.
        query = "MATCH ()-[f:friend]->() WHERE f.created_at IN [] OR f.created_at IN [] RETURN f.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at IN [0, 1, 2] OR f.created_at IN [14, 15, 16] RETURN n.name ORDER BY n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        expected_result = [['Alon'], ['Roi']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at IN [0, 1, 2] OR f.created_at IN [14, 15, 16] OR f.created_at IN [] RETURN n.name ORDER BY n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)

        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

    def test07_index_scan_and_id(self):
        query = """MATCH (n)-[f:friend]->() WHERE id(f)>=10 AND f.created_at<15 RETURN n.name ORDER BY n.name"""
        plan = redis_graph.execution_plan(query)
        query_result = redis_graph.query(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)

        self.env.assertEqual(2, len(query_result.result_set))
        expected_result = [['Alon'], ['Roi']]
        self.env.assertEquals(expected_result, query_result.result_set)

    # Validate placement of index scans and filter ops when not all filters can be replaced.
    def test08_index_scan_multiple_filters(self):
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at = 31 AND NOT EXISTS(f.fakeprop) RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertNotIn('Conditional Traverse', plan)
        self.env.assertIn('Filter', plan)

        query_result = redis_graph.query(query)
        expected_result = ["Ailon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test09_index_scan_with_params(self):
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at = $time RETURN n.name"
        params = {'time': 31}
        plan = redis_graph.execution_plan(query, params=params)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(query, params=params)
        expected_result = ["Ailon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test10_index_scan_with_param_array(self):
        query = "MATCH (n)-[f:friend]->() WHERE f.created_at in $times RETURN n.name"
        params = {'times': [31]}
        plan = redis_graph.execution_plan(query, params=params)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(query, params=params)
        expected_result = ["Ailon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test11_single_index_multiple_scans(self):
        query = "MATCH (p1:person {name: 'Roi'}), (p2:person {name: 'Alon'}) MERGE (p1)-[:friend {created_at: 100}]->(p2) MERGE (p1)-[:friend {created_at: 101}]->(p2)"
        plan = redis_graph.execution_plan(query)
        # Two index scans should be performed.
        self.env.assertEqual(plan.count("Edge By Index Scan"), 2)

        query_result = redis_graph.query(query)
        # Two new nodes should be created.
        self.env.assertEquals(query_result.relationships_created, 2)

    def test16_runtime_index_utilization(self):
        # find all person nodes with age in the range 33-37
        # current age (x) should be resolved at runtime
        # index query should be constructed for each age value
        q = """UNWIND range(33, 37) AS x
        MATCH (n)-[f:friend {created_at: x}]->()
        RETURN n.name
        ORDER BY n.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [['Ailon'], ['Ailon'], ['Boaz']]
        self.env.assertEquals(query_result.result_set, expected_result)

        # similar to the query above, only this time the filter is specified
        # by an OR condition
        q = """WITH 33 AS min, 37 AS max 
        MATCH (n)-[f:friend]->()
        WHERE f.created_at = min OR f.created_at = max
        RETURN n.name
        ORDER BY n.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [['Ailon'], ['Boaz']]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes with age equals 33 'x'
        # 'x' value is known only at runtime
        q = """WITH 33 AS x
        MATCH (n)-[f:friend {created_at: x}]->()
        RETURN n.name
        ORDER BY n.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Ailon"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes with age equals x + 1
        # the expression x+1 is evaluated to the constant 33 only at runtime
        # expecting index query to be constructed at runtime
        q = """WITH 32 AS x
        MATCH (n)-[f:friend]->()
        WHERE f.created_at = (x + 1)
        RETURN n.name
        ORDER BY n.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Ailon"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # same idea as previous query only we've switched the position of the
        # operands, queried entity (p.age) is now on the right hand side of the
        # filter, expecting the same behavior
        q = """WITH 32 AS x
        MATCH (n)-[f:friend]->()
        WHERE (x + 1) = f.created_at
        RETURN n.name
        ORDER BY n.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Ailon"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # make sure all node scan not removed because we need to filter
        q = """MATCH (a)-[e:friend]->()
        WHERE a.created_at > 5 AND e.created_at > a.created_at
        RETURN DISTINCT a.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Filter', plan)
        self.env.assertIn('All Node Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Ori"]]
        self.env.assertEquals(query_result.result_set, expected_result)

    def test_18_index_scan_and_label_filter(self):
        query = "MATCH (n)-[f:friend]->(m) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertNotIn('All Node Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)-[f:friend]->(m) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)-[f:friend]->(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Roi'})-[f:friend]->(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Alon'})-[f:friend]->(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        self.env.assertEquals(query_result.result_set, [])

        query = "MATCH (n)<-[f:friend]-(m) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertNotIn('All Node Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)<-[f:friend]-(m) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)<-[f:friend]-(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Roi'})<-[f:friend]-(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        self.env.assertEquals(query_result.result_set, [])

        query = "MATCH (n:person {name: 'Alon'})<-[f:friend]-(m:person) WHERE f.created_at = 1 RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test19_index_scan_and_with(self):
        query = "MATCH (n)-[f:friend]->(m) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertNotIn('All Node Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)-[f:friend]->(m) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)-[f:friend]->(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Roi'})-[f:friend]->(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Roi"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Alon'})-[f:friend]->(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        self.env.assertEquals(query_result.result_set, [])

        query = "MATCH (n)<-[f:friend]-(m) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertNotIn('All Node Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)<-[f:friend]-(m) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertNotIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person)<-[f:friend]-(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

        query = "MATCH (n:person {name: 'Roi'})<-[f:friend]-(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        self.env.assertEquals(query_result.result_set, [])

        query = "MATCH (n:person {name: 'Alon'})<-[f:friend]-(m:person) WHERE f.created_at = 1 WITH n RETURN n.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Edge By Index Scan', plan)
        self.env.assertIn('Node By Label Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Alon"]
        self.env.assertEquals(query_result.result_set[0], expected_result)
Exemplo n.º 2
0
class testComprehensionFunctions(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        graph_id = "list_comprehension"
        redis_con = self.env.getConnection()
        redis_graph = Graph(graph_id, redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph

        # Construct a graph with the form:
        # (v1)-[e1]->(v2)-[e2]->(v3)
        node_props = ['v1', 'v2', 'v3']

        nodes = []
        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"val": v})
            nodes.append(node)
            redis_graph.add_node(node)

        edge = Edge(nodes[0],
                    "E",
                    nodes[1],
                    properties={"edge_val": ['v1', 'v2']})
        redis_graph.add_edge(edge)

        edge = Edge(nodes[1],
                    "E",
                    nodes[2],
                    properties={"edge_val": ['v2', 'v3']})
        redis_graph.add_edge(edge)

        redis_graph.commit()

    # Test list comprehension queries with scalar inputs and a single result row
    def test01_list_comprehension_single_return(self):
        expected_result = [[[2, 6]]]

        # Test logically identical queries that generate the same input array with different methods.
        query = """WITH [1,2,3] AS arr RETURN [elem IN arr WHERE elem % 2 = 1 | elem * 2]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN [elem IN [1,2,3] WHERE elem % 2 = 1 | elem * 2]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN [elem IN range(1,3) WHERE elem % 2 = 1 | elem * 2]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test02_list_comprehension_no_filter_no_map(self):
        expected_result = [[[1, 2, 3]]]
        query = """WITH [1,2,3] AS arr RETURN [elem IN arr]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)
        query = """RETURN [elem IN [1,2,3]]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test03_list_comprehension_map_no_filter(self):
        query = """WITH [1,2,3] AS arr RETURN [elem IN arr | elem * 2]"""
        actual_result = redis_graph.query(query)
        expected_result = [[[2, 4, 6]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test04_list_comprehension_filter_no_map(self):
        query = """WITH [1,2,3] AS arr RETURN [elem IN arr WHERE elem % 2 = 1]"""
        actual_result = redis_graph.query(query)
        expected_result = [[[1, 3]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test05_list_comprehension_on_allocated_values(self):
        query = """WITH [toUpper('str1'), toUpper('str2'), toUpper('str3')] AS arr RETURN [elem IN arr]"""
        actual_result = redis_graph.query(query)
        expected_result = [[['STR1', 'STR2', 'STR3']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """WITH [toUpper('str1'), toUpper('str2'), toUpper('str3')] AS arr RETURN [elem IN arr WHERE toLower(elem) = 'str2']"""
        actual_result = redis_graph.query(query)
        expected_result = [[['STR2']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """WITH [toUpper('str1'), toUpper('str2'), toUpper('str3')] AS arr RETURN [elem IN arr WHERE toLower(elem) = 'str2' | elem + 'low']"""
        actual_result = redis_graph.query(query)
        expected_result = [[['STR2low']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test06_list_comprehension_on_graph_entities(self):
        query = """MATCH p=()-[*]->() WITH nodes(p) AS nodes RETURN [elem IN nodes]"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 3)

        query = """MATCH p=()-[*]->() WITH nodes(p) AS nodes WITH [elem IN nodes | elem.val] AS vals RETURN vals ORDER BY vals"""
        actual_result = redis_graph.query(query)
        expected_result = [[['v1', 'v2']], [['v1', 'v2', 'v3']], [['v2',
                                                                   'v3']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """MATCH p=()-[*]->() WITH nodes(p) AS nodes RETURN [elem IN nodes WHERE elem.val = 'v2' | elem.val]"""
        actual_result = redis_graph.query(query)
        expected_result = [[['v2']], [['v2']], [['v2']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """MATCH p=()-[*]->() WITH nodes(p) AS nodes RETURN [elem IN nodes WHERE elem.val = 'v2' | elem.val + 'a']"""
        actual_result = redis_graph.query(query)
        expected_result = [[['v2a']], [['v2a']], [['v2a']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test07_list_comprehension_in_where_predicate(self):
        # List comprehension with predicate in WHERE predicate on MATCH clause - evaluates to true
        query = """MATCH (n) WHERE n.val IN [x in ['v1', 'v3']] RETURN n.val ORDER BY n.val"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1'], ['v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # List comprehension with predicate in WHERE predicate - evaluates to true
        query = """WITH 1 AS a WHERE a IN [x in [1, 2]] RETURN a"""
        actual_result = redis_graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # List comprehension with predicate in WHERE predicate - evaluates to false
        query = """WITH 1 AS a WHERE a IN [x in [2,3]] RETURN a"""
        actual_result = redis_graph.query(query)
        expected_result = []
        self.env.assertEquals(actual_result.result_set, expected_result)

        # List comprehension with predicate and eval in WHERE predicate - evaluates to false
        query = """WITH 1 AS a WHERE [i in [2,3] WHERE i > 5] RETURN a"""
        actual_result = redis_graph.query(query)
        expected_result = []
        self.env.assertEquals(actual_result.result_set, expected_result)

        # List comprehension without predicate or eval in WHERE predicate - evaluates to true
        query = """WITH 1 AS a WHERE [i in [2,3]] RETURN a"""
        actual_result = redis_graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test08_list_comprehension_on_property_array(self):
        query = """MATCH (n)-[e]->() WITH n, e ORDER BY n.val RETURN [elem IN e.edge_val WHERE elem = n.val]"""
        actual_result = redis_graph.query(query)
        expected_result = [[['v1']], [['v2']]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test09_nested_list_comprehension(self):
        query = """RETURN [elem IN [nested_val IN range(0, 6) WHERE nested_val % 2 = 0] WHERE elem * 2 >= 4 | elem * 2]"""
        actual_result = redis_graph.query(query)
        expected_result = [[[4, 8, 12]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test10_any_all_comprehension_acceptance(self):
        # Reject ANY and ALL comprehensions that don't include a WHERE predicate.
        try:
            redis_graph.query("RETURN any(x IN [1,2])")
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn("requires a WHERE predicate", e.message)

        try:
            redis_graph.query("RETURN all(x IN [1,2])")
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn("requires a WHERE predicate", e.message)

    def test11_any_all_truth_table(self):
        # Test inputs and predicates where ANY and ALL are both false.
        query = """RETURN any(x IN [0,1] WHERE x = 2)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[False]])

        query = """RETURN all(x IN [0,1] WHERE x = 2)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[False]])

        # Test inputs and predicates where ANY is true and ALL is false.
        query = """RETURN any(x IN [0,1] WHERE x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[True]])

        query = """RETURN all(x IN [0,1] WHERE x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[False]])

        # Test inputs and predicates where ANY and ALL are both true.
        query = """RETURN any(x IN [0,1] WHERE x = 0 OR x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[True]])

        query = """RETURN all(x IN [0,1] WHERE x = 0 OR x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[True]])

        # Test inputs and predicates where ANY and ALL are both NULL.
        query = """RETURN any(x IN NULL WHERE x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[None]])

        query = """RETURN all(x IN NULL WHERE x = 1)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[None]])

    def test12_any_all_on_property_arrays(self):
        # The first array evaluates to ['v1', 'v2'] and the second evaluates to ['v2', 'v3']
        query = """MATCH ()-[e]->() WITH e ORDER BY e.edge_val RETURN ANY(elem IN e.edge_val WHERE elem = 'v2' OR elem = 'v3')"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[True], [True]])

        query = """MATCH ()-[e]->() WITH e ORDER BY e.edge_val RETURN ALL(elem IN e.edge_val WHERE elem = 'v2' OR elem = 'v3')"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, [[False], [True]])

    def test13_any_all_path_filtering(self):
        # Use ANY and ALL to introspect on named variable-length paths.
        # All paths should be returned using both ANY and ALL filters.
        expected_result = [['v1'], ['v1'], ['v2']]
        query = """MATCH p=()-[*]->() WHERE any(node IN nodes(p) WHERE node.val STARTS WITH 'v') WITH head(nodes(p)) AS n RETURN n.val ORDER BY n.val"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """MATCH p=()-[*]->() WHERE all(node IN nodes(p) WHERE node.val STARTS WITH 'v') WITH head(nodes(p)) AS n RETURN n.val ORDER BY n.val"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Run a query in which 2 paths pass an ANY filter and 1 path passes an ALL filter.
        query = """MATCH p=()-[*0..1]->() WHERE any(node IN nodes(p) WHERE node.val = 'v1') RETURN length(p) ORDER BY length(p)"""
        actual_result = redis_graph.query(query)
        expected_result = [[0], [1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """MATCH p=()-[*0..1]->() WHERE all(node IN nodes(p) WHERE node.val = 'v1') RETURN length(p) ORDER BY length(p)"""
        actual_result = redis_graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 3
0
class testGraphMergeFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        global graph_2
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        graph_2 = Graph("H", redis_con)

    # Create a single node without any labels or properties.
    def test01_single_node_with_label(self):
        global redis_graph
        query = """MERGE (robert:Critic)"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 0)

    # Retry to create an existing entity.
    def test02_existing_single_node_with_label(self):
        global redis_graph
        query = """MERGE (robert:Critic)"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    # Create a single node with two properties and no labels.
    def test03_single_node_with_properties(self):
        global redis_graph
        query = """MERGE (charlie { name: 'Charlie Sheen', age: 10 })"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 2)

    # Retry to create an existing entity.
    def test04_existing_single_node_with_properties(self):
        global redis_graph
        query = """MERGE (charlie { name: 'Charlie Sheen', age: 10 })"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    # Create a single node with both label and property.
    def test05_single_node_both_label_and_property(self):
        global redis_graph
        query = """MERGE (michael:Person { name: 'Michael Douglas' })"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 1)

    # Retry to create an existing entity.
    def test06_existing_single_node_both_label_and_property(self):
        global redis_graph
        query = """MERGE (michael:Person { name: 'Michael Douglas' })"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    # Create a single edge and additional two nodes.
    def test07_merge_on_relationship(self):
        global redis_graph
        query = """MERGE (charlie:ACTOR)-[r:ACTED_IN]->(wallStreet:MOVIE)"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 2)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.relationships_created, 1)

    # Retry to create a single edge and additional two nodes.
    def test08_existing_merge_on_relationship(self):
        global redis_graph
        query = """MERGE (charlie:ACTOR)-[r:ACTED_IN]->(wallStreet:MOVIE)"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.relationships_created, 0)

    # Update existing entity
    def test09_update_existing_node(self):
        global redis_graph
        query = """MERGE (charlie { name: 'Charlie Sheen' }) SET charlie.age = 11, charlie.lastname='Sheen' """
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 2)
        self.env.assertEquals(result.relationships_created, 0)

        query = """MATCH (charlie { name: 'Charlie Sheen' }) RETURN charlie.age, charlie.name, charlie.lastname"""
        actual_result = redis_graph.query(query)
        expected_result = [[11, 'Charlie Sheen', 'Sheen']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Update new entity
    def test10_update_new_node(self):
        global redis_graph
        query = """MERGE (tamara:ACTOR { name: 'tamara tunie' }) SET tamara.age = 59, tamara.name = 'Tamara Tunie' """
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.relationships_created, 0)

        query = """MATCH (tamara:ACTOR { name: 'Tamara Tunie' }) RETURN tamara.name, tamara.age"""
        actual_result = redis_graph.query(query)
        expected_result = [['Tamara Tunie', 59]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Create a single edge and additional two nodes.
    def test11_update_new_relationship(self):
        global redis_graph
        query = """MERGE (franklin:ACTOR { name: 'Franklin Cover' })-[r:ACTED_IN {rate:5.7}]->(almostHeroes:MOVIE) SET r.date=1998, r.rate=5.8"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 4)
        self.env.assertEquals(result.relationships_created, 1)

    # Update existing relation
    def test12_update_existing_edge(self):
        global redis_graph
        query = """MERGE (franklin:ACTOR { name: 'Franklin Cover' })-[r:ACTED_IN {rate:5.8, date:1998}]->(almostHeroes:MOVIE) SET r.date=1998, r.rate=5.9"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 2)
        self.env.assertEquals(result.relationships_created, 0)

        query = """MATCH (franklin:ACTOR { name: 'Franklin Cover' })-[r:ACTED_IN {rate:5.9, date:1998}]->(almostHeroes:MOVIE) RETURN franklin.name, franklin.age, r.rate, r.date"""
        actual_result = redis_graph.query(query)
        expected_result = [['Franklin Cover', None, 5.9, 1998]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Update multiple nodes
    def test13_update_multiple_nodes(self):
        global redis_graph
        query = """CREATE (:person {age:31}),(:person {age:31}),(:person {age:31}),(:person {age:31})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 4)
        self.env.assertEquals(result.properties_set, 4)

        query = """MERGE (p:person {age:31}) SET p.newprop=100"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 4)

        query = """MATCH (p:person) RETURN p.age, p.newprop"""
        actual_result = redis_graph.query(query)
        expected_result = [[31, 100], [31, 100], [31, 100], [31, 100]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Update multiple nodes
    def test14_merge_unbounded_pattern(self):
        global redis_graph
        query = """MERGE (p:person {age:31})-[:owns]->(d:dog {name:'max'})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 2)
        self.env.assertEquals(result.relationships_created, 1)

        # Although person with age 31 and dog with the name max exists,
        # specified pattern doesn't exists, as a result the entire pattern
        # will be created, if we were to support MATCH MERGE 'p' and 'd'
        # would probably be defined in the MATCH clause, as a result they're
        # bounded and won't be duplicated.
        query = """MERGE (p:person {age:31})-[:owns]->(d:dog {name:'max'})-[:eats]->(f:food {name:'Royal Canin'})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 3)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.relationships_created, 2)

    # Add node that matches pre-existing index
    def test15_merge_indexed_entity(self):
        global redis_graph
        # Create index
        query = """CREATE INDEX ON :person(age)"""
        redis_graph.query(query)

        count_query = """MATCH (p:person) WHERE p.age > 0 RETURN COUNT(p)"""
        result = redis_graph.query(count_query)
        original_count = result.result_set[0][0]

        # Add one new person
        merge_query = """MERGE (p:person {age:40})"""
        result = redis_graph.query(merge_query)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 1)
        # Verify that one indexed node has been added
        result = redis_graph.query(count_query)
        updated_count = result.result_set[0][0]
        self.env.assertEquals(updated_count, original_count + 1)

        # Perform another merge that does not create an entity
        result = redis_graph.query(merge_query)
        self.env.assertEquals(result.nodes_created, 0)

        # Verify that indexed node count is unchanged
        result = redis_graph.query(count_query)
        updated_count = result.result_set[0][0]
        self.env.assertEquals(updated_count, original_count + 1)

    # Update nodes based on non-constant inlined properties
    def test16_merge_dynamic_properties(self):
        global redis_graph
        # Create and verify a new node
        query = """MERGE (q:dyn {name: toUpper('abcde')}) RETURN q.name"""
        expected = [['ABCDE']]

        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 1)

        self.env.assertEquals(result.result_set, expected)

        # Repeat the query and verify that no changes were introduced
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)

        # Verify that MATCH...MERGE on the same entity does not introduce changes
        query = """MATCH (q {name: 'ABCDE'}) MERGE (r {name: q.name}) RETURN r.name"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

    def test17_complex_merge_queries(self):
        # Beginning with an empty graph
        global graph_2
        # Create a new pattern
        query = """MERGE (a:Person {name: 'a'}) MERGE (b:Person {name: 'b'}) MERGE (a)-[e:FRIEND {val: 1}]->(b) RETURN a.name, e.val, b.name"""
        result = graph_2.query(query)
        expected = [['a', 1, 'b']]

        # Verify the results
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 1)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.result_set, expected)

        # Repeat the query and verify that no changes were introduced
        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

        # Verify that these entities are accessed properly with MATCH...MERGE queries
        query = """MATCH (a:Person {name: 'a'}), (b:Person {name: 'b'}) MERGE (a)-[e:FRIEND {val: 1}]->(b) RETURN a.name, e.val, b.name"""
        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

        # Verify that we can bind entities properly in variable-length traversals
        query = """MATCH (a)-[*]->(b) MERGE (a)-[e:FRIEND {val: 1}]->(b) RETURN a.name, e.val, b.name"""
        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

        # Verify UNWIND...MERGE does not recreate existing entities
        query = """UNWIND ['a', 'b'] AS names MERGE (a:Person {name: names}) RETURN a.name"""
        expected = [['a'], ['b']]

        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

        # Merging entities from an UNWIND list
        query = """UNWIND ['a', 'b', 'c'] AS names MERGE (a:Person {name: names}) ON MATCH SET a.set_by = 'match' ON CREATE SET a.set_by = 'create' RETURN a.name, a.set_by ORDER BY a.name"""
        expected = [['a', 'match'], ['b', 'match'], ['c', 'create']]

        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 4)
        self.env.assertEquals(result.result_set, expected)

        # Connect 'c' to both 'a' and 'b' via a Friend relation
        # One thing to note here is that both `c` and `x` are bounded, which means
        # our current merge distinct validation inspect the created edge only using its relationship, properties and bounded
        # nodes! as such the first created edge is different from the second one (due to changes in the destination node).
        query = """MATCH (c:Person {name: 'c'}) MATCH (x:Person) WHERE x.name in ['a', 'b'] WITH c, x MERGE(c)-[:FRIEND]->(x)"""
        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.relationships_created, 2)

        # Verify function calls in MERGE do not recreate existing entities
        query = """UNWIND ['A', 'B'] AS names MERGE (a:Person {name: toLower(names)}) RETURN a.name"""
        expected = [['a'], ['b']]

        result = graph_2.query(query)
        self.env.assertEquals(result.labels_added, 0)
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.result_set, expected)

        query = """MERGE (a:Person {name: 'a'}) ON MATCH SET a.set_by = 'match' ON CREATE SET a.set_by = 'create' MERGE (b:Clone {name: a.name + '_clone'}) ON MATCH SET b.set_by = 'match' ON CREATE SET b.set_by = 'create' RETURN a.name, a.set_by, b.name, b.set_by"""
        result = graph_2.query(query)
        expected = [['a', 'match', 'a_clone', 'create']]

        # Verify the results
        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.result_set, expected)

    def test18_merge_unique_creations(self):
        global graph_2
        # Create a new pattern with non-unique entities.
        query = """UNWIND ['newprop1', 'newprop2'] AS x MERGE ({v:x})-[:e]->(n {v:'newprop1'})"""
        result = graph_2.query(query)

        # Verify that every entity was created in both executions.
        self.env.assertEquals(result.nodes_created, 4)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(result.properties_set, 4)

        # Repeat the query.
        result = graph_2.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test19_merge_dependency(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Starting with an empty graph.
        # Create 2 nodes and connect them to one another.
        self.env.flush()
        query = """MERGE (a:Person {name: 'a'}) MERGE (b:Person {name: 'b'}) MERGE (a)-[:FRIEND]->(b) MERGE (b)-[:FRIEND]->(a)"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(result.properties_set, 2)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test20_merge_edge_dependency(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Starting with an empty graph.
        # Make sure the pattern ()-[]->()-[]->()-[]->() exists.
        self.env.flush()
        query = """MERGE (a {v:1}) MERGE (b {v:2}) MERGE (a)-[:KNOWS]->(b) MERGE ()-[:KNOWS]->()-[:KNOWS]->()"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 5)
        self.env.assertEquals(result.relationships_created, 3)
        self.env.assertEquals(result.properties_set, 2)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test21_merge_scan(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Starting with an empty graph.
        # All node scan should see created nodes.
        self.env.flush()
        query = """MERGE (a {v:1}) WITH a MATCH (n) MERGE (n)-[:KNOWS]->(m)"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 1)
        self.env.assertEquals(result.properties_set, 1)

        # Starting with an empty graph.
        # Label scan should see created nodes.
        self.env.flush()
        query = """MERGE (a:L {v:1}) WITH a MATCH (n:L) MERGE (n)-[:KNOWS]->(m)"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 1)
        self.env.assertEquals(result.properties_set, 1)

    def test22_merge_label_scan(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Starting with an empty graph.
        # Make sure the pattern ()-[]->()-[]->()-[]->() exists.
        self.env.flush()
        query = """MERGE (a {v:1}) MERGE (b {v:2}) MERGE (a)-[:KNOWS]->(b) WITH a AS c, b AS d MATCH (c)-[:KNOWS]->(d) MERGE (c)-[:LIKES]->(d)"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(result.properties_set, 2)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test23_merge_var_traverse(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Starting with an empty graph.
        # Make sure the pattern ()-[]->()-[]->()-[]->() exists.
        self.env.flush()
        query = """MERGE (a {v:1}) MERGE (b {v:2}) MERGE (a)-[:KNOWS]->(b) WITH a AS c, b AS d MATCH (c)-[:KNOWS*]->(d) MERGE (c)-[:LIKES]->(d)"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(result.properties_set, 2)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.relationships_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test24_merge_merge_delete(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Merge followed by an additional merge and ending with a deletion
        # which doesn't have any data to operate on,
        # this used to trigger force lock release, as the delete didn't tried to acquire/release the lock
        self.env.flush()
        query = """MERGE (user:User {name:'Sceat'}) WITH user UNWIND [1,2,3] AS sessionHash MERGE (user)-[:HAS_SESSION]->(newSession:Session {hash:sessionHash}) WITH DISTINCT user, collect(newSession.hash) as newSessionHash MATCH (user)-->(s:Session) WHERE NOT s.hash IN newSessionHash DELETE s"""
        result = graph.query(query)

        # Verify that every entity was created.
        self.env.assertEquals(result.nodes_created, 4)
        self.env.assertEquals(result.properties_set, 4)
        self.env.assertEquals(result.relationships_created, 3)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)
        self.env.assertEquals(result.relationships_created, 0)

    def test25_merge_with_where(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        # Index the "L:prop) combination so that the MERGE tree will not have a filter op.
        query = """CREATE INDEX ON :L(prop)"""
        graph.query(query)

        query = """MERGE (n:L {prop:1}) WITH n WHERE n.prop < 1 RETURN n.prop"""
        result = graph.query(query)
        plan = graph.execution_plan(query)

        # Verify that the Filter op follows a Project op.
        self.env.assertTrue(re.search('Project\s+Filter', plan))

        # Verify that there is no Filter op after the Merge op.
        self.env.assertFalse(re.search('Merge\s+Filter', plan))

        # Verify that the entity was created and no results were returned.
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 1)

        # Repeat the query.
        result = graph.query(query)

        # Verify that no data was modified and no results were returned.
        self.env.assertEquals(result.nodes_created, 0)
        self.env.assertEquals(result.properties_set, 0)

    def test26_merge_set_invalid_property(self):
        redis_con = self.env.getConnection()
        graph = Graph("M", redis_con)

        query = """MATCH p=() MERGE () ON MATCH SET p.prop4 = 5"""
        result = graph.query(query)
        self.env.assertEquals(result.properties_set, 0)

    def test27_merge_create_invalid_entity(self):
        # Skip this test if running under Valgrind, as it causes a memory leak.
        if Env().envRunner.debugger is not None:
            Env().skip()

        redis_con = self.env.getConnection()
        graph = Graph("N", redis_con)  # Instantiate a new graph.

        try:
            # Try to create a node with an invalid NULL property.
            query = """MERGE (n {v: NULL})"""
            graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Cannot merge node using null property value" in e.message)
            pass

        # Verify that no entities were created.
        query = """MATCH (a) RETURN a"""
        result = graph.query(query)
        self.env.assertEquals(result.result_set, [])

        try:
            # Try to merge a node with a self-referential property.
            query = """MERGE (a:L {v: a.v})"""
            graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            self.env.assertIn("undefined property", e.message)
Exemplo n.º 4
0
class testFunctionCallsFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global graph
        global redis_con
        redis_con = self.env.getConnection()
        graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global graph
        nodes = {}
        # Create entities
        for idx, p in enumerate(people):
            node = Node(label="person", properties={"name": p, "val": idx})
            graph.add_node(node)
            nodes[p] = node

        # Fully connected graph
        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "know", nodes[dest])
                    graph.add_edge(edge)

        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "works_with", nodes[dest])
                    graph.add_edge(edge)

        graph.commit()
        query = """MATCH (a)-[:know]->(b) CREATE (a)-[:know]->(b)"""
        graph.query(query)

    def expect_type_error(self, query):
        try:
            graph.query(query)
            assert(False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn("Type mismatch", e.message)
    
    def expect_error(self, query, expected_err_msg):
        try:
            graph.query(query)
            assert(False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn(expected_err_msg, e.message)

    # Validate capturing of errors prior to query execution.
    def test01_compile_time_errors(self):
        query = """RETURN toUpper(5)"""
        self.expect_type_error(query)

        query = """RETURN 'a' * 2"""
        self.expect_type_error(query)

        query = """RETURN max(1 + min(2))"""
        self.expect_error(query, "Can't use aggregate functions inside of aggregate functions")

    def test02_boolean_comparisons(self):
        query = """RETURN true = 5"""
        actual_result = graph.query(query)
        expected_result = [[False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN true <> 'str'"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 'anything' <> NULL"""
        actual_result = graph.query(query)
        expected_result = [[None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 'anything' = NULL"""
        actual_result = graph.query(query)
        expected_result = [[None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 10 >= 1.5"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN -1 < 1"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test03_boolean_errors(self):
        query = """RETURN 'str' < 5.5"""
        self.expect_type_error(query)

        query = """RETURN true > 5"""
        self.expect_type_error(query)

        query = """MATCH (a) RETURN a < 'anything' LIMIT 1"""
        self.expect_type_error(query)

    def test04_entity_functions(self):
        query = "RETURN ID(5)"
        self.expect_type_error(query)

        query = "MATCH (a) RETURN ID(a) ORDER BY ID(a) LIMIT 3"
        actual_result = graph.query(query)
        expected_result = [[0], [1], [2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "MATCH (a)-[e]->() RETURN ID(e) ORDER BY ID(e) LIMIT 3"
        actual_result = graph.query(query)
        expected_result = [[0], [1], [2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN EXISTS(null)"
        actual_result = graph.query(query)
        expected_result = [[False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN EXISTS('anything')"
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test07_nonmap_errors(self):
        query = """MATCH (a) WITH a.name AS scalar RETURN scalar.name"""
        self.expect_type_error(query)

    def test08_apply_all_function(self):
        query = "MATCH () RETURN COUNT(*)"
        actual_result = graph.query(query)
        expected_result = [[4]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "UNWIND [1, 2] AS a RETURN COUNT(*)"
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)
    
    def test09_static_aggregation(self):
        query = "RETURN count(*)"
        actual_result = graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN max(2)"
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN min(3)"
        actual_result = graph.query(query)
        expected_result = [[3]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test10_modulo_inputs(self):
        # Validate modulo with integer inputs.
        query = "RETURN 5 % 2"
        actual_result = graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with a floating-point dividend.
        query = "RETURN 5.5 % 2"
        actual_result = graph.query(query)
        expected_result = [[1.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with a floating-point divisor.
        query = "RETURN 5 % 2.5"
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with both a floating-point dividen and a floating-point divisor.
        query = "RETURN 5.5 % 2.5"
        actual_result = graph.query(query)
        expected_result = [[0.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with negative integer inputs.
        query = "RETURN -5 % -2"
        actual_result = graph.query(query)
        expected_result = [[-1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with negative floating-point inputs.
        query = "RETURN -5.5 % -2.5"
        actual_result = graph.query(query)
        expected_result = [[-0.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Aggregate functions should handle null inputs appropriately.
    def test11_null_aggregate_function_inputs(self):
        # SUM should sum all non-null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN sum(a)"""
        actual_result = graph.query(query)
        expected_result = [[4]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # SUM should return 0 given a fully NULL input.
        query = """WITH NULL AS a RETURN sum(a)"""
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COUNT should count all non-null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN count(a)"""
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COUNT should return 0 given a fully NULL input.
        query = """WITH NULL AS a RETURN count(a)"""
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COLLECT should ignore null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN collect(a)"""
        actual_result = graph.query(query)
        expected_result = [[[1, 3]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COLLECT should return an empty array on all null inputs.
        query = """WITH NULL AS a RETURN collect(a)"""
        actual_result = graph.query(query)
        expected_result = [[[]]]
        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 5
0
class testNodeByIDFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph
        # Create entities
        for i in range(10):
            node = Node(label="person", properties={"id": i})
            redis_graph.add_node(node)
        redis_graph.commit()

        # Make sure node id attribute matches node's internal ID.
        query = """MATCH (n) SET n.id = ID(n)"""
        redis_graph.query(query)

    # Expect an error when trying to use a function which does not exists.
    def test_get_nodes(self):
        # All nodes, not including first node.
        query = """MATCH (n) WHERE ID(n) > 0 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id > 0 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 0 < ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 0 < n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # All nodes.
        query = """MATCH (n) WHERE ID(n) >= 0 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id >= 0 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 0 <= ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 0 <= n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # A single node.
        query = """MATCH (n) WHERE ID(n) = 0 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id = 0 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # 4 nodes (6,7,8,9)
        query = """MATCH (n) WHERE ID(n) > 5 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id > 5 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 5 < ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 5 < n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # 5 nodes (5, 6,7,8,9)
        query = """MATCH (n) WHERE ID(n) >= 5 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id >= 5 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 5 <= ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 5 <= n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # 5 nodes (0,1,2,3,4)
        query = """MATCH (n) WHERE ID(n) < 5 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id < 5 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 5 < ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 5 < n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # 6 nodes (0,1,2,3,4,5)
        query = """MATCH (n) WHERE ID(n) <= 5 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id <= 5 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 5 >= ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 5 >= n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # All nodes except last one.
        query = """MATCH (n) WHERE ID(n) < 9 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id < 9 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 9 > ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 9 > n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # All nodes.
        query = """MATCH (n) WHERE ID(n) <= 9 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id <= 9 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 9 >= ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 9 >= n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # All nodes.
        query = """MATCH (n) WHERE ID(n) < 100 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id < 100 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 100 > ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 100 > n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # All nodes.
        query = """MATCH (n) WHERE ID(n) <= 100 RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE n.id <= 100 RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (n) WHERE 100 >= ID(n) RETURN n ORDER BY n.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (n) WHERE 100 >= n.id RETURN n ORDER BY n.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        # cartesian product, tests reset works as expected.
        query = """MATCH (a), (b) WHERE ID(a) > 5 AND ID(b) <= 5 RETURN a,b ORDER BY a.id, b.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (a), (b) WHERE a.id > 5 AND b.id <= 5 RETURN a,b ORDER BY a.id, b.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

        query = """MATCH (a), (b) WHERE 5 < ID(a) AND 5 >= ID(b) RETURN a,b ORDER BY a.id, b.id"""
        resultsetA = redis_graph.query(query).result_set
        self.env.assertIn("NodeByIdSeek", redis_graph.execution_plan(query))
        query = """MATCH (a), (b) WHERE 5 < a.id AND 5 >= b.id RETURN a,b ORDER BY a.id, b.id"""
        self.env.assertNotIn("NodeByIdSeek", redis_graph.execution_plan(query))
        resultsetB = redis_graph.query(query).result_set
        self.env.assertEqual(resultsetA, resultsetB)

    # Try to fetch none existing entities by ID(s).
    def test_for_none_existing_entity_ids(self):
        # Try to fetch an entity with a none existing ID.
        queries = [
            """MATCH (a:person) WHERE ID(a) = 999 RETURN a""",
            """MATCH (a:person) WHERE ID(a) > 999 RETURN a""",
            """MATCH (a:person) WHERE ID(a) > 800 AND ID(a) < 900 RETURN a"""
        ]

        for query in queries:
            resultset = redis_graph.query(query).result_set
            self.env.assertEquals(len(resultset), 0)  # Expecting no results.
            self.env.assertIn("Node By Label and ID Scan",
                              redis_graph.execution_plan(query))
Exemplo n.º 6
0
class testIndexUpdatesFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()
        self.build_indices()

    def new_node(self):
        return Node(label=labels[node_ctr % 2],
                    properties={
                        'unique':
                        node_ctr,
                        'group':
                        random.choice(groups),
                        'doubleval':
                        round(random.uniform(-1, 1), 2),
                        'intval':
                        random.randint(1, 10000),
                        'stringval':
                        ''.join(
                            random.choice(string.ascii_lowercase)
                            for x in range(6))
                    })

    def populate_graph(self):
        global node_ctr
        for i in range(1000):
            node = self.new_node()
            redis_graph.add_node(node)
            node_ctr += 1
        redis_graph.commit()

    def build_indices(self):
        for field in fields:
            redis_graph.redis_con.execute_command(
                "GRAPH.QUERY", GRAPH_ID,
                "CREATE INDEX ON :label_a(%s)" % (field))
            redis_graph.redis_con.execute_command(
                "GRAPH.QUERY", GRAPH_ID,
                "CREATE INDEX ON :label_b(%s)" % (field))

    # Validate that all properties are indexed
    def validate_indexed(self):
        for field in fields:
            resp = redis_graph.execution_plan(
                """MATCH (a:label_a) WHERE a.%s > 0 RETURN a""" % (field))
            self.env.assertIn('Index Scan', resp)

    # So long as 'unique' is not modified, label_a.unique will always be even and label_b.unique will always be odd
    def validate_unique(self):
        result = redis_graph.query("MATCH (a:label_a) RETURN a.unique")
        # Remove the header
        result.result_set.pop(0)
        for val in result.result_set:
            self.env.assertEquals(int(float(val[0])) % 2, 0)

        result = redis_graph.query("MATCH (b:label_b) RETURN b.unique")
        # Remove the header
        result.result_set.pop(0)
        for val in result.result_set:
            self.env.assertEquals(int(float(val[0])) % 2, 1)

    # The index scan ought to return identical results to a label scan over the same range of values.
    def validate_doubleval(self):
        for label in labels:
            resp = redis_graph.execution_plan(
                """MATCH (a:%s) WHERE a.doubleval < 100 RETURN a.doubleval ORDER BY a.doubleval"""
                % (label))
            self.env.assertIn('Index Scan', resp)
            indexed_result = redis_graph.query(
                """MATCH (a:%s) WHERE a.doubleval < 100 RETURN a.doubleval ORDER BY a.doubleval"""
                % (label))
            scan_result = redis_graph.query(
                """MATCH (a:%s) RETURN a.doubleval ORDER BY a.doubleval""" %
                (label))

            self.env.assertEqual(len(indexed_result.result_set),
                                 len(scan_result.result_set))
            # Collect any elements between the two result sets that fail a string comparison
            # so that we may compare them as doubles (specifically, -0 and 0 should be considered equal)
            differences = [[i[0], j[0]] for i, j in zip(
                indexed_result.result_set, scan_result.result_set) if i != j]
            for pair in differences:
                self.env.assertEqual(float(pair[0]), float(pair[1]))

    # The intval property can be assessed similar to doubleval, but the result sets should be identical
    def validate_intval(self):
        for label in labels:
            resp = redis_graph.execution_plan(
                """MATCH (a:%s) WHERE a.intval > 0 RETURN a.intval ORDER BY a.intval"""
                % (label))
            self.env.assertIn('Index Scan', resp)
            indexed_result = redis_graph.query(
                """MATCH (a:%s) WHERE a.intval > 0 RETURN a.intval ORDER BY a.intval"""
                % (label))
            scan_result = redis_graph.query(
                """MATCH (a:%s) RETURN a.intval ORDER BY a.intval""" % (label))

            self.env.assertEqual(indexed_result.result_set,
                                 scan_result.result_set)

    # Validate a series of premises to ensure that the graph has not been modified unexpectedly
    def validate_state(self):
        self.validate_unique()
        self.validate_indexed()
        self.validate_doubleval()
        self.validate_intval()

    # Modify a property, triggering updates to all nodes in two indices
    def test01_full_property_update(self):
        result = redis_graph.query(
            "MATCH (a) SET a.doubleval = a.doubleval + %f" %
            (round(random.uniform(-1, 1), 2)))
        self.env.assertEquals(result.properties_set, 1000)
        # Verify that index scans still function and return correctly
        self.validate_state()

    # Modify a property, triggering updates to a subset of nodes in two indices
    def test02_partial_property_update(self):
        redis_graph.query(
            "MATCH (a) WHERE a.doubleval > 0 SET a.doubleval = a.doubleval + %f"
            % (round(random.uniform(-1, 1), 2)))
        # Verify that index scans still function and return correctly
        self.validate_state()

    #  Add 100 randomized nodes and validate indices
    def test03_node_creation(self):
        # Reset nodes in the Graph object so that we won't double-commit the originals
        redis_graph.nodes = {}
        global node_ctr
        for i in range(100):
            node = self.new_node()
            redis_graph.add_node(node)
            node_ctr += 1
        redis_graph.commit()
        self.validate_state()

    # Delete every other node in first 100 and validate indices
    def test04_node_deletion(self):
        # Reset nodes in the Graph object so that we won't double-commit the originals
        redis_graph.nodes = {}
        global node_ctr
        # Delete nodes one at a time
        for i in range(0, 100, 2):
            result = redis_graph.query("MATCH (a) WHERE ID(a) = %d DELETE a" %
                                       (i))
            self.env.assertEquals(result.nodes_deleted, 1)
            node_ctr -= 1
        self.validate_state()

        # Delete all nodes matching a filter
        result = redis_graph.query(
            "MATCH (a:label_a) WHERE a.group = 'Group A' DELETE a")
        self.env.assertGreater(result.nodes_deleted, 0)
        self.validate_state()

    def test05_unindexed_property_update(self):
        # Add an unindexed property to all nodes.
        redis_graph.query("MATCH (a) SET a.unindexed = 'unindexed'")

        # Retrieve a single node
        result = redis_graph.query("MATCH (a) RETURN a.unique LIMIT 1")
        unique_prop = result.result_set[0][0]
        query = """MATCH (a {unique: %s }) SET a.unindexed = 5, a.unique = %s RETURN a.unindexed, a.unique""" % (
            unique_prop, unique_prop)
        result = redis_graph.query(query)
        expected_result = [[5, unique_prop]]
        self.env.assertEquals(result.result_set, expected_result)
        self.env.assertEquals(result.properties_set, 1)

    # Validate that after deleting an indexed property, that property can no longer be found in the index.
    def test06_remove_indexed_prop(self):
        # Create a new node with a single indexed property
        query = """CREATE (:NEW {v: 5})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.properties_set, 1)
        self.env.assertEquals(result.labels_added, 1)
        redis_graph.query("CREATE INDEX ON :NEW(v)")

        # Delete the entity's property
        query = """MATCH (a:NEW {v: 5}) SET a.v = NULL"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.properties_set, 1)

        # Query the index for the entity
        query = """MATCH (a:NEW {v: 5}) RETURN a"""
        plan = redis_graph.execution_plan(query)
        self.env.assertIn("Index Scan", plan)
        result = redis_graph.query(query)
        # No entities should be returned
        expected_result = []
        self.env.assertEquals(result.result_set, expected_result)

    # Validate that when a label has both exact-match and full-text indexes
    # on different properties, an update operation checks all indexes to
    # determine whether they must be updated.
    # This is necessary because either one of the indexes may not track the
    # property being updated, but that does not guarantee that the other
    # index does not track the property.
    def test07_update_property_only_on_fulltext_index(self):
        # Remove the exact-match index on a property
        redis_graph.redis_con.execute_command("GRAPH.QUERY", GRAPH_ID,
                                              "DROP INDEX ON :label_a(group)")
        # Add a full-text index on the property
        redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('label_a', 'group')")

        # Modify the values of the property
        result = redis_graph.query(
            "MATCH (a:label_a) WHERE a.group = 'Group C' SET a.group = 'Group NEW'"
        )
        modified_count = result.properties_set
        self.env.assertGreater(modified_count, 0)

        # Validate that the full-text index reflects the update
        result = redis_graph.query(
            "CALL db.idx.fulltext.queryNodes('label_a', 'Group NEW')")
        self.env.assertEquals(len(result.result_set), modified_count)

        # Validate that the previous value has been removed
        result = redis_graph.query(
            "CALL db.idx.fulltext.queryNodes('label_a', 'Group C')")
        self.env.assertEquals(len(result.result_set), 0)
Exemplo n.º 7
0
class testGraphCreationFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)

    def test01_create_return(self):
        query = """CREATE (a:person {name:'A'}), (b:person {name:'B'})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.nodes_created, 2)

        query = """MATCH (src:person) CREATE (src)-[e:knows]->(dest {name:'C'}) RETURN src,e,dest ORDER BY ID(src) DESC LIMIT 1"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(len(result.result_set), 1)
        self.env.assertEquals(result.result_set[0][0].properties['name'], 'B')

    def test02_create_from_prop(self):
        query = """MATCH (p:person)-[e:knows]->() CREATE (c:clone {doublename: p.name + toLower(p.name), source_of: TYPE(e)}) RETURN c.doublename, c.source_of ORDER BY c.doublename"""
        result = redis_graph.query(query)
        expected_result = [['Aa', 'knows'], ['Bb', 'knows']]

        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 4)
        self.env.assertEquals(result.result_set, expected_result)

    def test03_create_from_projection(self):
        query = """UNWIND [10,20,30] AS x CREATE (p:person {age:x}) RETURN p.age ORDER BY p.age"""
        result = redis_graph.query(query)
        expected_result = [[10], [20], [30]]
        self.env.assertEquals(result.nodes_created, 3)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.result_set, expected_result)
Exemplo n.º 8
0
class testGraphCreationFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)

    def test01_create_return(self):
        query = """CREATE (a:person {name:'A'}), (b:person {name:'B'})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.nodes_created, 2)

        query = """MATCH (src:person) CREATE (src)-[e:knows]->(dest {name:'C'}) RETURN src,e,dest ORDER BY ID(src) DESC LIMIT 1"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.relationships_created, 2)
        self.env.assertEquals(len(result.result_set), 1)
        self.env.assertEquals(result.result_set[0][0].properties['name'], 'B')

    def test02_create_from_prop(self):
        query = """MATCH (p:person)-[e:knows]->() CREATE (c:clone {doublename: p.name + toLower(p.name), source_of: TYPE(e)}) RETURN c.doublename, c.source_of ORDER BY c.doublename"""
        result = redis_graph.query(query)
        expected_result = [['Aa', 'knows'], ['Bb', 'knows']]

        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 4)
        self.env.assertEquals(result.result_set, expected_result)

    def test03_create_from_projection(self):
        query = """UNWIND [10,20,30] AS x CREATE (p:person {age:x}) RETURN p.age ORDER BY p.age"""
        result = redis_graph.query(query)
        expected_result = [[10], [20], [30]]
        self.env.assertEquals(result.nodes_created, 3)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.result_set, expected_result)

        query = """UNWIND ['Vancouver', 'Portland', 'Calgary'] AS city CREATE (p:person {birthplace: city}) RETURN p.birthplace ORDER BY p.birthplace"""
        result = redis_graph.query(query)
        expected_result = [['Calgary'], ['Portland'], ['Vancouver']]
        self.env.assertEquals(result.nodes_created, 3)
        self.env.assertEquals(result.properties_set, 3)
        self.env.assertEquals(result.result_set, expected_result)

    def test04_create_with_null_properties(self):
        query = """CREATE (a:L {v1: NULL, v2: 'prop'}) RETURN a"""
        result = redis_graph.query(query)
        node = Node(label="L", properties={"v2": "prop"})
        expected_result = [[node]]

        self.env.assertEquals(result.labels_added, 1)
        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.properties_set, 1)
        self.env.assertEquals(result.result_set, expected_result)

        # Create 2 new nodes, one with no properties and one with a property 'v'
        query = """CREATE (:M), (:M {v: 1})"""
        redis_graph.query(query)

        # Verify that a MATCH...CREATE accesses the property correctly.
        query = """MATCH (m:M) WITH m ORDER BY m.v DESC CREATE ({v: m.v})"""
        result = redis_graph.query(query)
        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.properties_set, 1)

    def test05_create_with_property_reference(self):
        # Skip this test if running under Valgrind, as it causes a memory leak.
        if Env().envRunner.debugger is not None:
            Env().skip()

        # Queries that reference properties before they have been created should emit an error.
        try:
            query = """CREATE (a {val: 2}), (b {val: a.val})"""
            redis_graph.query(query)
            self.env.assertTrue(False)
        except redis.exceptions.ResponseError as e:
            self.env.assertIn("undefined property", str(e))

    def test06_create_project_volatile_value(self):
        # The path e is volatile; verify that it can be projected after entity creation.
        query = """MATCH ()-[e*]->() CREATE (:L) WITH e RETURN 5"""
        result = redis_graph.query(query)
        expected_result = [[5], [5]]

        self.env.assertEquals(result.nodes_created, 2)
        self.env.assertEquals(result.result_set, expected_result)

        query = """UNWIND [1, 2] AS val WITH collect(val) AS arr CREATE (:L) RETURN arr"""
        result = redis_graph.query(query)
        expected_result = [[[1, 2]]]

        self.env.assertEquals(result.nodes_created, 1)
        self.env.assertEquals(result.result_set, expected_result)
Exemplo n.º 9
0
class testMultiExecFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_con
        redis_con = self.env.getConnection()

    def test_graph_entities(self):
        # Delete previous graph if exists.
        redis_con.execute_command("DEL", GRAPH_ID)

        # Start a multi exec transaction.
        redis_con.execute_command("MULTI")

        # Create graph.
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, CREATE_QUERY)

        # Count outgoing connections from Al, expecting 2 edges.
        # (Al)-[e]->() count (e)
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)

        # Disconnect edge connecting Al to Betty.
        # (Al)-[e]->(Betty) delete (e)
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, DEL_QUERY)

        # Count outgoing connections from Al, expecting 1 edges.
        # (Al)-[e]->() count (e)
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)

        # Change Al name from Al to Steve.
        # (Al) set Al.name = Steve
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, UPDATE_QUERY)

        # Count outgoing connections from Al, expecting 0 edges.
        # (Al)-[e]->() count (e)
        redis_con.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)

        # Commit transaction.
        results = redis_con.execute_command("EXEC")

        # [
        #   [
        #       ['al.name', 'count(b)'],
        #       ['Al', '2']
        #   ],
        #       ['Query internal execution time: 0.143000 milliseconds']
        # ]

        two_edges = results[1]
        two_edges = two_edges[1][0][1]        
        self.env.assertEquals(two_edges, 2)

        one_edge = results[3]
        one_edge = one_edge[1][0][1]
        self.env.assertEquals(one_edge, 1)

        no_edges = results[5]
        no_edges = no_edges[1]
        self.env.assertEquals(len(no_edges), 0)

    def test_transaction_failure(self):
        redis_con_a = self.env.getConnection()
        redis_con_b = self.env.getConnection()
        results = redis_con_b.execute_command("INFO", "CLIENTS")
        self.env.assertGreaterEqual(results['connected_clients'], 2)

        # Delete previous graph if exists.
        redis_con_a.execute_command("DEL", GRAPH_ID)
        redis_con_a.execute_command("GRAPH.QUERY", GRAPH_ID, CREATE_QUERY)

        redis_con_a.execute_command("WATCH", GRAPH_ID)
        redis_con_a.execute_command("MULTI")
        redis_con_a.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)
        results = redis_con_a.execute_command("EXEC")

        self.env.assertNotEqual(results, None)

        # read only query from client B - transaction OK
        redis_con_a.execute_command("WATCH", GRAPH_ID)
        redis_con_a.execute_command("MULTI")
        redis_con_b.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)
        redis_con_a.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)
        results = redis_con_a.execute_command("EXEC")

        self.env.assertNotEqual(results, None)

        # write query from client B - transaction fails
        redis_con_a.execute_command("WATCH", GRAPH_ID)
        redis_con_a.execute_command("MULTI")
        redis_con_b.execute_command("GRAPH.QUERY", GRAPH_ID, UPDATE_QUERY)
        redis_con_a.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)
        results = redis_con_a.execute_command("EXEC")

        self.env.assertEqual(results, None)

        # GRAPH.EXPLAIN is read only - no data change - transaction OK
        redis_con_a.execute_command("WATCH", GRAPH_ID)
        redis_con_a.execute_command("MULTI")
        redis_con_b.execute_command("GRAPH.EXPLAIN", GRAPH_ID, UPDATE_QUERY)
        redis_con_a.execute_command("GRAPH.QUERY", GRAPH_ID, MATCH_QUERY)
        results = redis_con_a.execute_command("EXEC")

        self.env.assertNotEqual(results, None)
Exemplo n.º 10
0
class testIndexUpdatesFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()
        self.build_indices()

    def new_node(self):
        return Node(label=labels[node_ctr % 2],
                    properties={
                        'unique':
                        node_ctr,
                        'group':
                        random.choice(groups),
                        'doubleval':
                        round(random.uniform(-1, 1), 2),
                        'intval':
                        random.randint(1, 10000),
                        'stringval':
                        ''.join(
                            random.choice(string.lowercase) for x in range(6))
                    })

    def populate_graph(self):
        global node_ctr
        for i in range(1000):
            node = self.new_node()
            redis_graph.add_node(node)
            node_ctr += 1
        redis_graph.commit()

    def build_indices(self):
        for field in fields:
            redis_graph.redis_con.execute_command(
                "GRAPH.QUERY", GRAPH_ID,
                "CREATE INDEX ON :label_a(%s)" % (field))
            redis_graph.redis_con.execute_command(
                "GRAPH.QUERY", GRAPH_ID,
                "CREATE INDEX ON :label_b(%s)" % (field))

    # Validate that all properties are indexed
    def validate_indexed(self):
        for field in fields:
            resp = redis_graph.execution_plan(
                """MATCH (a:label_a) WHERE a.%s > 0 RETURN a""" % (field))
            self.env.assertIn('Index Scan', resp)

    # So long as 'unique' is not modified, label_a.unique will always be even and label_b.unique will always be odd
    def validate_unique(self):
        result = redis_graph.query("MATCH (a:label_a) RETURN a.unique")
        # Remove the header
        result.result_set.pop(0)
        for val in result.result_set:
            self.env.assertEquals(int(float(val[0])) % 2, 0)

        result = redis_graph.query("MATCH (b:label_b) RETURN b.unique")
        # Remove the header
        result.result_set.pop(0)
        for val in result.result_set:
            self.env.assertEquals(int(float(val[0])) % 2, 1)

    # The index scan ought to return identical results to a label scan over the same range of values.
    def validate_doubleval(self):
        for label in labels:
            resp = redis_graph.execution_plan(
                """MATCH (a:%s) WHERE a.doubleval < 100 RETURN a.doubleval ORDER BY a.doubleval"""
                % (label))
            self.env.assertIn('Index Scan', resp)
            indexed_result = redis_graph.query(
                """MATCH (a:%s) WHERE a.doubleval < 100 RETURN a.doubleval ORDER BY a.doubleval"""
                % (label))
            scan_result = redis_graph.query(
                """MATCH (a:%s) RETURN a.doubleval ORDER BY a.doubleval""" %
                (label))

            self.env.assertEqual(len(indexed_result.result_set),
                                 len(scan_result.result_set))
            # Collect any elements between the two result sets that fail a string comparison
            # so that we may compare them as doubles (specifically, -0 and 0 should be considered equal)
            differences = [[i[0], j[0]] for i, j in zip(
                indexed_result.result_set, scan_result.result_set) if i != j]
            for pair in differences:
                self.env.assertEqual(float(pair[0]), float(pair[1]))

    # The intval property can be assessed similar to doubleval, but the result sets should be identical
    def validate_intval(self):
        for label in labels:
            resp = redis_graph.execution_plan(
                """MATCH (a:%s) WHERE a.intval > 0 RETURN a.intval ORDER BY a.intval"""
                % (label))
            self.env.assertIn('Index Scan', resp)
            indexed_result = redis_graph.query(
                """MATCH (a:%s) WHERE a.intval > 0 RETURN a.intval ORDER BY a.intval"""
                % (label))
            scan_result = redis_graph.query(
                """MATCH (a:%s) RETURN a.intval ORDER BY a.intval""" % (label))

            self.env.assertEqual(indexed_result.result_set,
                                 scan_result.result_set)

    # Validate a series of premises to ensure that the graph has not been modified unexpectedly
    def validate_state(self):
        self.validate_unique()
        self.validate_indexed()
        self.validate_doubleval()
        self.validate_intval()

    # Modify a property, triggering updates to all nodes in two indices
    def test01_full_property_update(self):
        result = redis_graph.query(
            "MATCH (a) SET a.doubleval = a.doubleval + %f" %
            (round(random.uniform(-1, 1), 2)))
        self.env.assertEquals(result.properties_set, 1000)
        # Verify that index scans still function and return correctly
        self.validate_state()

    # Modify a property, triggering updates to a subset of nodes in two indices
    def test02_partial_property_update(self):
        redis_graph.query(
            "MATCH (a) WHERE a.doubleval > 0 SET a.doubleval = a.doubleval + %f"
            % (round(random.uniform(-1, 1), 2)))
        # Verify that index scans still function and return correctly
        self.validate_state()

    #  Add 100 randomized nodes and validate indices
    def test03_node_creation(self):
        # Reset nodes in the Graph object so that we won't double-commit the originals
        redis_graph.nodes = {}
        global node_ctr
        for i in range(100):
            node = self.new_node()
            redis_graph.add_node(node)
            node_ctr += 1
        redis_graph.commit()
        self.validate_state()

    # Delete every other node in first 100 and validate indices
    def test04_node_deletion(self):
        # Reset nodes in the Graph object so that we won't double-commit the originals
        redis_graph.nodes = {}
        global node_ctr
        # Delete nodes one at a time
        for i in range(0, 100, 2):
            result = redis_graph.query("MATCH (a) WHERE ID(a) = %d DELETE a" %
                                       (i))
            self.env.assertEquals(result.nodes_deleted, 1)
            node_ctr -= 1
        self.validate_state()

        # Delete all nodes matching a filter
        result = redis_graph.query(
            "MATCH (a:label_a) WHERE a.group = 'Group A' DELETE a")
        self.env.assertGreater(result.nodes_deleted, 0)
        self.validate_state()

    def test05_unindexed_property_update(self):
        # Add an unindexed property to all nodes.
        redis_graph.query("MATCH (a) SET a.unindexed = 'unindexed'")

        # Retrieve a single node
        result = redis_graph.query("MATCH (a) RETURN a.unique LIMIT 1")
        unique_prop = result.result_set[0][0]
        query = """MATCH (a {unique: %s }) SET a.unindexed = 5, a.unique = %s RETURN a.unindexed, a.unique""" % (
            unique_prop, unique_prop)
        result = redis_graph.query(query)
        expected_result = [[5, unique_prop]]
        self.env.assertEquals(result.result_set, expected_result)
        self.env.assertEquals(result.properties_set, 2)
Exemplo n.º 11
0
class testOptionalFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("optional_match", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global nodes
        # Construct a graph with the form:
        # (v1)-[:E1]->(v2)-[:E2]->(v3), (v4)
        node_props = ['v1', 'v2', 'v3', 'v4']

        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"v": v})
            nodes[v] = node
            redis_graph.add_node(node)

        edge = Edge(nodes['v1'], "E1", nodes['v2'])
        redis_graph.add_edge(edge)

        edge = Edge(nodes['v2'], "E2", nodes['v3'])
        redis_graph.add_edge(edge)

        redis_graph.flush()

    # Optional MATCH clause that does not interact with the mandatory MATCH.
    def test01_disjoint_optional(self):
        global redis_graph
        query = """MATCH (a {v: 'v1'}) OPTIONAL MATCH (b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v1'], ['v1', 'v2'], ['v1', 'v3'],
                           ['v1', 'v4']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause that extends the mandatory MATCH pattern and has matches for all results.
    def test02_optional_traverse(self):
        global redis_graph
        query = """MATCH (a) WHERE a.v IN ['v1', 'v2'] OPTIONAL MATCH (a)-[]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2'], ['v2', 'v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause that extends the mandatory MATCH pattern and has null results.
    def test03_optional_traverse_with_nulls(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        # (v3) and (v4) have no outgoing edges.
        expected_result = [['v1', 'v2'], ['v2', 'v3'], ['v3', None],
                           ['v4', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause that extends the mandatory MATCH pattern and has a WHERE clause.
    def test04_optional_traverse_with_predicate(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[]->(b) WHERE b.v = 'v2' RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        # only (v1) has an outgoing edge to (v2).
        expected_result = [['v1', 'v2'], ['v2', None], ['v3', None],
                           ['v4', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause with endpoints resolved by the mandatory MATCH pattern.
    def test05_optional_expand_into(self):
        global redis_graph
        query = """MATCH (a)-[]->(b) OPTIONAL MATCH (a)-[e]->(b) RETURN a.v, b.v, TYPE(e) ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2', 'E1'], ['v2', 'v3', 'E2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # The OPTIONAL MATCH exactly repeats the MATCH, producing identical results.
        query_without_optional = """MATCH (a)-[e]->(b) RETURN a.v, b.v, TYPE(e) ORDER BY a.v, b.v"""
        result_without_optional = redis_graph.query(query_without_optional)
        self.env.assertEquals(actual_result.result_set,
                              result_without_optional.result_set)

    # Optional MATCH clause with endpoints resolved by the mandatory MATCH pattern and new filters introduced.
    def test06_optional_expand_into_with_reltype(self):
        global redis_graph
        query = """MATCH (a)-[]->(b) OPTIONAL MATCH (a)-[e:E2]->(b) RETURN a.v, b.v, TYPE(e) ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        # Only (v2)-[E2]->(v3) fulfills the constraint of the OPTIONAL MATCH clause.
        expected_result = [['v1', 'v2', None], ['v2', 'v3', 'E2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause with endpoints resolved by the mandatory MATCH pattern, but no mandatory traversal.
    def test07_optional_expand_into_cartesian_product(self):
        global redis_graph
        query = """MATCH (a {v: 'v1'}), (b) OPTIONAL MATCH (a)-[e]->(b) RETURN a.v, b.v, TYPE(e) ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        # All nodes are represented, but (v1)-[E1]->(v2) is the only matching connection.
        expected_result = [['v1', 'v1', None], ['v1', 'v2', 'E1'],
                           ['v1', 'v3', None], ['v1', 'v4', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # TODO ExpandInto doesn't evaluate bidirectionally properly
    # Optional MATCH clause with endpoints resolved by the mandatory MATCH pattern and a bidirectional optional pattern.
    #  def test08_optional_expand_into_bidirectional(self):
    #  global redis_graph
    #  query = """MATCH (a), (b {v: 'v2'}) OPTIONAL MATCH (a)-[e]-(b) RETURN a.v, b.v, TYPE(e) ORDER BY a.v, b.v"""
    #  actual_result = redis_graph.query(query)
    #  # All nodes are represented, but only edges with (v2) as an endpoint match.
    #  expected_result = [['v1', 'v2', 'E1'],
    #  ['v2', 'v2', None],
    #  ['v3', 'v2', 'E2'],
    #  ['v3', 'v2', None]]
    #  self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause with variable-length traversal and some results match.
    def test09_optional_variable_length(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[*]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2'], ['v1', 'v3'], ['v2', 'v3'],
                           ['v3', None], ['v4', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause with variable-length traversal and all results match.
    def test10_optional_variable_length_all_matches(self):
        global redis_graph
        query = """MATCH (a {v: 'v1'}) OPTIONAL MATCH (a)-[*]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2'], ['v1', 'v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Optional MATCH clause with a variable-length traversal that has no matches.
    def test11_optional_variable_length_no_matches(self):
        global redis_graph
        query = """MATCH (a {v: 'v3'}) OPTIONAL MATCH (a)-[*]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v3', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Multiple interdependent optional MATCH clauses.
    def test12_multiple_optional_traversals(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[]->(b) OPTIONAL MATCH (b)-[]->(c) RETURN a.v, b.v, c.v ORDER BY a.v, b.v, c.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2', 'v3'], ['v2', 'v3', None],
                           ['v3', None, None], ['v4', None, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Multiple interdependent optional MATCH clauses with both directed and bidirectional traversals.
    def test13_multiple_optional_multi_directional_traversals(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[]-(b) OPTIONAL MATCH (b)-[]->(c) RETURN a.v, b.v, c.v ORDER BY a.v, b.v, c.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2', 'v3'], ['v2', 'v1', 'v2'],
                           ['v2', 'v3', None], ['v3', 'v2', 'v3'],
                           ['v4', None, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Multiple interdependent optional MATCH clauses with exclusively bidirectional traversals.
    def test14_multiple_optional_bidirectional_traversals(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[]-(b) OPTIONAL MATCH (b)-[]-(c) RETURN a.v, b.v, c.v ORDER BY a.v, b.v, c.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2', 'v1'], ['v1', 'v2', 'v3'],
                           ['v2', 'v1', 'v2'], ['v2', 'v3', 'v2'],
                           ['v3', 'v2', 'v1'], ['v3', 'v2', 'v3'],
                           ['v4', None, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Build a named path in an optional clause.
    def test15_optional_named_path(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH p = (a)-[]->(b) RETURN length(p) ORDER BY length(p)"""
        actual_result = redis_graph.query(query)
        # 2 nodes have outgoing edges and 2 do not, so expected 2 paths of length 1 and 2 null results.
        expected_result = [[1], [1], [None], [None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Return a result set with null values in the first record and non-null values in subsequent records.
    def test16_optional_null_first_result(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a)-[e]->(b) RETURN a, b, TYPE(e) ORDER BY EXISTS(b), a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [[nodes['v3'], None, None],
                           [nodes['v4'], None, None],
                           [nodes['v1'], nodes['v2'], 'E1'],
                           [nodes['v2'], nodes['v3'], 'E2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test17_optional_label_introductions(self):
        global redis_graph
        query = """MATCH (a) OPTIONAL MATCH (a:L)-[]->(b:L) RETURN a.v, b.v ORDER BY a.v, b.v"""
        actual_result = redis_graph.query(query)
        expected_result = [['v1', 'v2'], ['v2', 'v3'], ['v3', None],
                           ['v4', None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Make sure highly connected nodes aren't lost
    def test18_optional_over_intermidate(self):
        global redis_graph
        query = """MATCH (a)-[]->(b)-[]->(c) OPTIONAL MATCH (b)-[]->(c) RETURN a"""
        plan = redis_graph.execution_plan(query)
        # Expecting to find "Expand Into" operation as both 'b' and 'c'
        # are bounded, which means 'b' is treated as an intermidate node
        # that needs to be tracked.
        self.env.assertIn("Expand Into", plan)
Exemplo n.º 12
0
class testGraphPersistency(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_con
        redis_con = self.env.getConnection()

    def populate_graph(self, graph_name):
        people = ["Roi", "Alon", "Ailon", "Boaz", "Tal", "Omri", "Ori"]
        countries = ["Israel", "USA", "Japan", "United Kingdom"]
        visits = [("Roi", "USA"), ("Alon", "Israel"), ("Ailon", "Japan"),
                  ("Boaz", "United Kingdom")]

        redis_graph = Graph(graph_name, redis_con)
        if not redis_con.exists(graph_name):
            personNodes = {}
            countryNodes = {}
            # Create entities

            for p in people:
                person = Node(label="person", properties={"name": p})
                redis_graph.add_node(person)
                personNodes[p] = person

            for p in countries:
                country = Node(label="country", properties={"name": p})
                redis_graph.add_node(country)
                countryNodes[p] = country

            for v in visits:
                person = v[0]
                country = v[1]
                edge = Edge(personNodes[person],
                            'visit',
                            countryNodes[country],
                            properties={'purpose': 'pleasure'})
                redis_graph.add_edge(edge)

            redis_graph.commit()

            # Delete nodes, to introduce deleted item within our datablock
            query = """MATCH (n:person) WHERE n.name = 'Roi' or n.name = 'Ailon' DELETE n"""
            redis_graph.query(query)

            query = """MATCH (n:country) WHERE n.name = 'USA' DELETE n"""
            redis_graph.query(query)

            # Create index.
            actual_result = redis_con.execute_command(
                "GRAPH.QUERY", graph_name, "CREATE INDEX ON :person(name)")
            actual_result = redis_con.execute_command(
                "GRAPH.QUERY", graph_name, "CREATE INDEX ON :country(name)")
        return redis_graph

    def populate_dense_graph(self, dense_graph_name):
        dense_graph = Graph(dense_graph_name, redis_con)
        if not redis_con.exists(dense_graph_name):
            nodes = []
            for i in range(10):
                node = Node(label="n", properties={"val": i})
                dense_graph.add_node(node)
                nodes.append(node)

            for n_idx, n in enumerate(nodes):
                for m_idx, m in enumerate(nodes[:n_idx]):
                    dense_graph.add_edge(Edge(n, "connected", m))

            dense_graph.flush()
        return dense_graph

    #  Connect a single node to all other nodes.
    def test01_save_load_rdb(self):
        graph_names = ["G", "{tag}_G"]
        for graph_name in graph_names:
            redis_graph = self.populate_graph(graph_name)
            for i in range(2):
                if i == 1:
                    # Save RDB & Load from RDB
                    self.env.dumpAndReload()

                # Verify
                # Expecting 5 person entities.
                query = """MATCH (p:person) RETURN COUNT(p)"""
                actual_result = redis_graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 5)

                query = """MATCH (p:person) WHERE p.name='Alon' RETURN COUNT(p)"""
                actual_result = redis_graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 1)

                # Expecting 3 country entities.
                query = """MATCH (c:country) RETURN COUNT(c)"""
                actual_result = redis_graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 3)

                query = """MATCH (c:country) WHERE c.name = 'Israel' RETURN COUNT(c)"""
                actual_result = redis_graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 1)

                # Expecting 2 visit edges.
                query = """MATCH (n:person)-[e:visit]->(c:country) WHERE e.purpose='pleasure' RETURN COUNT(e)"""
                actual_result = redis_graph.query(query)
                edgeCount = actual_result.result_set[0][0]
                self.env.assertEquals(edgeCount, 2)

                # Verify indices exists.
                plan = redis_graph.execution_plan(
                    "MATCH (n:person) WHERE n.name = 'Roi' RETURN n")
                self.env.assertIn("Index Scan", plan)

                plan = redis_graph.execution_plan(
                    "MATCH (n:country) WHERE n.name = 'Israel' RETURN n")
                self.env.assertIn("Index Scan", plan)

    # Verify that edges are not modified after entity deletion
    def test02_deleted_entity_migration(self):
        graph_names = ("H", "{tag}_H")
        for graph_name in graph_names:
            dense_graph = self.populate_dense_graph(graph_name)
            query = """MATCH (p) WHERE ID(p) = 0 OR ID(p) = 3 OR ID(p) = 7 OR ID(p) = 9 DELETE p"""
            actual_result = dense_graph.query(query)
            self.env.assertEquals(actual_result.nodes_deleted, 4)
            query = """MATCH (p)-[]->(q) RETURN p.val, q.val ORDER BY p.val, q.val"""
            first_result = dense_graph.query(query)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            second_result = dense_graph.query(query)
            self.env.assertEquals(first_result.result_set,
                                  second_result.result_set)

    # Strings, numerics, booleans, and array properties should be properly serialized and reloaded
    def test03_restore_properties(self):
        graph_names = ("simple_props", "{tag}_simple_props")
        for graph_name in graph_names:
            graph = Graph(graph_name, redis_con)
            query = """CREATE (:p {strval: 'str', numval: 5.5, boolval: true, array: [1,2,3]})"""
            actual_result = graph.query(query)
            # Verify that node was created correctly
            self.env.assertEquals(actual_result.nodes_created, 1)
            self.env.assertEquals(actual_result.properties_set, 4)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            query = """MATCH (p) RETURN p.boolval, p.numval, p.strval, p.array"""
            actual_result = graph.query(query)

            # Verify that the properties are loaded correctly.
            expected_result = [[True, 5.5, 'str', [1, 2, 3]]]
            self.env.assertEquals(actual_result.result_set, expected_result)

    # Verify multiple edges of the same relation between nodes A and B
    # are saved and restored correctly.
    def test04_repeated_edges(self):
        graph_names = ["repeated_edges", "{tag}_repeated_edges"]
        for graph_name in graph_names:
            g = Graph(graph_name, redis_con)
            src = Node(label='p', properties={'name': 'src'})
            dest = Node(label='p', properties={'name': 'dest'})
            edge1 = Edge(src, 'e', dest, properties={'val': 1})
            edge2 = Edge(src, 'e', dest, properties={'val': 2})
            g.add_node(src)
            g.add_node(dest)
            g.add_edge(edge1)
            g.add_edge(edge2)
            g.flush()

            # Verify the new edge
            q = """MATCH (a)-[e]->(b) RETURN e.val, a.name, b.name ORDER BY e.val"""
            actual_result = g.query(q)

            expected_result = [[
                edge1.properties['val'], src.properties['name'],
                dest.properties['name']
            ],
                               [
                                   edge2.properties['val'],
                                   src.properties['name'],
                                   dest.properties['name']
                               ]]

            self.env.assertEquals(actual_result.result_set, expected_result)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            # Verify that the latest edge was properly saved and loaded
            actual_result = g.query(q)
            self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 13
0
class testQueryValidationFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_con
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        # Create a single graph.
        global redis_graph
        node = Node(properties={"age": 34})
        redis_graph.add_node(node)
        redis_graph.commit()

    # Expect an error when trying to use a function which does not exists.
    def test01_none_existing_function(self):
        query = """MATCH (n) RETURN noneExistingFunc(n.age) AS cast"""
        try:
            redis_graph.query(query)
            self.env.assertTrue(False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    # Make sure function validation is type case insensitive.
    def test02_case_insensitive_function_name(self):
        try:
            query = """MATCH (n) RETURN mAx(n.age)"""
            redis_graph.query(query)
        except redis.exceptions.ResponseError:
            # function validation should be case insensitive.
            self.env.assertTrue(False)

    def test03_edge_missing_relation_type(self):
        try:
            query = """CREATE (n:Person {age:32})-[]->(:person {age:30})"""
            redis_graph.query(query)
            self.env.assertTrue(False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test04_escaped_quotes(self):
        query = r"CREATE (:escaped{prop1:'single \' char', prop2: 'double \" char', prop3: 'mixed \' and \" chars'})"
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_created, 1)
        self.env.assertEquals(actual_result.properties_set, 3)

        query = r"MATCH (a:escaped) RETURN a.prop1, a.prop2, a.prop3"
        actual_result = redis_graph.query(query)
        expected_result = [[
            "single ' char", 'double " char', 'mixed \' and " chars'
        ]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test05_invalid_entity_references(self):
        try:
            query = """MATCH (a) RETURN e"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

        try:
            query = """MATCH (a) RETURN a ORDER BY e"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test06_where_references(self):
        try:
            query = """MATCH (a) WHERE fake = true RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test07_with_references(self):
        try:
            query = """MATCH (a) WITH e RETURN e"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test08_count_distinct_star(self):
        try:
            query = """MATCH (a) RETURN COUNT(DISTINCT *)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test09_invalid_apply_all(self):
        try:
            query = """MATCH (a) RETURN SUM(*)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test10_missing_params(self):
        try:
            query = """MATCH (a {name:$name}) RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test11_param_error(self):
        try:
            query = """CYPHER name=({name:'a'}) MATCH (a {name:$name}) RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test12_invalid_query_order(self):
        try:
            query = """MERGE (a) MATCH (a)-[]->(b) RETURN b"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test13_create_bound_variables(self):
        try:
            query = """MATCH (a)-[e]->(b) CREATE (a)-[e]->(b)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test14_treat_path_as_entity(self):
        redis_graph.query("CREATE ()-[:R]->()")
        try:
            query = """MATCH x=()-[]->() RETURN x.name"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test15_dont_crash_on_multiple_errors(self):
        try:
            query = """MATCH (a) where id(a) IN range(0) OR id(a) in range(1)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    # Run a query in which a parsed parameter introduces a type in an unsupported context.
    def test16_param_introduces_unhandled_type(self):
        try:
            query = """CYPHER props={a:1,b:2} CREATE (a:A $props)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Encountered unhandled type" in str(e))
            pass

    # Validate that the module fails properly with incorrect argument counts.
    def test17_query_arity(self):
        # Call GRAPH.QUERY with a missing query argument.
        try:
            res = redis_con.execute_command("GRAPH.QUERY", "G")
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("wrong number of arguments" in str(e))
            pass

    # Run queries in which compile-time variables are accessed but not defined.
    def test18_undefined_variable_access(self):
        try:
            query = """CREATE (:person{name:bar[1]})"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

        try:
            query = """MATCH (a {val: undeclared}) RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

        try:
            query = """UNWIND [fake] AS ref RETURN ref"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

    def test19_invalid_cypher_options(self):
        query = "EXPLAIN MATCH (p:president)-[:born]->(:state {name:'Hawaii'}) RETURN p"
        try:
            redis_graph.query(query)
            assert (False)
        except:
            # Expecting an error.
            pass

        query = "PROFILE MATCH (p:president)-[:born]->(:state {name:'Hawaii'}) RETURN p"
        try:
            redis_graph.query(query)
            assert (False)
        except:
            # Expecting an error.
            pass

        query = "CYPHER val=1 EXPLAIN MATCH (p:president)-[:born]->(:state {name:'Hawaii'}) RETURN p"
        try:
            redis_graph.query(query)
            assert (False)
        except:
            # Expecting an error.
            pass

        query = "CYPHER val=1 PROFILE MATCH (p:president)-[:born]->(:state {name:'Hawaii'}) RETURN p"
        try:
            redis_graph.query(query)
            assert (False)
        except:
            # Expecting an error.
            pass

    # Undirected edges are not allowed in CREATE clauses.
    def test20_undirected_edge_creation(self):
        try:
            query = """CREATE (:Endpoint)-[:R]-(:Endpoint)"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Only directed relationships" in str(e))
            pass

    # Applying a filter for non existing entity.
    def test20_non_existing_graph_entity(self):
        try:
            query = """MATCH p=() WHERE p.name='value' RETURN p"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Type mismatch: expected Node but was Path" in str(e))
            pass

    # Comments should not affect query functionality.
    def test21_ignore_query_comments(self):
        query = """MATCH (n)  // This is a comment
                   /* This is a block comment */
                   WHERE EXISTS(n.age)
                   RETURN n.age /* Also a block comment*/"""
        actual_result = redis_graph.query(query)
        expected_result = [[34]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """/* A block comment*/ MATCH (n)  // This is a comment
                /* This is a block comment */
                WHERE EXISTS(n.age)
                RETURN n.age /* Also a block comment*/"""
        actual_result = redis_graph.query(query)
        expected_result = [[34]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """// This is a comment
                MATCH (n)  // This is a comment
                /* This is a block comment */
                WHERE EXISTS(n.age)
                RETURN n.age /* Also a block comment*/"""
        actual_result = redis_graph.query(query)
        expected_result = [[34]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """MATCH (n)  /* This is a block comment */ WHERE EXISTS(n.age)
                RETURN n.age /* Also a block comment*/"""
        actual_result = redis_graph.query(query)
        expected_result = [[34]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Validate procedure call refrences and definitions
    def test22_procedure_validations(self):
        try:
            # procedure call refering to a none existing alias 'n'
            query = """CALL db.idx.fulltext.queryNodes(n, 'B') YIELD node RETURN node"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

        # refer to procedure call original output when output is aliased.
        try:
            query = """CALL db.idx.fulltext.queryNodes('A', 'B') YIELD node AS n RETURN node"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

        # valid procedure call, no output aliasing
        query = """CALL db.idx.fulltext.queryNodes('A', 'B') YIELD node RETURN node"""
        redis_graph.query(query)

        # valid procedure call, output aliasing
        query = """CALL db.idx.fulltext.queryNodes('A', 'B') YIELD node AS n RETURN n"""
        redis_graph.query(query)

    # Applying a filter for a non-boolean constant should raise a compile-time error.
    def test23_invalid_constant_filter(self):
        try:
            query = """MATCH (a) WHERE 1 RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            assert ("Expected boolean predicate" in str(e))
            pass

    # Referencing a variable before defining it should raise a compile-time error.
    def test24_reference_before_definition(self):
        try:
            query = """MATCH ({prop: reference}) MATCH (reference) RETURN *"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("not defined" in str(e))
            pass

    # Invalid filters in cartesian products should raise errors.
    def test25_cartesian_product_invalid_filter(self):
        try:
            query = """MATCH p1=(), (n), ({prop: p1.path_val}) RETURN *"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Type mismatch: expected Node but was Path" in str(e))
            pass

    # Scalar predicates in filters should raise errors.
    def test26_invalid_filter_predicate(self):
        try:
            query = """WITH 1 AS a WHERE '' RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Expected boolean predicate" in str(e))
            pass

    # Conditional filters with non-boolean scalar predicate children should raise errors.
    def test27_invalid_filter_predicate_child(self):
        try:
            # 'Amor' is an invalid construct for the RHS of 'OR'.
            query = """MATCH (a:Author) WHERE a.name CONTAINS 'Ernest' OR 'Amor' RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Expected boolean predicate" in str(e))
            pass

    # The NOT operator does not compare left and right side expressions.
    def test28_invalid_filter_binary_not(self):
        try:
            # Query should have been:
            # MATCH (u) where u.v IS NOT NULL RETURN u
            query = """MATCH (u) where u.v NOT NULL RETURN u"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Invalid usage of 'NOT' filter" in str(e))
            pass

    def test29_invalid_filter_non_boolean_constant(self):
        try:
            query = """MATCH (a) WHERE a RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            assert ("expected Boolean but was Node" in str(e))
            pass

        try:
            query = """MATCH (a) WHERE 1+rand() RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            assert ("expected Boolean but was Float" in str(e))
            pass

        try:
            query = """CYPHER p=3 WITH 1 AS a WHERE $p RETURN a"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            assert ("expected Boolean but was Integer" in str(e))
            pass

        # 'val' is a boolean, so this query is valid.
        query = """WITH true AS val WHERE val return val"""
        redis_graph.query(query)

        # Non-existent properties are treated as NULLs, which are boolean in Cypher's 3-valued logic.
        query = """MATCH (a) WHERE a.fakeprop RETURN a"""
        redis_graph.query(query)

    # Encountering traversals as property values or ORDER BY expressions should raise compile-time errors.
    def test30_unexpected_traversals(self):
        queries = [
            """MATCH (a {prop: ()-[]->()}) RETURN a""",
            """MATCH (a) RETURN a ORDER BY (a)-[]->()""",
            """MATCH (a) RETURN (a)-[]->()"""
        ]
        for query in queries:
            try:
                redis_graph.query(query)
                assert (False)
            except redis.exceptions.ResponseError as e:
                # Expecting an error.
                assert ("Encountered path traversal" in str(e))

    def test31_set_invalid_property_type(self):
        # Skip this test if running under Valgrind, as it causes a memory leak.
        if self.env.envRunner.debugger is not None:
            self.env.skip()

        queries = [
            """MATCH (a) CREATE (:L {v: a})""",
            """MATCH (a), (b) WHERE b.age IS NOT NULL SET b.age = a""",
            """MERGE (a) ON MATCH SET a.age = a"""
        ]
        for q in queries:
            try:
                redis_graph.query(q)
                assert (False)
            except redis.exceptions.ResponseError as e:
                # Expecting an error.
                assert ("Property values can only be of primitive types"
                        in str(e))
                pass

    def test32_return_following_clauses(self):
        # After a RETURN clause we're expecting only the following clauses:
        # SKIP, LIMIT, ORDER-BY and UNION, given that SKIP and LIMIT are
        # actually attributes of the RETURN clause this leaves us with
        # ORDER-BY and UNION.

        invalid_queries = [
            """RETURN 1 CREATE ()""", """RETURN 1 RETURN 2""",
            """MATCH(n) RETURN n DELETE n""",
            """MATCH(n) RETURN n SET n.v = 1""", """RETURN 1 MERGE ()""",
            """RETURN 1 MATCH (n) RETURN n""",
            """RETURN 1 WITH 1 as one RETURN one"""
        ]

        # Invalid queries, expecting errors.
        for q in invalid_queries:
            try:
                redis_graph.query(q)
                assert (False)
            except redis.exceptions.ResponseError as e:
                # Expecting an error.
                assert ("Unexpected clause following RETURN" in str(e))
                pass

    # Parameters cannot reference aliases.
    def test33_alias_reference_in_param(self):
        try:
            query = """CYPHER A=[a] RETURN 5"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            assert ("Attempted to access variable" in str(e))
            pass

    def test34_self_referential_properties(self):
        # Skip this test if running under Valgrind, as it causes a memory leak.
        if self.env.envRunner.debugger is not None:
            self.env.skip()

        try:
            # The server should emit an error on trying to create a node with a self-referential property.
            query = """CREATE (a:L {v: a.v})"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting an error.
            self.env.assertIn("undefined property", str(e))

        # MATCH clauses should be able to use self-referential properties as existential filters.
        query = """MATCH (a {age: a.age}) RETURN a.age"""
        actual_result = redis_graph.query(query)
        expected_result = [[34]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test a query that allocates a large buffer.
    def test35_large_query(self):
        retval = "abcdef" * 1_000
        query = "RETURN " + "\"" + retval + "\""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.result_set[0][0], retval)

    def test36_multiple_proc_calls(self):
        query = """MATCH (a)
                   CALL algo.BFS(a, 3, NULL) YIELD nodes as ns1
                   MATCH (b)
                   CALL algo.BFS(b, 3, NULL) YIELD nodes as ns2
                   RETURN ns1"""
        plan = redis_graph.execution_plan(query)
        self.env.assertTrue(plan.count("ProcedureCall") == 2)

    def test37_list_comprehension_missuse(self):
        # all expect list comprehension,
        # unfortunately this isn't enforced by the parser
        # as such it is possible for a user miss-use this function
        # and our current arithmetic expression construction logic will
        # construct a malformed function call

        # make sure we're reciving an exception for each miss-use query
        queries = [
            "WITH 1 AS x RETURN all(x > 2)", "WITH 1 AS x RETURN all([1],2,3)"
        ]

        for q in queries:
            try:
                redis_graph.query(q)
                assert (False)
            except redis.exceptions.ResponseError as e:
                pass
Exemplo n.º 14
0
class testIndexScanFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)

    def setUp(self):
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(social_utils.graph_name, redis_con)
        social_utils.populate_graph(redis_con, redis_graph)
        self.build_indices()

    def tearDown(self):
        self.env.cmd('flushall')

    def build_indices(self):
        global redis_graph
        redis_graph.query("CREATE INDEX ON :person(age)")
        redis_graph.query("CREATE INDEX ON :country(name)")

    # Validate that Cartesian products using index and label scans succeed
    def test01_cartesian_product_mixed_scans(self):
        query = "MATCH (p:person), (c:country) WHERE p.age > 0 RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH (p:person), (c:country) RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Node By Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set, unindexed_result.result_set)

    # Validate that Cartesian products using just index scans succeed
    def test02_cartesian_product_index_scans_only(self):
        query = "MATCH (p:person), (c:country) WHERE p.age > 0 AND c.name > '' RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Node By Index Scan'), 2)
        self.env.assertNotIn('Label Scan', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH (p:person), (c:country) RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Node By Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set, unindexed_result.result_set)

    # Validate that the appropriate bounds are respected when a Cartesian product uses the same index in two streams
    def test03_cartesian_product_reused_index(self):
        redis_graph.query("CREATE INDEX ON :person(name)")
        query = "MATCH (a:person {name: 'Omri Traub'}), (b:person) WHERE b.age <= 30 RETURN a.name, b.name ORDER BY a.name, b.name"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Node By Index Scan'), 2)
        self.env.assertNotIn('Label Scan', plan)


        expected_result = [['Omri Traub', 'Gal Derriere'],
                           ['Omri Traub', 'Lucy Yanfital']]
        result = redis_graph.query(query)

        self.env.assertEquals(result.result_set, expected_result)

    # Validate index utilization when filtering on a numeric field with the `IN` keyword.
    def test04_test_in_operator_numerics(self):
        # Validate the transformation of IN to multiple OR expressions.
        query = "MATCH (p:person) WHERE p.age IN [1,2,3] RETURN p"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        # Validate that nested arrays are not scanned in index.
        query = "MATCH (p:person) WHERE p.age IN [[1,2],3] RETURN p"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Node By Index Scan', plan)
        self.env.assertIn('Label Scan', plan)

        # Validate the transformation of IN to multiple OR, over a range.
        query = "MATCH (p:person) WHERE p.age IN range(0,30) RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

         # Validate the transformation of IN to empty index iterator.
        query = "MATCH (p:person) WHERE p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of IN OR IN to empty index iterators.
        query = "MATCH (p:person) WHERE p.age IN [] OR p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (p:person) WHERE p.age IN [26, 27, 30] OR p.age IN [33, 34, 35] RETURN p.name ORDER BY p.age"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub'], ['Noam Nativ']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (p:person) WHERE p.age IN [26, 27, 30] OR p.age IN [33, 34, 35] OR p.age IN [] RETURN p.name ORDER BY p.age"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub'], ['Noam Nativ']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

    # Validate index utilization when filtering on string fields with the `IN` keyword.
    def test05_test_in_operator_string_props(self):
        # Build an index on the name property.
        redis_graph.query("CREATE INDEX ON :person(name)")
        # Validate the transformation of IN to multiple OR expressions over string properties.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Combine numeric and string filters specified by IN.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] AND p.age in [30] RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

         # Validate an empty index on IN with multiple indexes
        query = "MATCH (p:person) WHERE p.name IN [] OR p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Combine IN filters with other relational filters.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] AND p.name < 'H' RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] OR p.age = 33 RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub']]
        result = redis_graph.query(query)
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

    # ',' is the default separator for tag indices
    # we've updated our separator to '\0' this test verifies issue 696:
    # https://github.com/RedisGraph/RedisGraph/issues/696
    def test06_tag_separator(self):
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)

        # Create a single node with a long string property, introduce a comma as part of the string.
        query = """CREATE (:Node{value:"A ValuePartition is a pattern that describes a restricted set of classes from which a property can be associated. The parent class is used in restrictions, and the covering axiom means that only members of the subclasses may be used as values."})"""
        redis_graph.query(query)

        # Index property.
        query = """CREATE INDEX ON :Node(value)"""
        redis_graph.query(query)

        # Make sure node is returned by index scan.
        query = """MATCH (a:Node{value:"A ValuePartition is a pattern that describes a restricted set of classes from which a property can be associated. The parent class is used in restrictions, and the covering axiom means that only members of the subclasses may be used as values."}) RETURN a"""
        plan = redis_graph.execution_plan(query)
        result_set = redis_graph.query(query).result_set
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertEqual(len(result_set), 1)

    def test07_index_scan_and_id(self):
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        nodes=[]
        for i in range(10):
            node = Node(node_id=i, label='person', properties={'age':i})
            nodes.append(node)
            redis_graph.add_node(node)
            redis_graph.flush()
        
        query = """CREATE INDEX ON :person(age)"""
        query_result = redis_graph.query(query)
        self.env.assertEqual(1, query_result.indices_created)

        query = """MATCH (n:person) WHERE id(n)>=7 AND n.age<9 RETURN n ORDER BY n.age"""
        plan = redis_graph.execution_plan(query)
        query_result = redis_graph.query(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)

        self.env.assertEqual(2, len(query_result.result_set))
        expected_result = [[nodes[7]], [nodes[8]]]
        self.env.assertEquals(expected_result, query_result.result_set)

    # Validate placement of index scans and filter ops when not all filters can be replaced.
    def test08_index_scan_multiple_filters(self):
        query = "MATCH (p:person) WHERE p.age = 30 AND NOT EXISTS(p.fakeprop) RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Node By Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)
        self.env.assertIn('Filter', plan)

        query_result = redis_graph.query(query)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test09_index_scan_with_params(self):
        query = "MATCH (p:person) WHERE p.age = $age RETURN p.name"
        params = {'age': 30}
        plan = redis_graph.execution_plan(query, params=params)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(query, params=params)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test10_index_scan_with_param_array(self):
        query = "MATCH (p:person) WHERE p.age in $ages RETURN p.name"
        params = {'ages': [30]}
        plan = redis_graph.execution_plan(query, params=params)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(query, params=params)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test11_single_index_multiple_scans(self):
        query = "MERGE (p1:person {age: 40}) MERGE (p2:person {age: 41})"
        plan = redis_graph.execution_plan(query)
        # Two index scans should be performed.
        self.env.assertEqual(plan.count("Node By Index Scan"), 2)

        query_result = redis_graph.query(query)
        # Two new nodes should be created.
        self.env.assertEquals(query_result.nodes_created, 2)

    def test12_remove_scans_before_index(self):
        query = "MATCH (a:person {age: 32})-[]->(b) WHERE (b:person)-[]->(a) RETURN a"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Node By Index Scan"), 1)

    def test13_point_index_scan(self):
        # create index
        q = "CREATE INDEX ON :restaurant(location)"
        redis_graph.query(q)

        # create restaurant
        q = "CREATE (:restaurant {location: point({latitude:30.27822306, longitude:-97.75134723})})"
        redis_graph.query(q)

        # locate other restaurants within a 1000m radius
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) < 1000
        RETURN r"""

        # make sure index is used
        plan = redis_graph.execution_plan(q)
        self.env.assertIn("Node By Index Scan", plan)

        # refine query from '<' to '<='
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) <= 1000
        RETURN r"""

        # make sure index is used
        plan = redis_graph.execution_plan(q)
        self.env.assertIn("Node By Index Scan", plan)

        # index should NOT be used when searching for points outside of a circle
        # testing operand: '>', '>=' and '='
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) > 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Node By Index Scan", plan)

        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) >= 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Node By Index Scan", plan)

        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) = 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Node By Index Scan", plan)

    def test14_index_scan_utilize_array(self):
        # Querying indexed properties using IN a constant array should utilize indexes.
        query = "MATCH (a:person) WHERE a.age IN [34, 33] RETURN a.name ORDER BY a.name"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Node By Index Scan"), 1)
        query_result = redis_graph.query(query)
        expected_result = [["Noam Nativ"],
                           ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # Querying indexed properties using IN a generated array should utilize indexes.
        query = "MATCH (a:person) WHERE a.age IN range(33, 34) RETURN a.name ORDER BY a.name"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Node By Index Scan"), 1)
        query_result = redis_graph.query(query)
        expected_result = [["Noam Nativ"],
                           ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # Querying indexed properties using IN a non-constant array should not utilize indexes.
        query = "MATCH (a:person)-[]->(b) WHERE a.age IN b.arr RETURN a"
        plan = redis_graph.execution_plan(query)
        # No index scans should be performed.
        self.env.assertEqual(plan.count("Label Scan"), 1)
        self.env.assertEqual(plan.count("Node By Index Scan"), 0)

    # Test fulltext result scoring
    def test15_fulltext_result_scoring(self):
        g = Graph('fulltext_scoring', self.env.getConnection())

        # create full-text index over label 'L', attribute 'v'
        g.call_procedure('db.idx.fulltext.createNodeIndex', 'L', 'v')

        # introduce 2 nodes
        g.query("create (:L {v:'hello world hello'})")
        g.query("create (:L {v:'hello world hello world'})")

        # query nodes using fulltext search
        q = """CALL db.idx.fulltext.queryNodes('L', 'hello world') YIELD node, score
               RETURN node.v, score
               ORDER BY score"""
        res = g.query(q)
        actual = res.result_set
        expected = [['hello world hello', 1.5], ['hello world hello world', 2]]
        self.env.assertEqual(expected, actual)

    def test16_runtime_index_utilization(self):
        # find all person nodes with age in the range 33-37
        # current age (x) should be resolved at runtime
        # index query should be constructed for each age value
        q = """UNWIND range(33, 37) AS x
        MATCH (p:person {age:x})
        RETURN p.name
        ORDER BY p.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Noam Nativ"], ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # similar to the query above, only this time the filter is specified
        # by an OR condition
        q = """WITH 33 AS min, 34 AS max 
        MATCH (p:person)
        WHERE p.age = min OR p.age = max
        RETURN p.name
        ORDER BY p.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Noam Nativ"], ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes with age equals 33 'x'
        # 'x' value is known only at runtime
        q = """WITH 33 AS x
        MATCH (p:person {age:x})
        RETURN p.name
        ORDER BY p.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes with age equals x + 1
        # the expression x+1 is evaluated to the constant 33 only at runtime
        # expecting index query to be constructed at runtime
        q = """WITH 32 AS x
        MATCH (p:person)
        WHERE p.age = (x + 1)
        RETURN p.name
        ORDER BY p.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # same idea as previous query only we've switched the position of the
        # operands, queried entity (p.age) is now on the right hand side of the
        # filter, expecting the same behavior
        q = """WITH 32 AS x
        MATCH (p:person)
        WHERE (x + 1) = p.age
        RETURN p.name
        ORDER BY p.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes 'b' with age greater than node 'a'
        # a's age value is determined only at runtime
        # expecting index to be used to resolve 'b' nodes, index query should be
        # constructed at runtime
        q = """MATCH (a:person {name:'Omri Traub'})
        WITH a AS a
        MATCH (b:person)
        WHERE b.age > a.age
        RETURN b.name
        ORDER BY b.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Noam Nativ"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # same idea as previous query, only this time we've switched filter
        # operands position, queries entity is on the right hand side
        q = """MATCH (a:person {name: 'Omri Traub'})
        WITH a AS a
        MATCH (b:person)
        WHERE a.age < b.age
        RETURN b.name
        ORDER BY b.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["Noam Nativ"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # TODO: The following query uses the "Value Hash Join" where it would be
        # better to use "Index Scan"
        q = """UNWIND range(33, 37) AS x MATCH (a:person {age:x}), (b:person {age:x}) RETURN a.name, b.name ORDER BY a.name, b.name"""

    def test17_runtime_index_utilization_array_values(self):
        # when constructing an index query at runtime it is possible to encounter
        # none indexable values e.g. Array, in which case the index will still be
        # utilize, producing every entity which was indexed with a none indexable value
        # to which the index scan operation will have to apply the original filter

        # create person nodes with array value for their 'age' attribute
        q = """CREATE (:person {age:[36], name:'leonard'}), (:person {age:[34], name:['maynard']})"""
        redis_graph.query(q)

        # find all person nodes with age value of [36]
        q = """WITH [36] AS age MATCH (a:person {age:age}) RETURN a.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["leonard"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # find all person nodes with age > [33]
        q = """WITH [33] AS age MATCH (a:person) WHERE a.age > age RETURN a.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["leonard"], [["maynard"]]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # combine indexable value with none-indexable value index query
        q = """WITH [33] AS age, 'leonard' AS name MATCH (a:person) WHERE a.age >= age AND a.name = name RETURN a.name"""
        plan = redis_graph.execution_plan(q)
        self.env.assertIn('Node By Index Scan', plan)
        query_result = redis_graph.query(q)
        expected_result = [["leonard"]]
        self.env.assertEquals(query_result.result_set, expected_result)

    # test for https://github.com/RedisGraph/RedisGraph/issues/1980
    def test18_index_scan_inside_apply(self):
        redis_graph = Graph('g', self.env.getConnection())

        redis_graph.query("CREATE INDEX ON :L1(id)")
        redis_graph.query("UNWIND range(1, 5) AS v CREATE (:L1 {id: v})")
        result = redis_graph.query("UNWIND range(1, 5) AS id OPTIONAL MATCH (u:L1{id: 5}) RETURN u.id")

        expected_result = [[5], [5], [5], [5], [5]]
        self.env.assertEquals(result.result_set, expected_result)
Exemplo n.º 15
0
class testGraphMultiPatternQueryFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph

        nodes = {}
        # Create entities
        for p in people:
            node = Node(label="person", properties={"name": p})
            redis_graph.add_node(node)
            nodes[p] = node

        redis_graph.commit()

    # Connect a single node to all other nodes.
    def test01_connect_node_to_rest(self):
        query = """MATCH(r:person {name:"Roi"}), (f:person) WHERE f.name <> r.name CREATE (r)-[:friend]->(f) RETURN count(f)"""
        actual_result = redis_graph.query(query)
        friend_count = actual_result.result_set[0][0]
        self.env.assertEquals(friend_count, 6)
        self.env.assertEquals(actual_result.relationships_created, 6)

    def test02_verify_cartesian_product_streams_reset(self):
        # See https://github.com/RedisGraph/RedisGraph/issues/249
        # Forevery outgoing edge, we expect len(people) to be matched.
        expected_resultset_size = 6 * len(people)
        queries = [
            """MATCH (r:person {name:"Roi"})-[]->(f), (x) RETURN f, x""",
            """MATCH (x), (r:person {name:"Roi"})-[]->(f) RETURN f, x""",
            """MATCH (r:person {name:"Roi"})-[]->(f) MATCH (x) RETURN f, x""",
            """MATCH (x) MATCH (r:person {name:"Roi"})-[]->(f) RETURN f, x"""
        ]
        for q in queries:
            actual_result = redis_graph.query(q)
            records_count = len(actual_result.result_set)
            self.env.assertEquals(records_count, expected_resultset_size)

    # Connect every node to every node.
    def test03_create_fully_connected_graph(self):
        query = """MATCH(a:person), (b:person) WHERE a.name <> b.name CREATE (a)-[f:friend]->(b) RETURN count(f)"""
        actual_result = redis_graph.query(query)
        friend_count = actual_result.result_set[0][0]
        self.env.assertEquals(friend_count, 42)
        self.env.assertEquals(actual_result.relationships_created, 42)

    # Perform a cartesian product of 3 sets.
    def test04_cartesian_product(self):
        queries = [
            """MATCH (a), (b), (c) RETURN count(a)""",
            """MATCH (a) MATCH (b), (c) RETURN count(a)""",
            """MATCH (a), (b) MATCH (c) RETURN count(a)""",
            """MATCH (a) MATCH (b) MATCH (c) RETURN count(a)"""
        ]

        for q in queries:
            actual_result = redis_graph.query(q)
            friend_count = actual_result.result_set[0][0]
            self.env.assertEquals(friend_count, 343)

    def test06_multiple_create_clauses(self):
        queries = [
            """CREATE (:a {v:1}), (:b {v:2, z:3}), (:c), (:a)-[:r0 {k:9}]->(:b), (:c)-[:r1]->(:d)""",
            """CREATE (:a {v:1}) CREATE (:b {v:2, z:3}) CREATE (:c) CREATE (:a)-[:r0 {k:9}]->(:b) CREATE (:c)-[:r1]->(:d)""",
            """CREATE (:a {v:1}), (:b {v:2, z:3}) CREATE (:c), (:a)-[:r0 {k:9}]->(:b) CREATE (:c)-[:r1]->(:d)"""
        ]
        for q in queries:
            actual_result = redis_graph.query(q)
            self.env.assertEquals(actual_result.relationships_created, 2)
            self.env.assertEquals(actual_result.properties_set, 4)
            self.env.assertEquals(actual_result.nodes_created, 7)
Exemplo n.º 16
0
class testBidirectionalTraversals(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_con
        redis_con = self.env.getConnection()
        self.populate_acyclic_graph()
        self.populate_cyclic_graph()

    def populate_acyclic_graph(self):
        global acyclic_graph
        acyclic_graph = Graph("G", redis_con)
        # Construct a graph with the form:
        # (v1)-[:E]->(v2)-[:E]->(v3)
        node_props = ['v1', 'v2', 'v3']

        nodes = []
        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"val": v})
            nodes.append(node)
            acyclic_graph.add_node(node)

        edge = Edge(nodes[0], "E", nodes[1])
        acyclic_graph.add_edge(edge)

        edge = Edge(nodes[1], "E", nodes[2])
        acyclic_graph.add_edge(edge)

        acyclic_graph.commit()

    def populate_cyclic_graph(self):
        global graph_with_cycle
        graph_with_cycle = Graph("H", redis_con)
        # Construct a graph with the form:
        # (v1)-[:E]->(v2)-[:E]->(v3), (v2)-[:E]->(v1)
        node_props = ['v1', 'v2', 'v3']

        nodes = []
        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"val": v})
            nodes.append(node)
            graph_with_cycle.add_node(node)

        edge = Edge(nodes[0], "E", nodes[1])
        graph_with_cycle.add_edge(edge)

        edge = Edge(nodes[1], "E", nodes[2])
        graph_with_cycle.add_edge(edge)

        # Introduce a cycle between v2 and v1.
        edge = Edge(nodes[1], "E", nodes[0])
        graph_with_cycle.add_edge(edge)

        graph_with_cycle.commit()

    # Test traversals that don't specify an edge direction.
    def test01_bidirectional_traversals(self):
        query = """MATCH (a)-[:E]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        # Each relation should appear twice with the source and destination swapped in the second result.
        expected_result = [['v1', 'v2'], ['v2', 'v1'], ['v2', 'v3'],
                           ['v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Test undirected traversals with a referenced edge.
        query = """MATCH (a)-[e:E]-(b) RETURN ID(e), a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        expected_result = [[0, 'v1', 'v2'], [0, 'v2', 'v1'], [1, 'v2', 'v3'],
                           [1, 'v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test 0-hop undirected traversals.
    def test02_bidirectional_zero_hop_traversals(self):
        query = """MATCH (a)-[*0]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        expected_result = [['v1', 'v1'], ['v2', 'v2'], ['v3', 'v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # TODO doesn't work - returns each node with itself as source and destination in adition to expected results.
        # Test combinations of directed and undirected traversals.
        #  query = """MATCH (a)-[:E]->()-[]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        #  actual_result = acyclic_graph.query(query)
        #  expected_result = [['v1', 'v3']]
        #  self.env.assertEquals(actual_result.result_set, expected_result)

        # TODO doesn't work for the same reason.
        # Test fixed-length multi-hop undirected traversals.
        #  query = """MATCH (a)-[:E*2]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        #  actual_result = acyclic_graph.query(query)
        #  expected_result = [[0, 'v1', 'v3'],
        #  [0, 'v3', 'v1']]
        #  self.env.assertEquals(actual_result.result_set, expected_result)

    # Test variable-length traversals that don't specify an edge direction.
    def test03_bidirectional_variable_length_traversals(self):
        query = """MATCH (a)-[*]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        # Each combination of distinct node source and destination should appear once.
        expected_result = [['v1', 'v2'], ['v1', 'v3'], ['v2', 'v1'],
                           ['v2', 'v3'], ['v3', 'v1'], ['v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Should generate the same results as the previous query.
        query = """MATCH (a)-[*1..2]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test collecting self and all direct neighbors.
    def test04_bidirectional_variable_bounded_length_traversals(self):
        query = """MATCH (a)-[*0..1]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        # Each combination of distinct node source and destination should appear once.
        expected_result = [['v1', 'v1'], ['v1', 'v2'], ['v2', 'v1'],
                           ['v2', 'v2'], ['v2', 'v3'], ['v3', 'v2'],
                           ['v3', 'v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test bidirectional query on nonexistent edge.
    def test05_bidirectional_variable_length_traversals_over_nonexistent_type(
            self):
        query = """MATCH (a)-[:NONEXISTENT*]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        expected_result = []
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test bidirectional query on real edge or nonexistent edge.
    def test06_bidirectional_variable_length_traversals_over_partial_existing_types(
            self):
        query = """MATCH (a)-[:NONEXISTENT|:E*]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        # Each combination of distinct node source and destination should appear once.
        expected_result = [['v1', 'v2'], ['v1', 'v3'], ['v2', 'v1'],
                           ['v2', 'v3'], ['v3', 'v1'], ['v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # TODO returns 16 rows; 18 rows expected.
    # The missing two rows are both `['v2', 'v3']
    # Test bidirectional query on two real edge types.
    #  def test07_bidirectional_variable_length_traversals_over_multiple_existing_types(self):
    #  # Generate new dest->src edges between every current src->dest pair.
    #  query = """MATCH (a {val: 'v1'})-[e]->(b {val: 'v2'}) CREATE (a)-[:CLONE]->(b)"""
    #  actual_result = acyclic_graph.query(query)
    #  self.env.assertEquals(actual_result.relationships_created, 1)

    #  query = """MATCH (a)-[:E|:CLONE*]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
    #  actual_result = acyclic_graph.query(query)
    #  expected_result = [['v1', 'v1'],
    #  ['v1', 'v1'],
    #  ['v1', 'v2'],
    #  ['v1', 'v2'],
    #  ['v1', 'v3'],
    #  ['v1', 'v3'],
    #  ['v2', 'v1'],
    #  ['v2', 'v1'],
    #  ['v2', 'v2'],
    #  ['v2', 'v2'],
    #  ['v2', 'v3'],
    #  ['v2', 'v3'],
    #  ['v2', 'v3'],
    #  ['v3', 'v1'],
    #  ['v3', 'v1'],
    #  ['v3', 'v2'],
    #  ['v3', 'v2'],
    #  ['v3', 'v2']]
    #  self.env.assertEquals(actual_result.result_set, expected_result)

    # Test bidirectional query on two real edge types.
    def test08_bidirectional_variable_bounded_length_traversals_over_multiple_existing_types(
            self):
        # Generate one new edge between v1 and v2.
        query = """MATCH (a {val: 'v1'})-[e]->(b {val: 'v2'}) CREATE (a)-[:CLONE]->(b)"""
        actual_result = acyclic_graph.query(query)
        self.env.assertEquals(actual_result.relationships_created, 1)

        query = """MATCH (a)-[:E|:CLONE*1..2]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = acyclic_graph.query(query)
        expected_result = [['v1', 'v1'], ['v1', 'v1'], ['v1', 'v2'],
                           ['v1', 'v2'], ['v1', 'v3'], ['v1', 'v3'],
                           ['v2', 'v1'], ['v2', 'v1'], ['v2', 'v2'],
                           ['v2', 'v2'], ['v2', 'v3'], ['v3', 'v1'],
                           ['v3', 'v1'], ['v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Delete cloned edge.
        query = """MATCH ()-[e:CLONE]->() DELETE e"""
        actual_result = acyclic_graph.query(query)
        self.env.assertEquals(actual_result.relationships_deleted, 1)

    # Test traversals that don't specify an edge direction in a graph with a cycle.
    def test09_bidirectional_traversals_with_cycle(self):
        # Test undirected traversals with a referenced edge.
        # TODO The variant query in which the edge is not referenced does not work:
        #  query = """MATCH (a)-[:E]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        query = """MATCH (a)-[e:E]-(b) RETURN ID(e) AS id, a.val, b.val ORDER BY id, a.val, b.val"""
        actual_result = graph_with_cycle.query(query)
        # Each relation should appear twice with the source and destination swapped in the second result.
        expected_result = [[0, 'v1', 'v2'], [0, 'v2', 'v1'], [1, 'v2', 'v3'],
                           [1, 'v3', 'v2'], [2, 'v1', 'v2'], [2, 'v2', 'v1']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test variable-length traversals that don't specify an edge direction.
    def test10_bidirectional_variable_length_traversals_with_cycle(self):
        # TODO returns 16 rows; 18 rows expected.
        # The missing two rows are both `['v2', 'v3']
        #  query = """MATCH (a)-[*]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""

        query = """MATCH (a)-[*1..2]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = graph_with_cycle.query(query)
        # Each src/dest pair (including when the source and dest are the same) is returned twice
        # except for (v2)-[]->(v3), which correctly only occurs once as the missing traversal pattern takes 3 hops.
        expected_result = [['v1', 'v1'], ['v1', 'v1'], ['v1', 'v2'],
                           ['v1', 'v2'], ['v1', 'v3'], ['v1', 'v3'],
                           ['v2', 'v1'], ['v2', 'v1'], ['v2', 'v2'],
                           ['v2', 'v2'], ['v2', 'v3'], ['v3', 'v1'],
                           ['v3', 'v1'], ['v3', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Collect self and all direct neighbors with the pattern (v1)-[]-(v2) repeated.
        query = """MATCH (a)-[*0..1]-(b) RETURN a.val, b.val ORDER BY a.val, b.val"""
        actual_result = graph_with_cycle.query(query)
        expected_result = [['v1', 'v1'], ['v1', 'v2'], ['v1', 'v2'],
                           ['v2', 'v1'], ['v2', 'v1'], ['v2', 'v2'],
                           ['v2', 'v3'], ['v3', 'v2'], ['v3', 'v3']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test11_bidirectional_multiple_edge_type(self):
        # Construct a simple graph:
        # (a)-[E1]->(b), (c)-[E2]->(d)

        g = Graph("multi_edge_type", redis_con)

        a = Node(properties={'val': 'a'})
        b = Node(properties={'val': 'b'})
        c = Node(properties={'val': 'c'})
        d = Node(properties={'val': 'd'})
        g.add_node(a)
        g.add_node(b)
        g.add_node(c)
        g.add_node(d)

        ab = Edge(a, "E1", b)
        cd = Edge(c, "E2", d)
        g.add_edge(ab)
        g.add_edge(cd)

        g.flush()

        query = """MATCH (a)-[:E1|:E2]-(z) RETURN a.val, z.val ORDER BY a.val, z.val"""
        actual_result = g.query(query)

        expected_result = [['a', 'b'], ['b', 'a'], ['c', 'd'], ['d', 'c']]

        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 17
0
class testIndexCreationFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)

    # full-text index creation
    def test01_fulltext_index_creation(self):
        # create an index over L:v0
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v0')")
        self.env.assertEquals(result.indices_created, 1)

        # create an index over L:v1
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v1')")
        self.env.assertEquals(result.indices_created, 1)

        # create an index over L:v1 and L:v2
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v1', 'v2')")
        self.env.assertEquals(result.indices_created, 1)

        # create an index over L:v0, L:v1 and L:v2
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v0', 'v1', 'v2')")
        self.env.assertEquals(result.indices_created, 0)

        # create an index over L:v2, L:v1 and L:v0
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v2', 'v1', 'v0')")
        self.env.assertEquals(result.indices_created, 0)

        # create an index over L:v3 and L:v4
        result = redis_graph.query(
            "CALL db.idx.fulltext.createNodeIndex('L', 'v3', 'v4')")
        self.env.assertEquals(result.indices_created, 2)
Exemplo n.º 18
0
class testGraphMixLabelsFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        redis_graph

        nodes = {}
        # Create entities

        for m in male:
            node = Node(label="male", properties={"name": m})
            redis_graph.add_node(node)
            nodes[m] = node

        for f in female:
            node = Node(label="female", properties={"name": f})
            redis_graph.add_node(node)
            nodes[f] = node

        for n in nodes:
            for m in nodes:
                if n == m: continue
                edge = Edge(nodes[n], "knows", nodes[m])
                redis_graph.add_edge(edge)

        redis_graph.commit()

    # Connect a single node to all other nodes.
    def test_male_to_all(self):
        query = """MATCH (m:male)-[:knows]->(t) RETURN m,t ORDER BY m.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male) * (len(male + female) - 1)))

    def test_male_to_male(self):
        query = """MATCH (m:male)-[:knows]->(t:male) RETURN m,t ORDER BY m.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male) * (len(male) - 1)))

    def test_male_to_female(self):
        query = """MATCH (m:male)-[:knows]->(t:female) RETURN m,t ORDER BY m.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male) * len(female)))

    def test_female_to_all(self):
        query = """MATCH (f:female)-[:knows]->(t) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(female) * (len(male + female) - 1)))

    def test_female_to_male(self):
        query = """MATCH (f:female)-[:knows]->(t:male) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(female) * len(male)))

    def test_female_to_female(self):
        query = """MATCH (f:female)-[:knows]->(t:female) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(female) * (len(female) - 1)))

    def test_all_to_female(self):
        query = """MATCH (f)-[:knows]->(t:female) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male) * len(female)) + (len(female) *
                                                           (len(female) - 1)))

    def test_all_to_male(self):
        query = """MATCH (f)-[:knows]->(t:male) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male) *
                               (len(male) - 1)) + len(female) * len(male))

    def test_all_to_all(self):
        query = """MATCH (f)-[:knows]->(t) RETURN f,t ORDER BY f.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set),
                              (len(male + female) * (len(male + female) - 1)))
Exemplo n.º 19
0
class testVariableLengthTraversals(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_con
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph

        nodes = []
        # Create nodes
        for n in node_names:
            node = Node(label="node", properties={"name": n})
            redis_graph.add_node(node)
            nodes.append(node)

        # Create edges
        for i in range(len(nodes) - 1):
            edge = Edge(
                nodes[i],
                "knows",
                nodes[i + 1],
                properties={"connects": node_names[i] + node_names[i + 1]})
            redis_graph.add_edge(edge)

        redis_graph.commit()

    # Sanity check against single-hop traversal
    def test01_conditional_traverse(self):
        query = """MATCH (a)-[e]->(b) RETURN a.name, e.connects, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        expected_result = [['A', 'AB', 'B'], ['B', 'BC', 'C'],
                           ['C', 'CD', 'D']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Traversal with no labels
    def test02_unlabeled_traverse(self):
        query = """MATCH (a)-[*]->(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

        query = """MATCH (a)<-[*]-(b) RETURN a, b ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

    # Traversal with labeled source
    def test03_source_labeled(self):
        query = """MATCH (a:node)-[*]->(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

        query = """MATCH (a:node)<-[*]-(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

    # Traversal with labeled dest
    def test04_dest_labeled(self):
        query = """MATCH (a)-[*]->(b:node) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

        query = """MATCH (a)<-[*]-(b:node) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), max_results)

    # Attempt to traverse non-existent relationship type.
    def test05_invalid_traversal(self):
        query = """MATCH (a)-[:no_edge*]->(b) RETURN a.name"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 0)

    # Test bidirectional traversal
    def test06_bidirectional_traversal(self):
        query = """MATCH (a)-[*]-(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        # The undirected traversal should represent every combination twice.
        self.env.assertEquals(len(actual_result.result_set), max_results * 2)

    def test07_non_existing_edge_traversal_with_zero_length(self):
        # Verify that zero length traversals always return source, even for non existing edges.
        query = """MATCH (a)-[:not_knows*0..1]->(b) RETURN a"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 4)

    # Test traversal with a possibly-null source.
    def test08_optional_source(self):
        query = """OPTIONAL MATCH (a:fake) OPTIONAL MATCH (a)-[*]->(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        expected_result = [[None, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """OPTIONAL MATCH (a:node {name: 'A'}) OPTIONAL MATCH (a)-[*]->(b {name: 'B'}) RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        expected_result = [['A', 'B']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Test traversals with filters on variable-length edges
    def test09_filtered_edges(self):
        # Test an inline equality predicate
        query = """MATCH (a)-[* {connects: 'BC'}]->(b) RETURN a.name, b.name ORDER BY a.name, b.name"""
        # The filter op should have been optimized out
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn("Filter", plan)
        actual_result = redis_graph.query(query)
        expected_result = [['B', 'C']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Test a WHERE clause predicate
        query = """MATCH (a)-[e*]->(b) WHERE e.connects IN ['BC', 'CD'] RETURN a.name, b.name ORDER BY a.name, b.name"""
        # The filter op should have been optimized out
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn("Filter", plan)
        actual_result = redis_graph.query(query)
        expected_result = [['B', 'C'], ['B', 'D'], ['C', 'D']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Test a WHERE clause predicate with an OR condition
        query = """MATCH (a)-[e*]->(b) WHERE e.connects = 'BC' OR e.connects = 'CD' RETURN a.name, b.name ORDER BY a.name, b.name"""
        # The filter op should have been optimized out
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn("Filter", plan)
        actual_result = redis_graph.query(query)
        # Expecting the same result
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Test the concatenation of multiple predicates
        query = """MATCH (a)-[e*]->(b) WHERE e.connects IN ['AB', 'BC', 'CD'] AND e.connects <> 'CD' RETURN a.name, b.name ORDER BY a.name, b.name"""
        # The filter op should have been optimized out
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn("Filter", plan)
        actual_result = redis_graph.query(query)
        expected_result = [['A', 'B'], ['A', 'C'], ['B', 'C']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Test the concatenation of AND and OR conditions
        query = """MATCH (a)-[e*]->(b) WHERE e.connects IN ['AB', 'BC', 'CD'] AND (e.connects = 'AB' OR e.connects = 'BC')  AND e.connects <> 'CD' RETURN a.name, b.name ORDER BY a.name, b.name"""
        # The filter op should have been optimized out
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn("Filter", plan)
        actual_result = redis_graph.query(query)
        expected_result = [['A', 'B'], ['A', 'C'], ['B', 'C']]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate that WHERE clause predicates are applied to edges lower than the minHops value
        query = """MATCH (a)-[e*2..]->(b) WHERE e.connects <> 'AB' RETURN a.name, b.name ORDER BY a.name, b.name"""
        actual_result = redis_graph.query(query)
        expected_result = [['B', 'D']]
        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 20
0
class testPathFilter(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_con
        redis_con = self.env.getConnection()

    def setUp(self):
        global redis_graph
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.env.flush()

    def test00_simple_path_filter(self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0]]
        query_info = QueryInfo(query=query,
                               description="Tests simple path filter",
                               expected_result=expected_results)
        self._assert_resultset_equals_expected(result_set, query_info)

    def test01_negated_simple_path_filter(self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE NOT (n)-[:R]->(:L) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node1]]
        query_info = QueryInfo(query=query,
                               description="Tests simple negated path filter",
                               expected_result=expected_results)
        self._assert_resultset_equals_expected(result_set, query_info)

    def test02_test_path_filter_or_property_filter(self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) OR n.x=1 RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0], [node1]]
        query_info = QueryInfo(
            query=query,
            description="Tests OR condition with simple filter and path filter",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test03_path_filter_or_negated_path_filter(self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) OR NOT (n)-[:R]->(:L) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0], [node1]]
        query_info = QueryInfo(
            query=query,
            description="Tests OR condition with path and negated path filters",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test04_test_level_1_nesting_logical_operators_over_path_and_property_filters(
            self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) OR (n.x=1 AND NOT (n)-[:R]->(:L)) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0], [node1]]
        query_info = QueryInfo(
            query=query,
            description=
            "Tests AND condition with simple filter and negated path filter",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test05_test_level_2_nesting_logical_operators_over_path_and_property_filters(
            self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) OR (n.x=1 AND (n.x = 2 OR NOT (n)-[:R]->(:L))) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0], [node1]]
        query_info = QueryInfo(
            query=query,
            description="Tests AND condition with simple filter and nested OR",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test06_test_level_2_nesting_logical_operators_over_path_filters(self):
        node0 = Node(node_id=0, label="L")
        node1 = Node(node_id=1, label="L", properties={'x': 1})
        node2 = Node(node_id=2, label="L2")
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        edge12 = Edge(src_node=node1, dest_node=node2, relation="R2")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_edge(edge01)
        redis_graph.add_edge(edge12)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R]->(:L) OR (n.x=1 AND ((n)-[:R2]->(:L2) OR (n)-[:R]->(:L))) RETURN n"
        result_set = redis_graph.query(query)
        expected_results = [[node0], [node1]]
        query_info = QueryInfo(
            query=query,
            description="Tests AND condition with simple filter and nested OR",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test07_test_edge_filters(self):
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        node2 = Node(node_id=2, label="L", properties={'x': 'c'})
        edge01 = Edge(src_node=node0,
                      dest_node=node1,
                      relation="R",
                      properties={'x': 1})
        edge12 = Edge(src_node=node1, dest_node=node2, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_edge(edge01)
        redis_graph.add_edge(edge12)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[:R {x:1}]->() RETURN n.x"
        result_set = redis_graph.query(query)
        expected_results = [['a']]
        query_info = QueryInfo(
            query=query,
            description="Tests pattern filter edge conditions",
            expected_result=expected_results)
        self._assert_resultset_and_expected_mutually_included(
            result_set, query_info)

    def test08_indexed_child_stream_resolution(self):
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        node2 = Node(node_id=2, label="L", properties={'x': 'c'})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        edge12 = Edge(src_node=node1, dest_node=node2, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_edge(edge01)
        redis_graph.add_edge(edge12)
        redis_graph.flush()

        # Create index.
        query = "CREATE INDEX ON :L(x)"
        result_set = redis_graph.query(query)
        self.env.assertEquals(result_set.indices_created, 1)

        # Issue a query in which the bound variable stream of the SemiApply op is an Index Scan.
        query = "MATCH (n:L) WHERE (:L)<-[]-(n)<-[]-(:L {x: 'a'}) AND n.x = 'b' RETURN n.x"
        result_set = redis_graph.query(query)
        expected_results = [['b']]
        self.env.assertEquals(result_set.result_set, expected_results)

    def test09_no_invalid_expand_into(self):
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        node2 = Node(node_id=2, label="L", properties={'x': 'c'})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        edge12 = Edge(src_node=node1, dest_node=node2, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_edge(edge01)
        redis_graph.add_edge(edge12)
        redis_graph.flush()

        # Issue a query in which the match stream and the bound stream must both perform traversal.
        query = "MATCH (n:L)-[]->(:L) WHERE ({x: 'a'})-[]->(n) RETURN n.x"
        plan = redis_graph.execution_plan(query)
        # Verify that the execution plan has no Expand Into and two traversals.
        self.env.assertNotIn("Expand Into", plan)
        self.env.assertEquals(2, plan.count("Conditional Traverse"))

        result_set = redis_graph.query(query)
        expected_results = [['b']]
        self.env.assertEquals(result_set.result_set, expected_results)

    def test10_verify_apply_results(self):
        # Build a graph with 3 nodes and 3 edges, 2 of which have the same source.
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        node2 = Node(node_id=2, label="L", properties={'x': 'c'})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        edge02 = Edge(src_node=node0, dest_node=node2, relation="R")
        edge12 = Edge(src_node=node1, dest_node=node2, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_edge(edge01)
        redis_graph.add_edge(edge02)
        redis_graph.add_edge(edge12)
        redis_graph.flush()

        query = "MATCH (n:L) WHERE (n)-[]->() RETURN n.x ORDER BY n.x"
        result_set = redis_graph.query(query)
        # Each source node should be returned exactly once.
        expected_results = [['a'], ['b']]
        self.env.assertEquals(result_set.result_set, expected_results)

    def test11_unbound_path_filters(self):
        # Build a graph with 2 nodes connected by 1 edge.
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        # Emit a query that uses an AntiSemiApply op to return values.
        query = "MATCH (n:L) WHERE NOT (:L)-[]->() RETURN n.x ORDER BY n.x"
        result_set = redis_graph.query(query)
        # The WHERE filter evaluates to false, no results should be returned.
        expected_result = []
        self.env.assertEquals(result_set.result_set, expected_result)

        # Emit a query that uses a SemiApply op to return values.
        query = "MATCH (n:L) WHERE (:L)-[]->() RETURN n.x ORDER BY n.x"
        result_set = redis_graph.query(query)
        # The WHERE filter evaluates to true, all results should be returned.
        expected_result = [['a'], ['b']]
        self.env.assertEquals(result_set.result_set, expected_result)

    def test12_label_introduced_in_path_filter(self):
        # Build a graph with 2 nodes connected by 1 edge.
        node0 = Node(node_id=0, label="L", properties={'x': 'a'})
        node1 = Node(node_id=1, label="L", properties={'x': 'b'})
        edge01 = Edge(src_node=node0, dest_node=node1, relation="R")
        redis_graph.add_node(node0)
        redis_graph.add_node(node1)
        redis_graph.add_edge(edge01)
        redis_graph.flush()

        # Write a WHERE filter that introduces label data.
        query = "MATCH (a1)-[]->(a2) WHERE (a1:L)-[]->(a2:L) return a1.x, a2.x"
        result_set = redis_graph.query(query)
        expected_result = [['a', 'b']]
        self.env.assertEquals(result_set.result_set, expected_result)
Exemplo n.º 21
0
class testGraphBulkInsertFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        global redis_con
        redis_con = self.env.getConnection()
        port = self.env.envRunner.port
        redis_graph = Graph("graph", redis_con)

    # Run bulk loader script and validate terminal output
    def test01_run_script(self):
        graphname = "graph"
        runner = CliRunner()

        csv_path = os.path.dirname(
            os.path.abspath(__file__)) + '/../../demo/bulk_insert/resources/'
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', csv_path + 'Person.csv', '--nodes',
            csv_path + 'Country.csv', '--relations', csv_path + 'KNOWS.csv',
            '--relations', csv_path + 'VISITED.csv', graphname
        ])

        # The script should report 27 node creations and 48 edge creations
        self.env.assertEquals(res.exit_code, 0)
        self.env.assertIn('27 nodes created', res.output)
        self.env.assertIn('56 relations created', res.output)

    # Validate that the expected nodes and properties have been constructed
    def test02_validate_nodes(self):
        global redis_graph
        # Query the newly-created graph
        query_result = redis_graph.query(
            'MATCH (p:Person) RETURN p.name, p.age, p.gender, p.status, ID(p) ORDER BY p.name'
        )
        # Verify that the Person label exists, has the correct attributes, and is properly populated
        expected_result = [['Ailon Velger', 32, 'male', 'married', 2],
                           ['Alon Fital', 32, 'male', 'married', 1],
                           ['Boaz Arad', 31, 'male', 'married', 4],
                           ['Gal Derriere', 26, 'male', 'single', 11],
                           ['Jane Chernomorin', 31, 'female', 'married', 8],
                           ['Lucy Yanfital', 30, 'female', 'married', 7],
                           ['Mor Yesharim', 31, 'female', 'married', 12],
                           ['Noam Nativ', 34, 'male', 'single', 13],
                           ['Omri Traub', 33, 'male', 'single', 5],
                           ['Ori Laslo', 32, 'male', 'married', 3],
                           ['Roi Lipman', 32, 'male', 'married', 0],
                           ['Shelly Laslo Rooz', 31, 'female', 'married', 9],
                           ['Tal Doron', 32, 'male', 'single', 6],
                           [
                               'Valerie Abigail Arad', 31, 'female', 'married',
                               10
                           ]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # Verify that the Country label exists, has the correct attributes, and is properly populated
        query_result = redis_graph.query(
            'MATCH (c:Country) RETURN c.name, ID(c) ORDER BY c.name')
        expected_result = [['Andora', 21], ['Canada', 18], ['China', 19],
                           ['Germany', 24], ['Greece', 17], ['Italy', 25],
                           ['Japan', 16], ['Kazakhstan', 22],
                           ['Netherlands', 20], ['Prague', 15], ['Russia', 23],
                           ['Thailand', 26], ['USA', 14]]
        self.env.assertEquals(query_result.result_set, expected_result)

    # Validate that the expected relations and properties have been constructed
    def test03_validate_relations(self):
        # Query the newly-created graph
        query_result = redis_graph.query(
            'MATCH (a)-[e:KNOWS]->(b) RETURN a.name, e.relation, b.name ORDER BY e.relation, a.name, b.name'
        )

        expected_result = [['Ailon Velger', 'friend', 'Noam Nativ'],
                           ['Alon Fital', 'friend', 'Gal Derriere'],
                           ['Alon Fital', 'friend', 'Mor Yesharim'],
                           ['Boaz Arad', 'friend', 'Valerie Abigail Arad'],
                           ['Roi Lipman', 'friend', 'Ailon Velger'],
                           ['Roi Lipman', 'friend', 'Alon Fital'],
                           ['Roi Lipman', 'friend', 'Boaz Arad'],
                           ['Roi Lipman', 'friend', 'Omri Traub'],
                           ['Roi Lipman', 'friend', 'Ori Laslo'],
                           ['Roi Lipman', 'friend', 'Tal Doron'],
                           ['Ailon Velger', 'married', 'Jane Chernomorin'],
                           ['Alon Fital', 'married', 'Lucy Yanfital'],
                           ['Ori Laslo', 'married', 'Shelly Laslo Rooz']]
        self.env.assertEquals(query_result.result_set, expected_result)

        query_result = redis_graph.query(
            'MATCH (a)-[e:VISITED]->(b) RETURN a.name, e.purpose, b.name ORDER BY e.purpose, a.name, b.name'
        )

        expected_result = [['Alon Fital', 'business', 'Prague'],
                           ['Alon Fital', 'business', 'USA'],
                           ['Boaz Arad', 'business', 'Netherlands'],
                           ['Boaz Arad', 'business', 'USA'],
                           ['Gal Derriere', 'business', 'Netherlands'],
                           ['Jane Chernomorin', 'business', 'USA'],
                           ['Lucy Yanfital', 'business', 'USA'],
                           ['Mor Yesharim', 'business', 'Germany'],
                           ['Ori Laslo', 'business', 'China'],
                           ['Ori Laslo', 'business', 'USA'],
                           ['Roi Lipman', 'business', 'Prague'],
                           ['Roi Lipman', 'business', 'USA'],
                           ['Tal Doron', 'business', 'Japan'],
                           ['Tal Doron', 'business', 'USA'],
                           ['Alon Fital', 'pleasure', 'Greece'],
                           ['Alon Fital', 'pleasure', 'Prague'],
                           ['Alon Fital', 'pleasure', 'USA'],
                           ['Boaz Arad', 'pleasure', 'Netherlands'],
                           ['Boaz Arad', 'pleasure', 'USA'],
                           ['Jane Chernomorin', 'pleasure', 'Greece'],
                           ['Jane Chernomorin', 'pleasure', 'Netherlands'],
                           ['Jane Chernomorin', 'pleasure', 'USA'],
                           ['Lucy Yanfital', 'pleasure', 'Kazakhstan'],
                           ['Lucy Yanfital', 'pleasure', 'Prague'],
                           ['Lucy Yanfital', 'pleasure', 'USA'],
                           ['Mor Yesharim', 'pleasure', 'Greece'],
                           ['Mor Yesharim', 'pleasure', 'Italy'],
                           ['Noam Nativ', 'pleasure', 'Germany'],
                           ['Noam Nativ', 'pleasure', 'Netherlands'],
                           ['Noam Nativ', 'pleasure', 'Thailand'],
                           ['Omri Traub', 'pleasure', 'Andora'],
                           ['Omri Traub', 'pleasure', 'Greece'],
                           ['Omri Traub', 'pleasure', 'USA'],
                           ['Ori Laslo', 'pleasure', 'Canada'],
                           ['Roi Lipman', 'pleasure', 'Japan'],
                           ['Roi Lipman', 'pleasure', 'Prague'],
                           ['Shelly Laslo Rooz', 'pleasure', 'Canada'],
                           ['Shelly Laslo Rooz', 'pleasure', 'China'],
                           ['Shelly Laslo Rooz', 'pleasure', 'USA'],
                           ['Tal Doron', 'pleasure', 'Andora'],
                           ['Tal Doron', 'pleasure', 'USA'],
                           ['Valerie Abigail Arad', 'pleasure', 'Netherlands'],
                           ['Valerie Abigail Arad', 'pleasure', 'Russia']]
        self.env.assertEquals(query_result.result_set, expected_result)

    def test04_private_identifiers(self):
        graphname = "tmpgraph1"
        # Write temporary files
        with open('/tmp/nodes.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["_identifier", "nodename"])
            out.writerow([0, "a"])
            out.writerow([5, "b"])
            out.writerow([3, "c"])
        with open('/tmp/relations.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["src", "dest"])
            out.writerow([0, 3])
            out.writerow([5, 3])

        runner = CliRunner()
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', '/tmp/nodes.tmp', '--relations',
            '/tmp/relations.tmp', graphname
        ])

        # The script should report 3 node creations and 2 edge creations
        self.env.assertEquals(res.exit_code, 0)
        self.env.assertIn('3 nodes created', res.output)
        self.env.assertIn('2 relations created', res.output)

        # Delete temporary files
        os.remove('/tmp/nodes.tmp')
        os.remove('/tmp/relations.tmp')

        tmp_graph = Graph(graphname, redis_con)
        # The field "_identifier" should not be a property in the graph
        query_result = tmp_graph.query('MATCH (a) RETURN a')

        for propname in query_result.header:
            self.env.assertNotIn('_identifier', propname)

    def test05_reused_identifier(self):
        graphname = "tmpgraph2"
        # Write temporary files
        with open('/tmp/nodes.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["_identifier", "nodename"])
            out.writerow([0, "a"])
            out.writerow([5, "b"])
            out.writerow([0, "c"])  # reused identifier
        with open('/tmp/relations.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["src", "dest"])
            out.writerow([0, 3])

        runner = CliRunner()
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', '/tmp/nodes.tmp', '--relations',
            '/tmp/relations.tmp', graphname
        ])

        # The script should fail because a node identifier is reused
        self.env.assertNotEqual(res.exit_code, 0)
        self.env.assertIn('used multiple times', res.output)

        # Run the script again without creating relations
        runner = CliRunner()
        res = runner.invoke(
            bulk_insert,
            ['--port', port, '--nodes', '/tmp/nodes.tmp', graphname])

        # The script should succeed and create 3 nodes
        self.env.assertEquals(res.exit_code, 0)
        self.env.assertIn('3 nodes created', res.output)

        # Delete temporary files
        os.remove('/tmp/nodes.tmp')
        os.remove('/tmp/relations.tmp')

    def test06_batched_build(self):
        # Create demo graph wth one query per input file
        graphname = "batched_graph"
        runner = CliRunner()

        csv_path = os.path.dirname(
            os.path.abspath(__file__)) + '/../../demo/bulk_insert/resources/'
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', csv_path + 'Person.csv', '--nodes',
            csv_path + 'Country.csv', '--relations', csv_path + 'KNOWS.csv',
            '--relations', csv_path + 'VISITED.csv', '--max-token-count', 1,
            graphname
        ])

        self.env.assertEquals(res.exit_code, 0)
        # The script should report statistics multiple times
        self.env.assertGreater(res.output.count('nodes created'), 1)

        new_graph = Graph(graphname, redis_con)

        # Newly-created graph should be identical to graph created in single query
        original_result = redis_graph.query(
            'MATCH (p:Person) RETURN p, ID(p) ORDER BY p.name')
        new_result = new_graph.query(
            'MATCH (p:Person) RETURN p, ID(p) ORDER BY p.name')
        self.env.assertEquals(original_result.result_set,
                              new_result.result_set)

        original_result = redis_graph.query(
            'MATCH (a)-[e:KNOWS]->(b) RETURN a.name, e, b.name ORDER BY e.relation, a.name'
        )
        new_result = new_graph.query(
            'MATCH (a)-[e:KNOWS]->(b) RETURN a.name, e, b.name ORDER BY e.relation, a.name'
        )
        self.env.assertEquals(original_result.result_set,
                              new_result.result_set)

    def test07_script_failures(self):
        graphname = "tmpgraph3"
        # Write temporary files
        with open('/tmp/nodes.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["id", "nodename"])
            out.writerow([0])  # Wrong number of properites

        runner = CliRunner()
        res = runner.invoke(
            bulk_insert,
            ['--port', port, '--nodes', '/tmp/nodes.tmp', graphname])

        # The script should fail because a row has the wrong number of fields
        self.env.assertNotEqual(res.exit_code, 0)
        self.env.assertIn('Expected 2 columns', str(res.exception))

        # Write temporary files
        with open('/tmp/nodes.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["id", "nodename"])
            out.writerow([0, "a"])

        with open('/tmp/relations.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["src"])  # Incomplete relation description
            out.writerow([0])

        runner = CliRunner()
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', '/tmp/nodes.tmp', '--relations',
            '/tmp/relations.tmp', graphname
        ])

        # The script should fail because a row has the wrong number of fields
        self.env.assertNotEqual(res.exit_code, 0)
        self.env.assertIn('should have at least 2 elements',
                          str(res.exception))

        with open('/tmp/relations.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["src", "dest"])
            out.writerow([0, "fakeidentifier"])

        runner = CliRunner()
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', '/tmp/nodes.tmp', '--relations',
            '/tmp/relations.tmp', graphname
        ])

        # The script should fail because an invalid node identifier was used
        self.env.assertNotEqual(res.exit_code, 0)
        self.env.assertIn('fakeidentifier', str(res.exception))
        os.remove('/tmp/nodes.tmp')
        os.remove('/tmp/relations.tmp')

    # Verify that numeric, boolean, and null types are properly handled
    def test08_property_types(self):
        graphname = "tmpgraph4"
        # Write temporary files
        with open('/tmp/nodes.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["numeric", "mixed", "bool"])
            out.writerow([0, '', True])
            out.writerow([5, "notnull", False])
            out.writerow([7, '', False])  # reused identifier
        with open('/tmp/relations.tmp', mode='w') as csv_file:
            out = csv.writer(csv_file)
            out.writerow(["src", "dest", "prop"])
            out.writerow([0, 5, True])
            out.writerow([5, 7, 3.5])
            out.writerow([7, 0, ''])

        runner = CliRunner()
        res = runner.invoke(bulk_insert, [
            '--port', port, '--nodes', '/tmp/nodes.tmp', '--relations',
            '/tmp/relations.tmp', graphname
        ])

        self.env.assertEquals(res.exit_code, 0)
        self.env.assertIn('3 nodes created', res.output)
        self.env.assertIn('3 relations created', res.output)

        graph = Graph(graphname, redis_con)
        query_result = graph.query(
            'MATCH (a)-[e]->() RETURN a.numeric, a.mixed, a.bool, e.prop ORDER BY a.numeric, e.prop'
        )
        expected_result = [[0, None, True, True], [5, 'notnull', False, 3.5],
                           [7, None, False, None]]

        # The graph should have the correct types for all properties
        self.env.assertEquals(query_result.result_set, expected_result)
Exemplo n.º 22
0
class testBoundVariables(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph

        # Construct a graph with the form:
        # (v1)-[:E]->(v2)-[:E]->(v3)
        node_props = ['v1', 'v2', 'v3']

        nodes = []
        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"val": v})
            nodes.append(node)
            redis_graph.add_node(node)

        edge = Edge(nodes[0], "E", nodes[1])
        redis_graph.add_edge(edge)

        edge = Edge(nodes[1], "E", nodes[2])
        redis_graph.add_edge(edge)

        redis_graph.commit()

    def test01_with_projected_entity(self):
        query = """MATCH (a:L {val: 'v1'}) WITH a MATCH (a)-[e]->(b) RETURN b.val"""
        actual_result = redis_graph.query(query)

        # Verify that this query does not generate a Cartesian product.
        execution_plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Cartesian Product', execution_plan)

        # Verify results.
        expected_result = [['v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test02_match_create_bound_variable(self):
        # Extend the graph such that the new form is:
        # (v1)-[:E]->(v2)-[:E]->(v3)-[:e]->(v4)
        query = """MATCH (a:L {val: 'v3'}) CREATE (a)-[:E]->(b:L {val: 'v4'}) RETURN b.val"""
        actual_result = redis_graph.query(query)
        expected_result = [['v4']]
        self.env.assertEquals(actual_result.result_set, expected_result)
        self.env.assertEquals(actual_result.relationships_created, 1)
        self.env.assertEquals(actual_result.nodes_created, 1)

    def test03_procedure_match_bound_variable(self):
        # Create a full-text index.
        redis_graph.call_procedure("db.idx.fulltext.createNodeIndex", 'L',
                                   'val')

        # Project the result of scanning this index into a MATCH pattern.
        query = """CALL db.idx.fulltext.queryNodes('L', 'v1') YIELD node MATCH (node)-[]->(b) RETURN b.val"""
        # Verify that execution begins at the procedure call and proceeds into the traversals.
        execution_plan = redis_graph.execution_plan(query)
        # For the moment, we'll just verify that ProcedureCall appears later in the plan than
        # its parent, Conditional Traverse.
        traverse_idx = execution_plan.index("Conditional Traverse")
        call_idx = execution_plan.index("ProcedureCall")
        self.env.assertTrue(call_idx > traverse_idx)

        # Verify the results
        actual_result = redis_graph.query(query)
        expected_result = [['v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test04_projected_scanned_entity(self):
        query = """MATCH (a:L {val: 'v1'}) WITH a MATCH (a), (b {val: 'v2'}) RETURN a.val, b.val"""
        actual_result = redis_graph.query(query)

        # Verify that this query generates exactly 2 scan ops.
        execution_plan = redis_graph.execution_plan(query)
        self.env.assertEquals(2, execution_plan.count('Scan'))

        # Verify results.
        expected_result = [['v1', 'v2']]
        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 23
0
class testFunctionCallsFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global graph
        global redis_con
        redis_con = self.env.getConnection()
        graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global graph
        nodes = {}
        # Create entities
        for idx, p in enumerate(people):
            node = Node(label="person", properties={"name": p, "val": idx})
            graph.add_node(node)
            nodes[p] = node

        # Fully connected graph
        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "know", nodes[dest])
                    graph.add_edge(edge)

        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "works_with", nodes[dest])
                    graph.add_edge(edge)

        graph.commit()
        query = """MATCH (a)-[:know]->(b) CREATE (a)-[:know]->(b)"""
        graph.query(query)

    def expect_type_error(self, query):
        try:
            graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn("Type mismatch", e.message)

    def expect_error(self, query, expected_err_msg):
        try:
            graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError as e:
            # Expecting a type error.
            self.env.assertIn(expected_err_msg, e.message)

    # Validate capturing of errors prior to query execution.
    def test01_compile_time_errors(self):
        query = """RETURN toUpper(5)"""
        self.expect_type_error(query)

        query = """RETURN 'a' * 2"""
        self.expect_type_error(query)

        query = """RETURN max(1 + min(2))"""
        self.expect_error(
            query,
            "Can't use aggregate functions inside of aggregate functions")

    def test02_boolean_comparisons(self):
        query = """RETURN true = 5"""
        actual_result = graph.query(query)
        expected_result = [[False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN true <> 'str'"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 'anything' <> NULL"""
        actual_result = graph.query(query)
        expected_result = [[None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 'anything' = NULL"""
        actual_result = graph.query(query)
        expected_result = [[None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN 10 >= 1.5"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """RETURN -1 < 1"""
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test03_boolean_errors(self):
        query = """RETURN 'str' < 5.5"""
        self.expect_type_error(query)

        query = """RETURN true > 5"""
        self.expect_type_error(query)

        query = """MATCH (a) RETURN a < 'anything' LIMIT 1"""
        self.expect_type_error(query)

    def test04_entity_functions(self):
        query = "RETURN ID(5)"
        self.expect_type_error(query)

        query = "MATCH (a) RETURN ID(a) ORDER BY ID(a) LIMIT 3"
        actual_result = graph.query(query)
        expected_result = [[0], [1], [2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "MATCH (a)-[e]->() RETURN ID(e) ORDER BY ID(e) LIMIT 3"
        actual_result = graph.query(query)
        expected_result = [[0], [1], [2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN EXISTS(null)"
        actual_result = graph.query(query)
        expected_result = [[False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN EXISTS('anything')"
        actual_result = graph.query(query)
        expected_result = [[True]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test07_nonmap_errors(self):
        query = """MATCH (a) WITH a.name AS scalar RETURN scalar.name"""
        self.expect_type_error(query)

    def test08_apply_all_function(self):
        query = "MATCH () RETURN COUNT(*)"
        actual_result = graph.query(query)
        expected_result = [[4]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "UNWIND [1, 2] AS a RETURN COUNT(*)"
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test09_static_aggregation(self):
        query = "RETURN count(*)"
        actual_result = graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN max(2)"
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = "RETURN min(3)"
        actual_result = graph.query(query)
        expected_result = [[3]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test10_modulo_inputs(self):
        # Validate modulo with integer inputs.
        query = "RETURN 5 % 2"
        actual_result = graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with a floating-point dividend.
        query = "RETURN 5.5 % 2"
        actual_result = graph.query(query)
        expected_result = [[1.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with a floating-point divisor.
        query = "RETURN 5 % 2.5"
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with both a floating-point dividen and a floating-point divisor.
        query = "RETURN 5.5 % 2.5"
        actual_result = graph.query(query)
        expected_result = [[0.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with negative integer inputs.
        query = "RETURN -5 % -2"
        actual_result = graph.query(query)
        expected_result = [[-1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Validate modulo with negative floating-point inputs.
        query = "RETURN -5.5 % -2.5"
        actual_result = graph.query(query)
        expected_result = [[-0.5]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Aggregate functions should handle null inputs appropriately.
    def test11_null_aggregate_function_inputs(self):
        # SUM should sum all non-null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN sum(a)"""
        actual_result = graph.query(query)
        expected_result = [[4]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # SUM should return 0 given a fully NULL input.
        query = """WITH NULL AS a RETURN sum(a)"""
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COUNT should count all non-null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN count(a)"""
        actual_result = graph.query(query)
        expected_result = [[2]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COUNT should return 0 given a fully NULL input.
        query = """WITH NULL AS a RETURN count(a)"""
        actual_result = graph.query(query)
        expected_result = [[0]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COLLECT should ignore null inputs.
        query = """UNWIND [1, NULL, 3] AS a RETURN collect(a)"""
        actual_result = graph.query(query)
        expected_result = [[[1, 3]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # COLLECT should return an empty array on all null inputs.
        query = """WITH NULL AS a RETURN collect(a)"""
        actual_result = graph.query(query)
        expected_result = [[[]]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # Verify that nested functions that perform heap allocations return properly.
    def test12_nested_heap_functions(self):
        query = """MATCH p = (n) WITH head(nodes(p)) AS node RETURN node.name ORDER BY node.name"""
        actual_result = graph.query(query)
        expected_result = [['Ailon'], ['Alon'], ['Boaz'], ['Roi']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # CASE...WHEN statements should properly handle NULL, false, and true evaluations.
    def test13_case_when_inputs(self):
        # Simple case form: single value evaluation.
        query = """UNWIND [NULL, true, false] AS v RETURN v, CASE v WHEN true THEN v END"""
        actual_result = graph.query(query)
        expected_result = [[None, None], [True, True], [False, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """UNWIND [NULL, true, false] AS v RETURN v, CASE v WHEN true THEN v WHEN false THEN v END"""
        actual_result = graph.query(query)
        expected_result = [[None, None], [True, True], [False, False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Generic case form: evaluation for each case.
        query = """UNWIND [NULL, true, false] AS v RETURN v, CASE WHEN v THEN v END"""
        actual_result = graph.query(query)
        # Only the true value should return non-NULL.
        expected_result = [[None, None], [True, True], [False, None]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        query = """UNWIND [NULL, true, false] AS v RETURN v, CASE WHEN v IS NOT NULL THEN v END"""
        actual_result = graph.query(query)
        # The true and false values should both return non-NULL.
        expected_result = [[None, None], [True, True], [False, False]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    # CASE...WHEN statements should manage allocated values properly.
    def test14_case_when_memory_management(self):
        # Simple case form: single value evaluation.
        query = """WITH 'A' AS a WITH CASE a WHEN 'A' THEN toString(a) END AS key RETURN toLower(key)"""
        actual_result = graph.query(query)
        expected_result = [['a']]
        self.env.assertEquals(actual_result.result_set, expected_result)
        # Generic case form: evaluation for each case.
        query = """WITH 'A' AS a WITH CASE WHEN true THEN toString(a) END AS key RETURN toLower(key)"""
        actual_result = graph.query(query)
        expected_result = [['a']]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test15_aggregate_error_handling(self):
        functions = [
            "avg", "collect", "count", "max", "min", "sum", "percentileDisc",
            "percentileCont", "stDev"
        ]
        # Test all functions for invalid argument counts.
        for function in functions:
            query = """UNWIND range(0, 10) AS val RETURN %s(val, val, val)""" % (
                function)
            self.expect_error(query, "Received 3 arguments")

        # Test numeric functions for invalid input types.
        numeric_functions = ["avg", "sum", "stDev"]
        for function in numeric_functions:
            query = """UNWIND ['a', 'b', 'c'] AS val RETURN %s(val)""" % (
                function)
            self.expect_type_error(query)

        # Test invalid numeric input for percentile function.
        query = """UNWIND range(0, 10) AS val RETURN percentileDisc(val, -1)"""
        self.expect_error(query, "must be a number in the range 0.0 to 1.0")

    # startNode and endNode calls should return the appropriate nodes.
    def test16_edge_endpoints(self):
        query = """MATCH (a)-[e]->(b) RETURN a.name, startNode(e).name, b.name, endNode(e).name"""
        actual_result = graph.query(query)
        for row in actual_result.result_set:
            self.env.assertEquals(row[0], row[1])
            self.env.assertEquals(row[2], row[3])
Exemplo n.º 24
0
class testGraphPersistency(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)
        global redis_con
        redis_con = self.env.getConnection()

    def populate_graph(self, graph_name):
        # quick return if graph already exists
        if redis_con.exists(graph_name):
            return redis_graph

        people = ["Roi", "Alon", "Ailon", "Boaz", "Tal", "Omri", "Ori"]
        visits = [("Roi", "USA"), ("Alon", "Israel"), ("Ailon", "Japan"),
                  ("Boaz", "United Kingdom")]
        countries = ["Israel", "USA", "Japan", "United Kingdom"]
        redis_graph = Graph(graph_name, redis_con)
        personNodes = {}
        countryNodes = {}

        # create nodes
        for p in people:
            person = Node(label="person",
                          properties={
                              "name": p,
                              "height": random.randint(160, 200)
                          })
            redis_graph.add_node(person)
            personNodes[p] = person

        for p in countries:
            country = Node(label="country",
                           properties={
                               "name": p,
                               "population": random.randint(100, 400)
                           })
            redis_graph.add_node(country)
            countryNodes[p] = country

        # create edges
        for v in visits:
            person = v[0]
            country = v[1]
            edge = Edge(personNodes[person],
                        'visit',
                        countryNodes[country],
                        properties={'purpose': 'pleasure'})
            redis_graph.add_edge(edge)

        redis_graph.commit()

        # delete nodes, to introduce deleted item within our datablock
        query = """MATCH (n:person) WHERE n.name = 'Roi' or n.name = 'Ailon' DELETE n"""
        redis_graph.query(query)

        query = """MATCH (n:country) WHERE n.name = 'USA' DELETE n"""
        redis_graph.query(query)

        # create indices
        actual_result = redis_con.execute_command(
            "GRAPH.QUERY", graph_name, "CREATE INDEX ON :person(name, height)")
        actual_result = redis_con.execute_command(
            "GRAPH.QUERY", graph_name,
            "CREATE INDEX ON :country(name, population)")

        return redis_graph

    def populate_dense_graph(self, graph_name):
        dense_graph = Graph(graph_name, redis_con)

        # return early if graph exists
        if redis_con.exists(graph_name):
            return dense_graph

        nodes = []
        for i in range(10):
            node = Node(label="n", properties={"val": i})
            dense_graph.add_node(node)
            nodes.append(node)

        for n_idx, n in enumerate(nodes):
            for m_idx, m in enumerate(nodes[:n_idx]):
                dense_graph.add_edge(Edge(n, "connected", m))

        dense_graph.flush()
        return dense_graph

    def test01_save_load_rdb(self):
        graph_names = ["G", "{tag}_G"]
        for graph_name in graph_names:
            graph = self.populate_graph(graph_name)
            for i in range(2):
                if i == 1:
                    # Save RDB & Load from RDB
                    self.env.dumpAndReload()

                # Verify
                # Expecting 5 person entities.
                query = """MATCH (p:person) RETURN COUNT(p)"""
                actual_result = graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 5)

                query = """MATCH (p:person) WHERE p.name='Alon' RETURN COUNT(p)"""
                actual_result = graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 1)

                # Expecting 3 country entities.
                query = """MATCH (c:country) RETURN COUNT(c)"""
                actual_result = graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 3)

                query = """MATCH (c:country) WHERE c.name = 'Israel' RETURN COUNT(c)"""
                actual_result = graph.query(query)
                nodeCount = actual_result.result_set[0][0]
                self.env.assertEquals(nodeCount, 1)

                # Expecting 2 visit edges.
                query = """MATCH (n:person)-[e:visit]->(c:country) WHERE e.purpose='pleasure' RETURN COUNT(e)"""
                actual_result = graph.query(query)
                edgeCount = actual_result.result_set[0][0]
                self.env.assertEquals(edgeCount, 2)

                # Verify indices exists
                expected_indices = [[
                    "exact-match", "country", ["name", "population"]
                ], ["exact-match", "person", ["name", "height"]]]
                indices = graph.query("""CALL db.indexes()""").result_set
                self.env.assertEquals(indices, expected_indices)

    # Verify that edges are not modified after entity deletion
    def test02_deleted_entity_migration(self):
        graph_names = ("H", "{tag}_H")
        for graph_name in graph_names:
            graph = self.populate_dense_graph(graph_name)

            query = """MATCH (p) WHERE ID(p) = 0 OR ID(p) = 3 OR ID(p) = 7 OR ID(p) = 9 DELETE p"""
            actual_result = graph.query(query)
            self.env.assertEquals(actual_result.nodes_deleted, 4)

            query = """MATCH (p)-[]->(q) RETURN p.val, q.val ORDER BY p.val, q.val"""
            first_result = graph.query(query)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            second_result = graph.query(query)
            self.env.assertEquals(first_result.result_set,
                                  second_result.result_set)

    # Strings, numerics, booleans, array, and point properties should be properly serialized and reloaded
    def test03_restore_properties(self):
        graph_names = ("simple_props", "{tag}_simple_props")
        for graph_name in graph_names:
            graph = Graph(graph_name, redis_con)

            query = """CREATE (:p {strval: 'str', numval: 5.5, boolval: true, array: [1,2,3], pointval: point({latitude: 5.5, longitude: 6})})"""
            result = graph.query(query)

            # Verify that node was created correctly
            self.env.assertEquals(result.nodes_created, 1)
            self.env.assertEquals(result.properties_set, 5)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            query = """MATCH (p) RETURN p.boolval, p.numval, p.strval, p.array, p.pointval"""
            actual_result = graph.query(query)

            # Verify that the properties are loaded correctly.
            expected_result = [[
                True, 5.5, 'str', [1, 2, 3], {
                    "latitude": 5.5,
                    "longitude": 6.0
                }
            ]]
            self.env.assertEquals(actual_result.result_set, expected_result)

    # Verify multiple edges of the same relation between nodes A and B
    # are saved and restored correctly.
    def test04_repeated_edges(self):
        graph_names = ["repeated_edges", "{tag}_repeated_edges"]
        for graph_name in graph_names:
            graph = Graph(graph_name, redis_con)
            src = Node(label='p', properties={'name': 'src'})
            dest = Node(label='p', properties={'name': 'dest'})
            edge1 = Edge(src, 'e', dest, properties={'val': 1})
            edge2 = Edge(src, 'e', dest, properties={'val': 2})

            graph.add_node(src)
            graph.add_node(dest)
            graph.add_edge(edge1)
            graph.add_edge(edge2)
            graph.flush()

            # Verify the new edge
            q = """MATCH (a)-[e]->(b) RETURN e.val, a.name, b.name ORDER BY e.val"""
            actual_result = graph.query(q)

            expected_result = [[
                edge1.properties['val'], src.properties['name'],
                dest.properties['name']
            ],
                               [
                                   edge2.properties['val'],
                                   src.properties['name'],
                                   dest.properties['name']
                               ]]

            self.env.assertEquals(actual_result.result_set, expected_result)

            # Save RDB & Load from RDB
            redis_con.execute_command("DEBUG", "RELOAD")

            # Verify that the latest edge was properly saved and loaded
            actual_result = graph.query(q)
            self.env.assertEquals(actual_result.result_set, expected_result)

    # Verify that graphs larger than the
    # default capacity are persisted correctly.
    def test05_load_large_graph(self):
        graph_name = "LARGE_GRAPH"
        graph = Graph(graph_name, redis_con)
        q = """UNWIND range(1, 50000) AS v CREATE (:L)-[:R {v: v}]->(:L)"""
        actual_result = graph.query(q)
        self.env.assertEquals(actual_result.nodes_created, 100_000)
        self.env.assertEquals(actual_result.relationships_created, 50_000)

        redis_con.execute_command("DEBUG", "RELOAD")

        expected_result = [[50000]]

        queries = [
            """MATCH (:L)-[r {v: 50000}]->(:L) RETURN r.v""",
            """MATCH (:L)-[r:R {v: 50000}]->(:L) RETURN r.v""",
            """MATCH ()-[r:R {v: 50000}]->() RETURN r.v"""
        ]

        for q in queries:
            actual_result = graph.query(q)
            self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 25
0
class testIndexScanFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env(decodeResponses=True)

    def setUp(self):
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(social_utils.graph_name, redis_con)
        social_utils.populate_graph(redis_con, redis_graph)
        self.build_indices()

    def tearDown(self):
        self.env.cmd('flushall')

    def build_indices(self):
        global redis_graph
        redis_graph.redis_con.execute_command("GRAPH.QUERY", "social",
                                              "CREATE INDEX ON :person(age)")
        redis_graph.redis_con.execute_command(
            "GRAPH.QUERY", "social", "CREATE INDEX ON :country(name)")

    # Validate that Cartesian products using index and label scans succeed
    def test01_cartesian_product_mixed_scans(self):
        query = "MATCH (p:person), (c:country) WHERE p.age > 0 RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH (p:person), (c:country) RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set,
                              unindexed_result.result_set)

    # Validate that Cartesian products using just index scans succeed
    def test02_cartesian_product_index_scans_only(self):
        query = "MATCH (p:person), (c:country) WHERE p.age > 0 AND c.name > '' RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Index Scan'), 2)
        self.env.assertNotIn('Label Scan', plan)
        indexed_result = redis_graph.query(query)

        query = "MATCH (p:person), (c:country) RETURN p.age, c.name ORDER BY p.age, c.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Index Scan', plan)
        self.env.assertIn('Label Scan', plan)
        unindexed_result = redis_graph.query(query)

        self.env.assertEquals(indexed_result.result_set,
                              unindexed_result.result_set)

    # Validate that the appropriate bounds are respected when a Cartesian product uses the same index in two streams
    def test03_cartesian_product_reused_index(self):
        redis_graph.redis_con.execute_command("GRAPH.QUERY", "social",
                                              "CREATE INDEX ON :person(name)")
        query = "MATCH (a:person {name: 'Omri Traub'}), (b:person) WHERE b.age <= 30 RETURN a.name, b.name ORDER BY a.name, b.name"
        plan = redis_graph.execution_plan(query)
        # The two streams should both use index scans
        self.env.assertEquals(plan.count('Index Scan'), 2)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Omri Traub', 'Gal Derriere'],
                           ['Omri Traub', 'Lucy Yanfital']]
        result = redis_graph.query(query)

        self.env.assertEquals(result.result_set, expected_result)

    # Validate index utilization when filtering on a numeric field with the `IN` keyword.
    def test04_test_in_operator_numerics(self):
        # Validate the transformation of IN to multiple OR expressions.
        query = "MATCH (p:person) WHERE p.age IN [1,2,3] RETURN p"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        # Validate that nested arrays are not scanned in index.
        query = "MATCH (p:person) WHERE p.age IN [[1,2],3] RETURN p"
        plan = redis_graph.execution_plan(query)
        self.env.assertNotIn('Index Scan', plan)
        self.env.assertIn('Label Scan', plan)

        # Validate the transformation of IN to multiple OR, over a range.
        query = "MATCH (p:person) WHERE p.age IN range(0,30) RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of IN to empty index iterator.
        query = "MATCH (p:person) WHERE p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of IN OR IN to empty index iterators.
        query = "MATCH (p:person) WHERE p.age IN [] OR p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (p:person) WHERE p.age IN [26, 27, 30] OR p.age IN [33, 34, 35] RETURN p.name ORDER BY p.age"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub'],
                           ['Noam Nativ']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate the transformation of multiple IN filters.
        query = "MATCH (p:person) WHERE p.age IN [26, 27, 30] OR p.age IN [33, 34, 35] OR p.age IN [] RETURN p.name ORDER BY p.age"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub'],
                           ['Noam Nativ']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

    # Validate index utilization when filtering on string fields with the `IN` keyword.
    def test05_test_in_operator_string_props(self):
        # Build an index on the name property.
        redis_graph.redis_con.execute_command("GRAPH.QUERY", "social",
                                              "CREATE INDEX ON :person(name)")
        # Validate the transformation of IN to multiple OR expressions over string properties.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Combine numeric and string filters specified by IN.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] AND p.age in [30] RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Lucy Yanfital']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Validate an empty index on IN with multiple indexes
        query = "MATCH (p:person) WHERE p.name IN [] OR p.age IN [] RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)

        expected_result = []
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        # Combine IN filters with other relational filters.
        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] AND p.name < 'H' RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere']]
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

        query = "MATCH (p:person) WHERE p.name IN ['Gal Derriere', 'Lucy Yanfital'] OR p.age = 33 RETURN p.name ORDER BY p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)

        expected_result = [['Gal Derriere'], ['Lucy Yanfital'], ['Omri Traub']]
        result = redis_graph.query(query)
        result = redis_graph.query(query)
        self.env.assertEquals(result.result_set, expected_result)

    # ',' is the default separator for tag indices
    # we've updated our separator to '\0' this test verifies issue 696:
    # https://github.com/RedisGraph/RedisGraph/issues/696
    def test06_tag_separator(self):
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)

        # Create a single node with a long string property, introduce a comma as part of the string.
        query = """CREATE (:Node{value:"A ValuePartition is a pattern that describes a restricted set of classes from which a property can be associated. The parent class is used in restrictions, and the covering axiom means that only members of the subclasses may be used as values."})"""
        redis_graph.query(query)

        # Index property.
        query = """CREATE INDEX ON :Node(value)"""
        redis_graph.query(query)

        # Make sure node is returned by index scan.
        query = """MATCH (a:Node{value:"A ValuePartition is a pattern that describes a restricted set of classes from which a property can be associated. The parent class is used in restrictions, and the covering axiom means that only members of the subclasses may be used as values."}) RETURN a"""
        plan = redis_graph.execution_plan(query)
        result_set = redis_graph.query(query).result_set
        self.env.assertIn('Index Scan', plan)
        self.env.assertEqual(len(result_set), 1)

    def test07_index_scan_and_id(self):
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)
        nodes = []
        for i in range(10):
            node = Node(node_id=i, label='person', properties={'age': i})
            nodes.append(node)
            redis_graph.add_node(node)
            redis_graph.flush()

        query = """CREATE INDEX ON :person(age)"""
        query_result = redis_graph.query(query)
        self.env.assertEqual(1, query_result.indices_created)

        query = """MATCH (n:person) WHERE id(n)>=7 AND n.age<9 RETURN n ORDER BY n.age"""
        plan = redis_graph.execution_plan(query)
        query_result = redis_graph.query(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertIn('Filter', plan)
        query_result = redis_graph.query(query)

        self.env.assertEqual(2, len(query_result.result_set))
        expected_result = [[nodes[7]], [nodes[8]]]
        self.env.assertEquals(expected_result, query_result.result_set)

    # Validate placement of index scans and filter ops when not all filters can be replaced.
    def test08_index_scan_multiple_filters(self):
        query = "MATCH (p:person) WHERE p.age = 30 AND NOT EXISTS(p.fakeprop) RETURN p.name"
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        self.env.assertNotIn('Label Scan', plan)
        self.env.assertIn('Filter', plan)

        query_result = redis_graph.query(query)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test09_index_scan_with_params(self):
        query = "MATCH (p:person) WHERE p.age = $age RETURN p.name"
        params = {'age': 30}
        query = redis_graph.build_params_header(params) + query
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test10_index_scan_with_param_array(self):
        query = "MATCH (p:person) WHERE p.age in $ages RETURN p.name"
        params = {'ages': [30]}
        query = redis_graph.build_params_header(params) + query
        plan = redis_graph.execution_plan(query)
        self.env.assertIn('Index Scan', plan)
        query_result = redis_graph.query(query)
        expected_result = ["Lucy Yanfital"]
        self.env.assertEquals(query_result.result_set[0], expected_result)

    def test11_single_index_multiple_scans(self):
        query = "MERGE (p1:person {age: 40}) MERGE (p2:person {age: 41})"
        plan = redis_graph.execution_plan(query)
        # Two index scans should be performed.
        self.env.assertEqual(plan.count("Index Scan"), 2)

        query_result = redis_graph.query(query)
        # Two new nodes should be created.
        self.env.assertEquals(query_result.nodes_created, 2)

    def test12_remove_scans_before_index(self):
        query = "MATCH (a:person {age: 32})-[]->(b) WHERE (b:person)-[]->(a) RETURN a"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Index Scan"), 1)

    def test13_point_index_scan(self):
        # create index
        q = "CREATE INDEX ON :restaurant(location)"
        redis_graph.query(q)

        # create restaurant
        q = "CREATE (:restaurant {location: point({latitude:30.27822306, longitude:-97.75134723})})"
        redis_graph.query(q)

        # locate other restaurants within a 1000m radius
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) < 1000
        RETURN r"""

        # make sure index is used
        plan = redis_graph.execution_plan(q)
        self.env.assertIn("Index Scan", plan)

        # refine query from '<' to '<='
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) <= 1000
        RETURN r"""

        # make sure index is used
        plan = redis_graph.execution_plan(q)
        self.env.assertIn("Index Scan", plan)

        # index should NOT be used when searching for points outside of a circle
        # testing operand: '>', '>=' and '='
        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) > 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Index Scan", plan)

        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) >= 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Index Scan", plan)

        q = """MATCH (r:restaurant)
        WHERE distance(r.location, point({latitude:30.27822306, longitude:-97.75134723})) = 1000
        RETURN r"""

        # make sure index is NOT used
        plan = redis_graph.execution_plan(q)
        self.env.assertNotIn("Index Scan", plan)

    def test14_index_scan_utilize_array(self):
        # Querying indexed properties using IN a constant array should utilize indexes.
        query = "MATCH (a:person) WHERE a.age IN [34, 33] RETURN a.name ORDER BY a.name"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Index Scan"), 1)
        query_result = redis_graph.query(query)
        expected_result = [["Noam Nativ"], ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # Querying indexed properties using IN a generated array should utilize indexes.
        query = "MATCH (a:person) WHERE a.age IN range(33, 34) RETURN a.name ORDER BY a.name"
        plan = redis_graph.execution_plan(query)
        # One index scan should be performed.
        self.env.assertEqual(plan.count("Index Scan"), 1)
        query_result = redis_graph.query(query)
        expected_result = [["Noam Nativ"], ["Omri Traub"]]
        self.env.assertEquals(query_result.result_set, expected_result)

        # Querying indexed properties using IN a non-constant array should not utilize indexes.
        query = "MATCH (a:person)-[]->(b) WHERE a.age IN b.arr RETURN a"
        plan = redis_graph.execution_plan(query)
        # No index scans should be performed.
        self.env.assertEqual(plan.count("Label Scan"), 1)
        self.env.assertEqual(plan.count("Index Scan"), 0)
Exemplo n.º 26
0
class testGraphDeletionFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()

    def populate_graph(self):
        nodes = {}
        # Create entities
        people = ["Roi", "Alon", "Ailon", "Boaz", "Tal", "Omri", "Ori"]
        for p in people:
            node = Node(label="person", properties={"name": p})
            redis_graph.add_node(node)
            nodes[p] = node

        # Fully connected graph
        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "know", nodes[dest])
                    redis_graph.add_edge(edge)

        # Connect Roi to Alon via another edge type.
        edge = Edge(nodes["Roi"], "SameBirthday", nodes["Alon"])
        redis_graph.add_edge(edge)
        redis_graph.commit()

    # Count how many nodes contains the `name` attribute
    # remove the `name` attribute from some nodes
    # make sure the count updates accordingly,
    # restore `name` attribute from, verify that count returns to its original value.
    def test01_delete_attribute(self):
        # How many nodes contains the 'name' attribute
        query = """MATCH (n) WHERE EXISTS(n.name)=true RETURN count(n)"""
        actual_result = redis_graph.query(query)
        nodeCount = actual_result.result_set[0][0]
        self.env.assertEquals(nodeCount, 7)

        # Remove Tal's name attribute.
        query = """MATCH (n) WHERE n.name = 'Tal' SET n.name = NULL"""
        redis_graph.query(query)

        # How many nodes contains the 'name' attribute,
        # should reduce by 1 from previous count.
        query = """MATCH (n) WHERE EXISTS(n.name)=true RETURN count(n)"""
        actual_result = redis_graph.query(query)
        nodeCount = actual_result.result_set[0][0]
        self.env.assertEquals(nodeCount, 6)

        # Reintroduce Tal's name attribute.
        query = """MATCH (n) WHERE EXISTS(n.name)=false SET n.name = 'Tal'"""
        actual_result = redis_graph.query(query)

        # How many nodes contains the 'name' attribute
        query = """MATCH (n) WHERE EXISTS(n.name)=true RETURN count(n)"""
        actual_result = redis_graph.query(query)
        nodeCount = actual_result.result_set[0][0]
        self.env.assertEquals(nodeCount, 7)

    # Delete edges pointing into either Boaz or Ori.
    def test02_delete_edges(self):
        query = """MATCH (s:person)-[e:know]->(d:person) WHERE d.name = "Boaz" OR d.name = "Ori" RETURN count(e)"""
        actual_result = redis_graph.query(query)
        edge_count = actual_result.result_set[0][0]

        query = """MATCH (s:person)-[e:know]->(d:person) WHERE d.name = "Boaz" OR d.name = "Ori" DELETE e"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.relationships_deleted, edge_count)
        self.env.assertEquals(actual_result.nodes_deleted, 0)

    # Make sure there are no edges going into either Boaz or Ori.
    def test03_verify_edge_deletion(self):
        query = """MATCH (s:person)-[e:know]->(d:person)                    
                    WHERE d.name = "Boaz" AND d.name = "Ori"
                    RETURN COUNT(s)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 0)

    # Remove 'know' edge connecting Roi to Alon
    # Leaving a single edge of type SameBirthday
    # connecting the two.
    def test04_delete_typed_edge(self):
        query = """MATCH (s:person {name: "Roi"})-[e:know]->(d:person {name: "Alon"})
                   RETURN count(e)"""

        actual_result = redis_graph.query(query)
        edge_count = actual_result.result_set[0][0]

        query = """MATCH (s:person {name: "Roi"})-[e:know]->(d:person {name: "Alon"})
                   DELETE e"""

        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.relationships_deleted, edge_count)
        self.env.assertEquals(actual_result.nodes_deleted, 0)

    # Make sure Roi is still connected to Alon
    # via the "SameBirthday" type edge.
    def test05_verify_delete_typed_edge(self):
        query = """MATCH (s:person {name: "Roi"})-[e:SameBirthday]->(d:person {name: "Alon"})
                   RETURN COUNT(s)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 1)

        query = """MATCH (s:person {name: "Roi"})-[e:know]->(d:person {name: "Alon"})
                   RETURN COUNT(s)"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 0)

    # Remove both Alon and Boaz from the graph.
    def test06_delete_nodes(self):
        rel_count_query = """MATCH (a:person)-[e]->(b:person)
                             WHERE a.name = 'Boaz' OR a.name = 'Alon'
                             OR b.name = 'Boaz' OR b.name = 'Alon'
                             RETURN COUNT(e)"""
        rel_count_result = redis_graph.query(rel_count_query)
        # Get the total number of unique edges (incoming and outgoing)
        # connected to Alon and Boaz.
        rel_count = rel_count_result.result_set[0][0]

        query = """MATCH (s:person)
                    WHERE s.name = "Boaz" OR s.name = "Alon"
                    DELETE s"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.relationships_deleted, rel_count)
        self.env.assertEquals(actual_result.nodes_deleted, 2)

    # Make sure Alon and Boaz are not in the graph.
    def test07_get_deleted_nodes(self):
        query = """MATCH (s:person)
                    WHERE s.name = "Boaz" OR s.name = "Alon"
                    RETURN s"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(len(actual_result.result_set), 0)

    # Make sure Alon and Boaz are the only removed nodes.
    def test08_verify_node_deletion(self):
        query = """MATCH (s:person)
                   RETURN COUNT(s)"""
        actual_result = redis_graph.query(query)
        nodeCount = actual_result.result_set[0][0]
        self.env.assertEquals(nodeCount, 5)

    def test09_delete_entire_graph(self):
        # Make sure graph exists.
        query = """MATCH (n) RETURN COUNT(n)"""
        result = redis_graph.query(query)
        nodeCount = result.result_set[0][0]
        self.env.assertGreater(nodeCount, 0)

        # Delete graph.
        redis_graph.delete()

        # Try to query a deleted graph.
        redis_graph.query(query)
        result = redis_graph.query(query)
        nodeCount = result.result_set[0][0]
        self.env.assertEquals(nodeCount, 0)

    def test10_bulk_edge_deletion_timing(self):
        # Create large amount of relationships (50000).
        redis_graph.query(
            """UNWIND(range(1, 50000)) as x CREATE ()-[:R]->()""")
        # Delete and benchmark for 300ms.
        query = """MATCH (a)-[e:R]->(b) DELETE e"""
        result = redis_graph.query(query)
        query_info = QueryInfo(
            query=query,
            description=
            "Test the execution time for deleting large number of edges",
            max_run_time_ms=300)
        # Test will not fail for execution time > 300ms but a warning will be shown at the test output.
        self.env.assertEquals(result.relationships_deleted, 50000)
        self._assert_run_time(result, query_info)

    def test11_delete_entity_type_validation(self):
        # Currently we only support deletion of either nodes or edges
        # we've yet to introduce deletion of Path.

        # Try to delete an integer.
        query = """UNWIND [1] AS x DELETE x"""
        try:
            redis_graph.query(query)
            self.env.assertTrue(False)
        except Exception as error:
            self.env.assertTrue("Delete type mismatch" in error.message)

        query = """MATCH p=(n) DELETE p"""
        try:
            redis_graph.query(query)
            self.env.assertTrue(False)
        except Exception as error:
            self.env.assertTrue("Delete type mismatch" in error.message)

    def test12_delete_unwind_entity(self):
        redis_con = self.env.getConnection()
        redis_graph = Graph("delete_test", redis_con)

        # Create 10 nodes.
        for i in range(10):
            redis_graph.add_node(Node())
        redis_graph.flush()

        # Unwind path nodes.
        query = """MATCH p = () UNWIND nodes(p) AS node DELETE node"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_deleted, 10)
        self.env.assertEquals(actual_result.relationships_deleted, 0)

        for i in range(10):
            redis_graph.add_node(Node())
        redis_graph.flush()

        # Unwind collected nodes.
        query = """MATCH (n) WITH collect(n) AS nodes UNWIND nodes AS node DELETE node"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_deleted, 10)
        self.env.assertEquals(actual_result.relationships_deleted, 0)

    def test13_delete_path_elements(self):
        self.env.flush()
        redis_con = self.env.getConnection()
        redis_graph = Graph("delete_test", redis_con)

        src = Node()
        dest = Node()
        edge = Edge(src, "R", dest)

        redis_graph.add_node(src)
        redis_graph.add_node(dest)
        redis_graph.add_edge(edge)
        redis_graph.flush()

        # Delete projected
        # Unwind path nodes.
        query = """MATCH p = (src)-[e]->(dest) WITH nodes(p)[0] AS node, relationships(p)[0] as edge DELETE node, edge"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_deleted, 1)
        self.env.assertEquals(actual_result.relationships_deleted, 1)

    # Verify that variable-length traversals in each direction produce the correct results after deletion.
    def test14_post_deletion_traversal_directions(self):
        self.env.flush()
        redis_con = self.env.getConnection()
        redis_graph = Graph("G", redis_con)

        nodes = {}
        # Create entities.
        labels = ["Dest", "Src", "Src2"]
        for idx, l in enumerate(labels):
            node = Node(label=l, properties={"val": idx})
            redis_graph.add_node(node)
            nodes[l] = node

        edge = Edge(nodes["Src"], "R", nodes["Dest"])
        redis_graph.add_edge(edge)
        edge = Edge(nodes["Src2"], "R", nodes["Dest"])
        redis_graph.add_edge(edge)
        redis_graph.commit()

        # Delete a node.
        query = """MATCH (n:Src2) DELETE n"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_deleted, 1)
        self.env.assertEquals(actual_result.relationships_deleted, 1)

        query = """MATCH (n1:Src)-[*]->(n2:Dest) RETURN COUNT(*)"""
        actual_result = redis_graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

        # Perform the same traversal, this time traveling from destination to source.
        query = """MATCH (n1:Src)-[*]->(n2:Dest {val: 0}) RETURN COUNT(*)"""
        actual_result = redis_graph.query(query)
        expected_result = [[1]]
        self.env.assertEquals(actual_result.result_set, expected_result)

    def test15_update_deleted_entities(self):
        self.env.flush()
        redis_con = self.env.getConnection()
        redis_graph = Graph("delete_test", redis_con)

        src = Node()
        dest = Node()
        edge = Edge(src, "R", dest)

        redis_graph.add_node(src)
        redis_graph.add_node(dest)
        redis_graph.add_edge(edge)
        redis_graph.flush()

        # Attempt to update entities after deleting them.
        query = """MATCH (a)-[e]->(b) DELETE a, b SET a.v = 1, e.v = 2, b.v = 3"""
        actual_result = redis_graph.query(query)
        self.env.assertEquals(actual_result.nodes_deleted, 2)
        self.env.assertEquals(actual_result.relationships_deleted, 1)
        # No properties should be set.
        # (Note that this behavior is left unspecified by Cypher.)
        self.env.assertEquals(actual_result.properties_set, 0)

        # Validate that the graph is empty.
        query = """MATCH (a) RETURN a"""
        actual_result = redis_graph.query(query)
        expected_result = []
        self.env.assertEquals(actual_result.result_set, expected_result)
Exemplo n.º 27
0
class testProcedures(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_con
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()

    def populate_graph(self):
        if redis_con.exists(GRAPH_ID):
            return

        edge = Edge(node1, 'goWellWith', node5)
        redis_graph.add_node(node1)
        redis_graph.add_node(node2)
        redis_graph.add_node(node3)
        redis_graph.add_node(node4)
        redis_graph.add_node(node5)
        redis_graph.add_edge(edge)
        redis_graph.commit()

        # Create full-text index.
        redis_graph.call_procedure("db.idx.fulltext.createNodeIndex", 'fruit', 'name')

    # Compares two nodes based on their properties.
    def _compareNodes(self, a, b):
        return a.properties == b.properties

    # Make sure given item is found within resultset.
    def _inResultSet(self, item, resultset):
        for i in range(len(resultset)):
            result = resultset[i][0]
            if self._compareNodes(item, result):
                return True
        return False

    # Issue query and validates resultset.
    def queryAndValidate(self, query, expected_results, query_params={}):
        actual_resultset = redis_graph.query(query, query_params).result_set
        self.env.assertEquals(len(actual_resultset), len(expected_results))
        for i in range(len(actual_resultset)):
            self.env.assertTrue(self._inResultSet(expected_results[i], actual_resultset))
    
    # Call procedure, omit yield, expecting all procedure outputs to
    # be included in result-set.
    def test_no_yield(self):
        actual_result = redis_graph.call_procedure("db.idx.fulltext.queryNodes", "fruit", "Orange1")
        assert(len(actual_result.result_set) is 1)

        header = actual_result.header
        data = actual_result.result_set[0]
        assert(header[0][1] == 'node')
        assert(data[0] is not None)

    # Call procedure specify different outputs.
    def test_yield(self):
        actual_result = redis_graph.call_procedure("db.idx.fulltext.queryNodes", "fruit", "Orange1", y=["node"])
        assert(len(actual_result.result_set) is 1)

        header = actual_result.header
        data = actual_result.result_set[0]
        assert(header[0][1] == 'node')
        assert(data[0] is not None)

        # Yield an unknown output.
        # Expect an error when trying to use an unknown procedure output.
        try:
            redis_graph.call_procedure("db.idx.fulltext.queryNodes", "fruit", "Orange1", y=["unknown"])
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass
        
        # Yield the same output multiple times.
        # Expect an error when trying to use the same output multiple times.
        try:
            redis_graph.call_procedure("db.idx.fulltext.queryNodes", "fruit", "Orange1", y=["node", "node"])
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass
    
    def test_arguments(self):
        # Omit arguments.
        # Expect an error when trying to omit arguments.
        try:
            redis_graph.call_procedure("db.idx.fulltext.queryNodes")
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass
        
        # Omit arguments, queryNodes expecting 2 argument, provide 1.
        # Expect an error when trying to omit arguments.
        try:
            redis_graph.call_procedure("db.idx.fulltext.queryNodes", "arg1")
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

        # Overload arguments.
        # Expect an error when trying to send too many arguments.
        try:
            redis_graph.call_procedure("db.idx.fulltext.queryNodes", "fruit", "query", "fruit", "query", y=["node"])
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    # Test procedure call while mixing a number of addition clauses.
    def test_mix_clauses(self):
        query_params = {'prefix': 'Orange*'}
        # CALL + RETURN.

        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    RETURN node"""
        expected_results = [node4, node2, node3, node1]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # The combination of CALL and WHERE currently creates a syntax error in libcypher-parser.
        # CALL + WHERE + RETURN + ORDER.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    """
        expected_results = [node3, node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER + SKIP.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value
                    SKIP 1"""
        expected_results = [node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    LIMIT 2"""
        expected_results = [node3, node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER + SKIP + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value
                    SKIP 1
                    LIMIT 1"""
        expected_results = [node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)

        # CALL + RETURN + ORDER.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    RETURN node
                    ORDER BY node.value
                    """
        expected_results = [node1, node2, node3, node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + RETURN + ORDER + SKIP.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    RETURN node
                    ORDER BY node.value
                    SKIP 1
                    """
        expected_results = [node2, node3, node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + RETURN + ORDER + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    RETURN node
                    ORDER BY node.value
                    LIMIT 2
                    """
        expected_results = [node1, node2]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + RETURN + ORDER + SKIP + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    RETURN node
                    ORDER BY node.value
                    SKIP 1
                    LIMIT 1
                    """
        expected_results = [node2]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value"""
        expected_results = [node3, node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER + SKIP.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value
                    SKIP 1"""
        expected_results = [node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value
                    LIMIT 1"""
        expected_results = [node3]
        self.queryAndValidate(query, expected_results, query_params=query_params)


        # CALL + WHERE + RETURN + ORDER + SKIP + LIMIT.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
                    WHERE node.value > 2
                    RETURN node
                    ORDER BY node.value
                    SKIP 1
                    LIMIT 1"""
        expected_results = [node4]
        self.queryAndValidate(query, expected_results, query_params=query_params)

        # CALL + MATCH + RETURN.
        query = """CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node
            MATCH (node)-[]->(z)
            RETURN z"""
        expected_results = [node5]
        self.queryAndValidate(query, expected_results, query_params=query_params)

        # UNWIND + CALL + RETURN.
        query = """UNWIND([1,2]) AS x CALL db.idx.fulltext.queryNodes('fruit', $prefix) YIELD node RETURN node"""
        expected_results = [node4, node2, node3, node1, node4, node2, node3, node1]
        self.queryAndValidate(query, expected_results, query_params=query_params)

    def test_procedure_labels(self):
        actual_resultset = redis_graph.call_procedure("db.labels").result_set
        expected_results = [["fruit"]]        
        self.env.assertEquals(actual_resultset, expected_results)
    
    def test_procedure_relationshipTypes(self):
        actual_resultset = redis_graph.call_procedure("db.relationshipTypes").result_set
        expected_results = [["goWellWith"]]
        self.env.assertEquals(actual_resultset, expected_results)
    
    def test_procedure_propertyKeys(self):
        actual_resultset = redis_graph.call_procedure("db.propertyKeys").result_set
        expected_results = [["name"], ["value"]]
        self.env.assertEquals(actual_resultset, expected_results)

    def test_procedure_fulltext_syntax_error(self):
        try:
            query = """CALL db.idx.fulltext.queryNodes('fruit', 'Orange || Apple') YIELD node RETURN node"""
            redis_graph.query(query)
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    def test_procedure_lookup(self):
        try:
            redis_graph.call_procedure("dB.LaBeLS")
        except redis.exceptions.ResponseError:
            # This should not cause an error
            self.env.assertFalse(1)
            pass

        try:
            # looking for a non existing procedure
            redis_graph.call_procedure("db.nonExistingProc")
            self.env.assertFalse(1)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

        try:
            redis_graph.call_procedure("db.IDX.FulLText.QueRyNoDes", "fruit", "or")
        except redis.exceptions.ResponseError:
            # This should not cause an error
            self.env.assertFalse(1)
            pass

    def test_procedure_get_all_procedures(self):
        actual_resultset = redis_graph.call_procedure("dbms.procedures").result_set

        # The following two procedure are a part of the expected results
        expected_result = [["db.labels", "READ"], ["db.idx.fulltext.createNodeIndex", "WRITE"],
                           ["db.propertyKeys", "READ"], ["dbms.procedures", "READ"], ["db.relationshipTypes", "READ"],
                           ["algo.BFS", "READ"], ["algo.pageRank", "READ"], ["db.idx.fulltext.queryNodes", "READ"],
                           ["db.idx.fulltext.drop", "WRITE"]]
        for res in expected_result:
            self.env.assertContains(res, actual_resultset)
Exemplo n.º 28
0
class testPendingQueryLimit():
    def __init__(self):
        self.env = Env(decodeResponses=True)
        self.conn = self.env.getConnection()

    def test_01_query_limit_config(self):
        # read max queued queries config
        result = self.conn.execute_command("GRAPH.CONFIG", "GET",
                                           "MAX_QUEUED_QUERIES")
        max_queued_queries = result[1]
        self.env.assertEquals(max_queued_queries, 4294967295)

        # update configuration, set max queued queries
        self.conn.execute_command("GRAPH.CONFIG", "SET", "MAX_QUEUED_QUERIES",
                                  10)

        # re-read configuration
        result = self.conn.execute_command("GRAPH.CONFIG", "GET",
                                           "MAX_QUEUED_QUERIES")
        max_queued_queries = result[1]
        self.env.assertEquals(max_queued_queries, 10)

    def stress_server(self):
        threads = []
        connections = []
        threadpool_size = self.conn.execute_command("GRAPH.CONFIG", "GET",
                                                    "THREAD_COUNT")[1]
        thread_count = threadpool_size * 5

        # init connections
        for i in range(thread_count):
            connections.append(self.env.getConnection())

        # invoke queries
        for i in range(thread_count):
            con = connections.pop()
            t = threading.Thread(target=issue_query, args=(con, SLOW_QUERY))
            t.setDaemon(True)
            threads.append(t)
            t.start()

        # wait for threads to return
        for i in range(thread_count):
            t = threads[i]
            t.join()

    def test_02_overflow_no_limit(self):
        global error_encountered
        error_encountered = False

        # no limit on number of pending queries
        limit = 4294967295
        self.conn.execute_command("GRAPH.CONFIG", "SET", "MAX_QUEUED_QUERIES",
                                  limit)

        self.stress_server()

        self.env.assertFalse(error_encountered)

    def test_03_overflow_with_limit(self):
        global error_encountered
        error_encountered = False

        # limit number of pending queries
        limit = 1
        self.conn.execute_command("GRAPH.CONFIG", "SET", "MAX_QUEUED_QUERIES",
                                  limit)

        self.stress_server()

        self.env.assertTrue(error_encountered)
Exemplo n.º 29
0
class testUnion(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global redis_graph
        redis_con = self.env.getConnection()
        redis_graph = Graph(GRAPH_ID, redis_con)
        self.populate_graph()

    def populate_graph(self):
        global redis_graph
        # Construct a graph with the form:
        # (v1)-[:E1]->(v2)-[:E2]->(v3)
        node_props = ['v1', 'v2', 'v3']

        nodes = {}
        for idx, v in enumerate(node_props):
            node = Node(label="L", properties={"v": v})
            nodes[v] = node
            redis_graph.add_node(node)

        edge = Edge(nodes['v1'], "E1", nodes['v2'], properties={"v": "v1_v2"})
        redis_graph.add_edge(edge)

        edge = Edge(nodes['v2'], "E2", nodes['v3'], properties={"v": "v2_v3"})
        redis_graph.add_edge(edge)

        redis_graph.flush()

    def test01_union(self):
        q = """RETURN 1 as one UNION ALL RETURN 1 as one"""
        result = redis_graph.query(q)
        # Expecting 2 identical records.
        self.env.assertEquals(len(result.result_set), 2)
        expected_result = [[1], [1]]
        self.env.assertEquals(result.result_set, expected_result)

        q = """RETURN 1 as one UNION RETURN 1 as one"""
        result = redis_graph.query(q)
        # Expecting a single record, duplicate removed.
        self.env.assertEquals(len(result.result_set), 1)
        expected_result = [[1]]
        self.env.assertEquals(result.result_set, expected_result)

        q = """MATCH a = () return length(a) AS len UNION ALL MATCH b = () RETURN length(b) AS len"""
        result = redis_graph.query(q)
        # 3 records from each sub-query, coresponding to each path matched.
        self.env.assertEquals(len(result.result_set), 6)

    def test02_invalid_union(self):
        try:
            # projection must be exactly the same.
            q = """RETURN 1 as one UNION RETURN 1 as two"""
            redis_graph.query(q)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    # Performing UNION with the same left and right side should
    # produce the same result as evaluating just one side.
    def test03_union_deduplication(self):
        non_union_query = """MATCH (a)-[]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        non_union_result = redis_graph.query(non_union_query)

        union_query = """MATCH (a)-[]->(b) RETURN a.v, b.v ORDER BY a.v, b.v
                         UNION
                         MATCH (a)-[]->(b) RETURN a.v, b.v ORDER BY a.v, b.v"""
        union_result = redis_graph.query(union_query)
        self.env.assertEquals(union_result.result_set,
                              non_union_result.result_set)

    # A syntax error should be raised on edge alias reuse in one side of a union.
    def test04_union_invalid_reused_edge(self):
        try:
            query = """MATCH ()-[e]->()-[e]->() RETURN e
                       UNION
                       MATCH ()-[e]->() RETURN e"""
            redis_graph.query(query)
            assert (False)
        except redis.exceptions.ResponseError:
            # Expecting an error.
            pass

    # An edge alias appearing on both sides of a UNION is expected.
    def test05_union_valid_reused_edge(self):
        query = """MATCH ()-[e]->() RETURN e.v ORDER BY e.v
                   UNION
                   MATCH ()-[e]->() RETURN e.v ORDER BY e.v
                   UNION
                   MATCH ()-[e]->() RETURN e.v ORDER BY e.v"""
        result = redis_graph.query(query)

        expected_result = [["v1_v2"], ["v2_v3"]]
        self.env.assertEquals(result.result_set, expected_result)

    # Union should be capable of collating nodes and edges in a single column.
    def test06_union_nodes_with_edges(self):
        query = """MATCH ()-[e]->() RETURN e
                   UNION
                   MATCH (e) RETURN e"""
        union_result = redis_graph.query(query)

        # All 3 nodes and 2 edges should be returned.
        self.env.assertEquals(len(union_result.result_set), 5)

        query = """MATCH ()-[e]->() RETURN e
                   UNION ALL
                   MATCH (e) RETURN e"""
        union_all_result = redis_graph.query(query)

        # The same results should be produced regardless of whether ALL is specified.
        self.env.assertEquals(union_result.result_set,
                              union_all_result.result_set)
Exemplo n.º 30
0
class testResultSetFlow(FlowTestsBase):
    def __init__(self):
        self.env = Env()
        global graph
        global redis_con
        redis_con = self.env.getConnection()
        graph = Graph("G", redis_con)
        self.populate_graph()

    def populate_graph(self):
        global graph
        nodes = {}
        # Create entities
        for idx, p in enumerate(people):
            node = Node(label="person", properties={"name": p, "val": idx})
            graph.add_node(node)
            nodes[p] = node

        # Fully connected graph
        for src in nodes:
            for dest in nodes:
                if src != dest:
                    edge = Edge(nodes[src], "know", nodes[dest])
                    graph.add_edge(edge)

        graph.commit()

    # Verify that scalar returns function properly
    def test01_return_scalars(self):
        query = """MATCH (a) RETURN a.name, a.val ORDER BY a.val"""
        result = graph.query(query)

        expected_result = [['Roi', 0], ['Alon', 1], ['Ailon', 2], ['Boaz', 3]]

        self.env.assertEquals(len(result.result_set), 4)
        self.env.assertEquals(len(result.header), 2)  # 2 columns in result set
        self.env.assertEquals(result.result_set, expected_result)

    # Verify that full node returns function properly
    def test02_return_nodes(self):
        query = """MATCH (a) RETURN a"""
        result = graph.query(query)

        # TODO add more assertions after updated client format is defined
        self.env.assertEquals(len(result.result_set), 4)
        self.env.assertEquals(len(result.header), 1)  # 1 column in result set

    # Verify that full edge returns function properly
    def test03_return_edges(self):
        query = """MATCH ()-[e]->() RETURN e"""
        result = graph.query(query)

        # TODO add more assertions after updated client format is defined
        self.env.assertEquals(len(result.result_set),
                              12)  # 12 relations (fully connected graph)
        self.env.assertEquals(len(result.header), 1)  # 1 column in result set

    def test04_mixed_returns(self):
        query = """MATCH (a)-[e]->() RETURN a.name, a, e ORDER BY a.val"""
        result = graph.query(query)

        # TODO add more assertions after updated client format is defined
        self.env.assertEquals(len(result.result_set),
                              12)  # 12 relations (fully connected graph)
        self.env.assertEquals(len(result.header), 3)  # 3 columns in result set

    # Verify that the DISTINCT operator works with full entity returns
    def test05_distinct_full_entities(self):
        graph2 = Graph("H", redis_con)
        query = """CREATE (a)-[:e]->(), (a)-[:e]->()"""
        result = graph2.query(query)
        self.env.assertEquals(result.nodes_created, 3)
        self.env.assertEquals(result.relationships_created, 2)

        query = """MATCH (a)-[]->() RETURN a"""
        non_distinct = graph2.query(query)
        query = """MATCH (a)-[]->() RETURN DISTINCT a"""
        distinct = graph2.query(query)

        self.env.assertEquals(len(non_distinct.result_set), 2)
        self.env.assertEquals(len(distinct.result_set), 1)

    # Verify that RETURN * projections include all user-defined aliases.
    def test06_return_all(self):
        query = """MATCH (a)-[e]->(b) RETURN *"""
        result = graph.query(query)
        # Validate the header strings of the 3 columns.
        # NOTE - currently, RETURN * populates values in alphabetical order, but that is subject to later change.
        self.env.assertEqual(result.header[0][1], 'a')
        self.env.assertEqual(result.header[1][1], 'b')
        self.env.assertEqual(result.header[2][1], 'e')
        # Verify that 3 columns are returned
        self.env.assertEqual(len(result.result_set[0]), 3)

    # Tests for aggregation functions default values. Fix for issue 767.
    def test07_agg_func_default_values(self):
        # Test for aggregation over non existing node properties.
        # Max default value is null.
        query = """MATCH (a) return max(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual(None, result.result_set[0][0])

        # Min default value is null.
        query = """MATCH (a) return min(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual(None, result.result_set[0][0])

        # Count default value is 0.
        query = """MATCH (a) return count(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual(0, result.result_set[0][0])

        # Avarage default value is 0.
        query = """MATCH (a) return avg(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual(0, result.result_set[0][0])

        # Collect default value is an empty array.
        query = """MATCH (a) return collect(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual([], result.result_set[0][0])

        # StdDev default value is 0.
        query = """MATCH (a) return stdev(a.missing_field)"""
        result = graph.query(query)
        self.env.assertEqual(0, result.result_set[0][0])

        # percentileCont default value is null.
        query = """MATCH (a) return percentileCont(a.missing_field, 0.1)"""
        result = graph.query(query)
        self.env.assertEqual(None, result.result_set[0][0])

        # percentileDisc default value is null.
        query = """MATCH (a) return percentileDisc(a.missing_field, 0.1)"""
        result = graph.query(query)
        self.env.assertEqual(None, result.result_set[0][0])

    # Test returning multiple occurrence of an expression.
    def test08_return_duplicate_expression(self):
        query = """MATCH (a) RETURN max(a.val), max(a.val)"""
        result = graph.query(query)
        self.env.assertEqual(result.result_set[0][0], result.result_set[0][1])

        query = """MATCH (a) return max(a.val) as x, max(a.val) as x"""
        result = graph.query(query)
        self.env.assertEqual(result.result_set[0][0], result.result_set[0][1])

        query = """MATCH (a) RETURN a.val, a.val LIMIT 1"""
        result = graph.query(query)
        self.env.assertEqual(result.result_set[0][0], result.result_set[0][1])

        query = """MATCH (a) return a.val as x, a.val as x LIMIT 1"""
        result = graph.query(query)
        self.env.assertEqual(result.result_set[0][0], result.result_set[0][1])