diff --git a/tests/schemas/explain_setup.edgeql b/tests/schemas/explain_setup.edgeql index 5ff5a1806f1..463ce0ad51a 100644 --- a/tests/schemas/explain_setup.edgeql +++ b/tests/schemas/explain_setup.edgeql @@ -133,10 +133,18 @@ SET { }; -# Make the database more populated so that it uses indexes... -for i in range_unpack(range(1, 1000)) union ( +# Add a function that disables sequential scan. +create function _set_seqscan(val: std::str) -> std::str { + using sql $$ + select set_config('enable_seqscan', val, true) + $$; +}; + + +# Generate some data ... +for i in range_unpack(range(1, 10)) union ( with u := (insert User { name := i }), - for j in range_unpack(range(0, 5)) union ( + for j in range_unpack(range(0, 3)) union ( insert Issue { owner := u, number := (i*100 + j), @@ -150,7 +158,7 @@ update User set { }; -for x in range_unpack(range(0, 900_000)) union ( +for x in range_unpack(range(0, 100)) union ( insert RangeTest { rval := range(-(x * 101419 % 307), x * 201881 % 307), mval := multirange([ @@ -181,7 +189,7 @@ for x in range_unpack(range(0, 900_000)) union ( ); -for x in range_unpack(range(0, 100_000)) union ( +for x in range_unpack(range(0, 100)) union ( insert JSONTest{val := (a:=x, b:=x * 40123 % 10007)} ); diff --git a/tests/test_edgeql_explain.py b/tests/test_edgeql_explain.py index c8edcad0364..49c739bb796 100644 --- a/tests/test_edgeql_explain.py +++ b/tests/test_edgeql_explain.py @@ -29,7 +29,7 @@ class TestEdgeQLExplain(tb.QueryTestCase): - '''Tests for EXPLAIN. + '''Tests for ANALYZE. This is a good way of testing explain functionality, but also this can be used to test indexes. @@ -54,8 +54,12 @@ def assert_plan(self, data, shape, message=None): data, shape, fail=self.fail, message=message) async def explain(self, query, *, execute=True, con=None): + con = (con or self.con) + # Disable sequential scan so that we hit the index even on small + # datasets. + await con.query_single('select _set_seqscan("off")') no_ex = '(execute := False) ' if not execute else '' - return json.loads(await (con or self.con).query_single( + return json.loads(await con.query_single( f'analyze {no_ex}{query}' )) @@ -110,7 +114,6 @@ async def test_edgeql_explain_simple_01(self): "type": "expr", }, ]), - "startup_cost": float, } ], "subplans": [], @@ -138,214 +141,207 @@ async def test_edgeql_explain_with_bound_01(self): ''') shape = { - "contexts": [ - {"buffer_idx": 0, "end": 35, "start": 31, "text": "User"}], - "pipeline": [ - { - "actual_loops": 1, - "actual_rows": 1, - "plan_rows": 1, - "plan_type": "SubqueryScan", - "properties": tb.bag([ - { - "important": False, - "title": "filter", - "type": "expr", - }, - ]), - "startup_cost": float, - "total_cost": float, - } - ], - "subplans": [ + "contexts": [{ + "start": 31, + "end": 35, + "buffer_idx": 0, + "text": "User", + }], + "pipeline": [{ + "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, + "plan_type": "SubqueryScan", + "properties": [{ + "title": "filter", + "type": "expr", + "important": False, + }], + # Just validating that these fields appear. This was part of + # the early tests and these fields are something the users may + # rely on and should be part of stable API. + "startup_cost": float, + "total_cost": float, + }], + "subplans": tb.bag([ { - "contexts": [ - { - "buffer_idx": 0, - "end": 115, - "start": 74, - } - ], - "pipeline": [ + "contexts": [{ + "start": 74, + "end": 115, + "buffer_idx": 0, + "text": "elvis := (select U filter .name like 'E%'", + }], + "pipeline": tb.bag([ { - "actual_loops": 1, - "actual_rows": 1, "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, "plan_type": "Aggregate", "properties": tb.bag([ { - "important": False, "title": "parent_relationship", "type": "text", - "value": "InitPlan", + "important": False, }, { - "important": False, "title": "subplan_name", "type": "text", - "value": "InitPlan 1 (returns " "$0)", + "important": False, }, { - "important": True, "title": "strategy", - "type": "text", "value": "Plain", + "type": "text", + "important": True, }, { - "important": True, "title": "partial_mode", - "type": "text", "value": "Simple", + "type": "text", + "important": True, }, ]), - "startup_cost": float, - "total_cost": float, }, { - "actual_loops": 1, - "actual_rows": 1, "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, "plan_type": "IndexScan", "properties": tb.bag([ { - "important": False, "title": "filter", "type": "expr", + "important": False, }, { - "important": False, "title": "parent_relationship", - "type": "text", "value": "Outer", + "type": "text", + "important": False, }, { - "important": False, "title": "schema", - "type": "text", "value": "edgedbpub", + "type": "text", + "important": False, }, { - "important": False, "title": "alias", + "value": 'User~3', "type": "text", - "value": "User~3", + "important": False, }, { - "important": True, "title": "relation_name", + "value": 'User', "type": "relation", + "important": True, }, { - "important": True, "title": "scan_direction", - "type": "text", "value": "Forward", + "type": "text", + "important": True, }, { - "important": True, "title": "index_name", + "value": + "index of object type 'default::User' " + "on (__subject__.name)", "type": "index", - "value": "index of object type " - "'default::User' on " - "(__subject__.name)", + "important": True, }, { - "important": False, "title": "index_cond", "type": "expr", + "important": False, }, ]), - "startup_cost": float, - "total_cost": float, }, - ], + ]), "subplans": [], }, { - "contexts": [ - { - "buffer_idx": 0, - "end": 173, - "start": 134, - }, - ], - "pipeline": [ + "contexts": [{ + "start": 134, + "end": 173, + "buffer_idx": 0, + "text": "yury := (select U filter .name[0] = 'Y'", + }], + "pipeline": tb.bag([ { - "actual_loops": 1, - "actual_rows": 1, "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, "plan_type": "Aggregate", "properties": tb.bag([ { - "important": False, "title": "parent_relationship", "type": "text", - "value": "InitPlan", + "important": False, }, { - "important": False, "title": "subplan_name", "type": "text", - "value": "InitPlan 2 (returns " "$1)", + "important": False, }, { - "important": True, "title": "strategy", - "type": "text", "value": "Plain", + "type": "text", + "important": True, }, { - "important": True, "title": "partial_mode", - "type": "text", "value": "Simple", + "type": "text", + "important": True, }, ]), - "startup_cost": float, - "total_cost": float, }, { - "actual_loops": 1, + "plan_rows": 1, "actual_rows": 1, - "plan_rows": 5, + "actual_loops": 1, "plan_type": "SeqScan", "properties": tb.bag([ { - "important": False, "title": "filter", "type": "expr", + "important": False, }, { - "important": False, "title": "parent_relationship", - "type": "text", "value": "Outer", + "type": "text", + "important": False, }, { - "important": False, "title": "schema", - "type": "text", "value": "edgedbpub", + "type": "text", + "important": False, }, { - "important": False, "title": "alias", + "value": 'User~7', "type": "text", + "important": False, }, { - "important": True, "title": "relation_name", + "value": 'User', "type": "relation", + "important": True, }, ]), - "startup_cost": float, - "total_cost": float, - }, - ], + } + ]), "subplans": [], - }, - ], + } + ]), } + self.assert_plan(res['fine_grained'], shape) async def test_edgeql_explain_multi_link_01(self): @@ -355,393 +351,430 @@ async def test_edgeql_explain_multi_link_01(self): ''') shape = { - "contexts": [{ - "buffer_idx": 0, - "end": 32, - "start": 28, - "text": "User", - }], + "contexts": [ + { + "start": 28, + "end": 32, + "buffer_idx": 0, + "text": "User", + }, + ], "pipeline": [ { - "actual_loops": 1, - "actual_rows": 1, "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, "plan_type": "IndexScan", "properties": tb.bag([ { - "important": False, "title": "schema", - "type": "text", "value": "edgedbpub", + "type": "text", + "important": False, }, { - "important": False, "title": "alias", - "type": "text", "value": "User~2", + "type": "text", + "important": False, }, { - "important": True, "title": "relation_name", + "value": "User", "type": "relation", + "important": True, }, { - "important": True, "title": "scan_direction", - "type": "text", "value": "Forward", + "type": "text", + "important": True, }, { - "important": True, "title": "index_name", + "value": + "index of object type 'default::User' " + "on (__subject__.name)", "type": "index", - "value": "index of object type 'default::User' " - "on (__subject__.name)", + "important": True, }, { - "important": False, "title": "index_cond", "type": "expr", + "important": False, }, ]), - } + }, ], "subplans": [ { - "contexts": [{ - "buffer_idx": 0, - "end": 45, - "start": 41, - "text": "todo" - }], - "pipeline": [ + "contexts": [ + { + "start": 41, + "end": 45, + "buffer_idx": 0, + "text": "todo", + }, + ], + "pipeline": tb.bag([ { - "actual_loops": 1, - "actual_rows": 1, "plan_rows": 1, + "actual_rows": 1, + "actual_loops": 1, "plan_type": "Aggregate", "properties": tb.bag([ { - "important": False, "title": "parent_relationship", - "type": "text", "value": "SubPlan", + "type": "text", + "important": False, }, { - "important": False, "title": "subplan_name", "type": "text", - "value": "SubPlan 1", + "important": False, }, { - "important": True, "title": "strategy", - "type": "text", "value": "Plain", + "type": "text", + "important": True, }, { - "important": True, "title": "partial_mode", - "type": "text", "value": "Simple", + "type": "text", + "important": True, }, ]), }, { - "actual_loops": 1, + "plan_rows": 1, "actual_rows": 2, - "plan_rows": 2, + "actual_loops": 1, "plan_type": "NestedLoop", "properties": tb.bag([ { - "important": False, "title": "parent_relationship", - "type": "text", "value": "Outer", + "type": "text", + "important": False, }, { - "important": True, "title": "join_type", - "type": "text", "value": "Inner", + "type": "text", + "important": True, }, ]), }, - ], - "subplans": [ + ]), + "subplans": tb.bag([ { "pipeline": [ { - "actual_loops": 1, + "plan_rows": 1, "actual_rows": 2, - "plan_rows": 2, + "actual_loops": 1, "plan_type": "IndexOnlyScan", - # This has property `heap_fetches` - # that vary on github an locally. - # So skip checking "properties" - } - ], - "subplans": [], - }, - { - "pipeline": [ - { - "actual_loops": 2, - "actual_rows": 1, - "plan_rows": 1, - "plan_type": "IndexScan", "properties": tb.bag([ { - "important": False, "title": "parent_relationship", + "value": "Outer", "type": "text", - "value": "Inner", + "important": False, }, { - "important": False, "title": "schema", - "type": "text", "value": "edgedbpub", + "type": "text", + "important": False, }, { - "important": False, "title": "alias", + "value": "todo~1", "type": "text", - "value": "Issue~1", + "important": False, }, { - "important": True, "title": "relation_name", + "value": "User.todo", "type": "relation", + "important": True, }, { - "important": True, "title": "scan_direction", - "type": "text", "value": "Forward", + "type": "text", + "important": True, }, { - "important": True, "title": "index_name", + "value": + "User.todo forward " + "link index", "type": "index", - "value": "constraint " - "'std::exclusive' " - "of " - "property " - "'id' of " - "object " - "type " - "'default::Issue'", + "important": True, }, { - "important": False, "title": "index_cond", "type": "expr", - "value": '("Issue~1".id ' - "= " - '"todo~1".target)', + "important": False, + }, + { + "title": "heap_fetches", + "type": "float", + "important": False, }, ]), - } + }, ], "subplans": [], }, - ], - } - ], - } - self.assert_plan(res['fine_grained'], shape) - - async def test_edgeql_explain_computed_backlink_01(self): - res = await self.explain(''' - select User { name, owned_issues: {name, number} } - filter .name = 'Elvis'; - ''') - - shape = { - "contexts": [{ - "buffer_idx": 0, - "end": 32, - "start": 28, - "text": "User", - }], - "pipeline": [ - { - "actual_loops": 1, - "actual_rows": 1, - "plan_rows": 1, - "plan_type": "IndexScan", - "properties": tb.bag([ - { - "important": False, - "title": "schema", - "type": "text", - "value": "edgedbpub", - }, - { - "important": False, - "title": "alias", - "type": "text", - "value": "User~2", - }, - { - "important": True, - "title": "relation_name", - "type": "relation", - }, - { - "important": True, - "title": "scan_direction", - "type": "text", - "value": "Forward", - }, - { - "important": True, - "title": "index_name", - "type": "index", - "value": "index of object type 'default::User' " - "on (__subject__.name)", - }, - { - "important": False, - "title": "index_cond", - "type": "expr", - }, - ]), - "startup_cost": float, - "total_cost": float, - } - ], - "subplans": [ - { - "contexts": [ - { - "buffer_idx": 1, - "end": 26, - "start": 0, - "text": ".