diff --git a/pkg/executor/testdata/prepare_suite_out.json b/pkg/executor/testdata/prepare_suite_out.json index 69271a0bc6195..1fb6c26887e45 100644 --- a/pkg/executor/testdata/prepare_suite_out.json +++ b/pkg/executor/testdata/prepare_suite_out.json @@ -1208,9 +1208,15 @@ ], "Plan": [ "TopN_7 1.00 root test.t.b, offset:0, count:1", +<<<<<<< HEAD "└─TableReader_14 1.00 root data:TopN_13", " └─TopN_13 1.00 cop[tikv] test.t.b, offset:0, count:1", " └─TableFullScan_12 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" +======= + "└─TableReader_17 1.00 root data:TopN_16", + " └─TopN_16 1.00 cop[tikv] test.t.b, offset:0, count:1", + " └─TableFullScan_15 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ], "FromCache": "0" }, @@ -1224,9 +1230,15 @@ ], "Plan": [ "TopN_7 5.00 root test.t.b, offset:0, count:5", +<<<<<<< HEAD "└─TableReader_14 5.00 root data:TopN_13", " └─TopN_13 5.00 cop[tikv] test.t.b, offset:0, count:5", " └─TableFullScan_12 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" +======= + "└─TableReader_17 5.00 root data:TopN_16", + " └─TopN_16 5.00 cop[tikv] test.t.b, offset:0, count:5", + " └─TableFullScan_15 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ], "FromCache": "0" }, diff --git a/pkg/planner/cardinality/testdata/cardinality_suite_out.json b/pkg/planner/cardinality/testdata/cardinality_suite_out.json index 918120d673065..e7b2687cb4f35 100644 --- a/pkg/planner/cardinality/testdata/cardinality_suite_out.json +++ b/pkg/planner/cardinality/testdata/cardinality_suite_out.json @@ -1179,7 +1179,8 @@ "TopN 1.00 root test.t.c, offset:0, count:1", "└─IndexMerge 1.00 root type: union", " ├─IndexRangeScan(Build) 510.00 cop[tikv] table:t, index:ib(b) range:[0,50], keep order:false, stats:pseudo", - " ├─IndexRangeScan(Build) 510.00 cop[tikv] table:t, index:ic(c) range:[0,50], keep order:false, stats:pseudo", + " ├─Limit(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan 510.00 cop[tikv] table:t, index:ic(c) range:[0,50], keep order:true, stats:pseudo", " └─TopN(Probe) 1.00 cop[tikv] test.t.c, offset:0, count:1", " └─TableRowIDScan 1017.40 cop[tikv] table:t keep order:false, stats:pseudo" ] diff --git a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json index ed3ad30e2e01f..7e4ea7a74499a 100644 --- a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json +++ b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json @@ -540,23 +540,91 @@ { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ +<<<<<<< HEAD "Projection_7 5001.00 9913298.19 ((((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", "└─Projection_25 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", " └─IndexLookUp_24 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", " ├─Limit_23(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", " │ └─IndexRangeScan_21 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", " └─TableRowIDScan_22(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" +======= + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ] }, { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ limit_to_cop(), QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ +<<<<<<< HEAD "Projection_7 5001.00 9913298.19 ((((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", "└─Projection_21 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", " └─IndexLookUp_20 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", " ├─Limit_19(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", " │ └─IndexRangeScan_17 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", " └─TableRowIDScan_18(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" +======= + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + ] + } + ] + }, + { + "Name": "TestReproHashJoinIssue", + "Cases": [ + { + "SQL": "explain format = 'brief' SELECT COUNT(*) FROM t_small s STRAIGHT_JOIN t_big b ON b.id1 = s.id4 WHERE s.id1 = '10001' AND s.id2 = 0 AND s.id3 = 123456789 AND b.id2 = 10001 AND b.id3 = 0 AND b.id4 = 20991231;", + "Ratio": 0, + "Plan": [ + "HashAgg 1.00 root funcs:count(1)->Column#16", + "└─IndexHashJoin 100000.00 root inner join, inner:IndexReader, outer key:repro_hash_join_issue.t_small.id4, inner key:repro_hash_join_issue.t_big.id1, equal cond:eq(repro_hash_join_issue.t_small.id4, repro_hash_join_issue.t_big.id1)", + " ├─TableReader(Build) 1000.00 root data:Selection", + " │ └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_small.id1, \"10001\"), eq(repro_hash_join_issue.t_small.id2, 0), eq(repro_hash_join_issue.t_small.id3, 123456789)", + " │ └─TableFullScan 1000.00 cop[tikv] table:s keep order:false", + " └─IndexReader(Probe) 100000.00 root index:IndexRangeScan", + " └─IndexRangeScan 100000.00 cop[tikv] table:b, index:idx_id1_id2_id3_id4_id5(id1, id2, id3, id4, id5) range: decided by [eq(repro_hash_join_issue.t_big.id1, repro_hash_join_issue.t_small.id4) eq(repro_hash_join_issue.t_big.id2, 10001) eq(repro_hash_join_issue.t_big.id3, 0) eq(repro_hash_join_issue.t_big.id4, 20991231)], keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' SELECT COUNT(*) FROM t_small s STRAIGHT_JOIN t_big b ON b.id1 = s.id4 WHERE s.id1 = '10001' AND s.id2 = 0 AND s.id3 = 123456789 AND b.id2 = 10001 AND b.id3 = 0 AND b.id4 = 20991231;", + "Ratio": 0.5, + "Plan": [ + "HashAgg 1.00 root funcs:count(1)->Column#16", + "└─HashJoin 100000.00 root inner join, equal:[eq(repro_hash_join_issue.t_small.id4, repro_hash_join_issue.t_big.id1)]", + " ├─TableReader(Build) 1000.00 root data:Selection", + " │ └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_small.id1, \"10001\"), eq(repro_hash_join_issue.t_small.id2, 0), eq(repro_hash_join_issue.t_small.id3, 123456789)", + " │ └─TableFullScan 1000.00 cop[tikv] table:s keep order:false", + " └─IndexReader(Probe) 1000.00 root index:Selection", + " └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_big.id2, 10001), eq(repro_hash_join_issue.t_big.id3, 0), eq(repro_hash_join_issue.t_big.id4, 20991231)", + " └─IndexFullScan 1000.00 cop[tikv] table:b, index:idx_id1_id2_id3_id4_id5(id1, id2, id3, id4, id5) keep order:false" + ] + } + ] + }, + { + "Name": "TestIndexJoinPreferIndexCoversMoreJoinKeyCols", + "Cases": [ + { + "SQL": "explain format ='brief' SELECT \n mp.col1,\n mp.col2,\n mp.col3,\n mp.col4,\n mp.col5,\n mp.col6\nFROM\n mp\n LEFT JOIN ab ON ab.col1 = mp.col2\n AND ab.col2 = mp.col6\nWHERE\n mp.col5 = 'PKR'\n AND mp.col3 = 1\n AND mp.col6 IN ('1685', '2239')\n AND mp.col4 >= 1764788400\n AND mp.col4 <= 1767466799\n AND ab.col3 IN ('cj9dkqw7', 'del_cj9dkqw7')\nORDER BY\n mp.col4 DESC\nLIMIT\n 100;", + "Plan": [ + "TopN 84.77 root test.mp.col4:desc, offset:0, count:100", + "└─IndexHashJoin 84.77 root inner join, inner:IndexLookUp, outer key:test.ab.col1, test.ab.col2, inner key:test.mp.col2, test.mp.col6, equal cond:eq(test.ab.col1, test.mp.col2), eq(test.ab.col2, test.mp.col6)", + " ├─Batch_Point_Get(Build) 4.00 root table:ab, index:idx_1(col3, col2) keep order:false, desc:false", + " └─IndexLookUp(Probe) 84.77 root ", + " ├─Selection(Build) 382.27 cop[tikv] in(test.mp.col6, \"1685\", \"2239\")", + " │ └─IndexRangeScan 382.27 cop[tikv] table:mp, index:idx_1(col2, col6, col7) range: decided by [eq(test.mp.col2, test.ab.col1) eq(test.mp.col6, test.ab.col2)], keep order:false", + " └─Selection(Probe) 84.77 cop[tikv] eq(test.mp.col3, 1), eq(test.mp.col5, \"PKR\"), ge(test.mp.col4, 1764788400), le(test.mp.col4, 1767466799)", + " └─TableRowIDScan 382.27 cop[tikv] table:mp keep order:false" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ] } ] diff --git a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json new file mode 100644 index 0000000000000..ce1b52a0d0d0d --- /dev/null +++ b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json @@ -0,0 +1,657 @@ +[ + { + "Name": "TestCBOWithoutAnalyze", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t1, t2 where t1.a = t2.a", + "Plan": [ + "HashJoin 7.49 root inner join, equal:[eq(test.t1.a, test.t2.a)]", + "├─TableReader(Build) 5.99 root data:Selection", + "│ └─Selection 5.99 cop[tikv] not(isnull(test.t2.a))", + "│ └─TableFullScan 6.00 cop[tikv] table:t2 keep order:false, stats:pseudo", + "└─TableReader(Probe) 5.99 root data:Selection", + " └─Selection 5.99 cop[tikv] not(isnull(test.t1.a))", + " └─TableFullScan 6.00 cop[tikv] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'hint' select * from t1, t2 where t1.a = t2.a", + "Plan": [ + "hash_join_build(`test`.`t2`), use_index(@`sel_1` `test`.`t1` ), use_index(@`sel_1` `test`.`t2` )" + ] + } + ] + }, + { + "Name": "TestTableDual", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t where 1 = 0", + "Plan": [ + "TableDual 0.00 root rows:0" + ] + }, + { + "SQL": "explain format = 'brief' select * from t where 1 = 1 limit 0", + "Plan": [ + "TableDual 0.00 root rows:0" + ] + } + ] + }, + { + "Name": "TestEstimation", + "Cases": [ + { + "SQL": "explain format = 'brief' select count(*) from t group by a", + "Plan": [ + "HashAgg 2.00 root group by:test.t.a, funcs:count(Column#5)->Column#4", + "└─TableReader 2.00 root data:HashAgg", + " └─HashAgg 2.00 cop[tikv] group by:test.t.a, funcs:count(1)->Column#5", + " └─TableFullScan 8.00 cop[tikv] table:t keep order:false" + ] + } + ] + }, + { + "Name": "TestOutdatedAnalyze", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t where a <= 5 and b <= 5", + "EnablePseudoForOutdatedStats": true, + "RatioOfPseudoEstimate": 10, + "Plan": [ + "TableReader 28.80 root data:Selection", + "└─Selection 28.80 cop[tikv] le(test.t.a, 5), le(test.t.b, 5)", + " └─TableFullScan 80.00 cop[tikv] table:t keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' select * from t where a <= 5 and b <= 5", + "EnablePseudoForOutdatedStats": true, + "RatioOfPseudoEstimate": 0.7, + "Plan": [ + "TableReader 8.84 root data:Selection", + "└─Selection 8.84 cop[tikv] le(test.t.a, 5), le(test.t.b, 5)", + " └─TableFullScan 80.00 cop[tikv] table:t keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'brief' select * from t where a <= 5 and b <= 5", + "EnablePseudoForOutdatedStats": false, + "RatioOfPseudoEstimate": 10, + "Plan": [ + "TableReader 28.80 root data:Selection", + "└─Selection 28.80 cop[tikv] le(test.t.a, 5), le(test.t.b, 5)", + " └─TableFullScan 80.00 cop[tikv] table:t keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' select * from t where a <= 5 and b <= 5", + "EnablePseudoForOutdatedStats": false, + "RatioOfPseudoEstimate": 0.7, + "Plan": [ + "TableReader 28.80 root data:Selection", + "└─Selection 28.80 cop[tikv] le(test.t.a, 5), le(test.t.b, 5)", + " └─TableFullScan 80.00 cop[tikv] table:t keep order:false" + ] + } + ] + }, + { + "Name": "TestInconsistentEstimation", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t use index(ab) where a = 5 and c = 5", + "Plan": [ + "IndexLookUp 1.00 root ", + "├─IndexRangeScan(Build) 1.25 cop[tikv] table:t, index:ab(a, b) range:[5,5], keep order:false, stats:partial[ab:unInitialized, ac:unInitialized]", + "└─Selection(Probe) 1.00 cop[tikv] eq(test.t.c, 5)", + " └─TableRowIDScan 1.25 cop[tikv] table:t keep order:false, stats:partial[ab:unInitialized, ac:unInitialized]" + ] + } + ] + }, + { + "Name": "TestLimitCrossEstimation", + "Cases": [ + { + "SQL": [ + "set session tidb_opt_correlation_exp_factor = 0", + "explain format = 'brief' SELECT * FROM t WHERE b = 2 ORDER BY a limit 1;" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─IndexRangeScan 10.00 cop[tikv] table:t, index:idx_bc(b, c) range:[2,2], keep order:false, stats:pseudo" + ] + }, + { + "SQL": [ + "insert into t (a, b) values (1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 1),(8, 1),(9, 1),(10, 1),(11, 1),(12, 1),(13, 1),(14, 1),(15, 1),(16, 1),(17, 1),(18, 1),(19, 1),(20, 2),(21, 2),(22, 2),(23, 2),(24, 2),(25, 2)", + "analyze table t", + "explain format = 'brief' SELECT * FROM t WHERE b = 2 ORDER BY a limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─IndexRangeScan 6.00 cop[tikv] table:t, index:idx_bc(b, c) range:[2,2], keep order:false" + ] + }, + { + "SQL": [ + "truncate table t", + "insert into t (a, b) values (1, 25),(2, 24),(3, 23),(4, 23),(5, 21),(6, 20),(7, 19),(8, 18),(9, 17),(10, 16),(11, 15),(12, 14),(13, 13),(14, 12),(15, 11),(16, 10),(17, 9),(18, 8),(19, 7),(20, 6),(21, 5),(22, 4),(23, 3),(24, 2),(25, 1)", + "analyze table t", + "explain format = 'brief' SELECT * FROM t WHERE b <= 6 ORDER BY a limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─IndexRangeScan 7.00 cop[tikv] table:t, index:idx_bc(b, c) range:[-inf,6], keep order:false" + ] + }, + { + "SQL": [ + "explain format = 'brief' SELECT *, t1.a IN (SELECT t2.b FROM t t2) FROM t t1 WHERE t1.b <= 6 ORDER BY t1.a limit 1" + ], + "Plan": [ + "Limit 1.00 root offset:0, count:1", + "└─MergeJoin 1.00 root left outer semi join, left side:TopN, left key:test.t.a, right key:test.t.b", + " ├─IndexReader(Build) 25.00 root index:IndexFullScan", + " │ └─IndexFullScan 25.00 cop[tikv] table:t2, index:idx_bc(b, c) keep order:true", + " └─TopN(Probe) 1.00 root test.t.a, offset:0, count:1", + " └─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─IndexRangeScan 7.00 cop[tikv] table:t1, index:idx_bc(b, c) range:[-inf,6], keep order:false" + ] + }, + { + "SQL": [ + "truncate table t", + "insert into t (a, b) values (1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 2),(8, 2),(9, 2),(10, 2),(11, 2),(12, 2),(13, 2),(14, 2),(15, 2),(16, 2),(17, 2),(18, 2),(19, 2),(20, 2),(21, 2),(22, 2),(23, 2),(24, 2),(25, 2)", + "analyze table t", + "explain format = 'brief' SELECT * FROM t WHERE b = 1 ORDER BY a desc limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a:desc, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a:desc, offset:0, count:1", + " └─IndexRangeScan 6.00 cop[tikv] table:t, index:idx_bc(b, c) range:[1,1], keep order:false" + ] + }, + { + "SQL": [ + "truncate table t", + "insert into t (a, b) values (1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 1),(8, 1),(9, 2),(10, 1),(11, 1),(12, 1),(13, 1),(14, 2),(15, 2),(16, 1),(17, 2),(18, 1),(19, 2),(20, 1),(21, 2),(22, 1),(23, 1),(24, 1),(25, 1)", + "analyze table t", + "explain format = 'brief' SELECT * FROM t WHERE b = 2 ORDER BY a limit 1" + ], + "Plan": [ + "Limit 1.00 root offset:0, count:1", + "└─TableReader 1.00 root data:Limit", + " └─Limit 1.00 cop[tikv] offset:0, count:1", + " └─Selection 1.00 cop[tikv] eq(test.t.b, 2)", + " └─TableFullScan 4.38 cop[tikv] table:t keep order:true" + ] + }, + { + "SQL": [ + "set session tidb_opt_correlation_exp_factor = 1", + "explain format = 'brief' SELECT * FROM t WHERE b = 2 ORDER BY a limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─IndexRangeScan 6.00 cop[tikv] table:t, index:idx_bc(b, c) range:[2,2], keep order:false" + ] + }, + { + "SQL": [ + "set session tidb_opt_correlation_exp_factor = 0", + "truncate table t", + "insert into t (a, b) values (1, 1),(2, 1),(3, 1),(4, 1),(5, 1),(6, 1),(7, 1),(8, 1),(9, 1),(10, 1),(11, 1),(12, 1),(13, 1),(14, 1),(15, 1),(16, 1),(17, 1),(18, 1),(19, 1),(20, 2),(21, 2),(22, 2),(23, 2),(24, 2),(25, 2)", + "analyze table t", + "explain format = 'brief' SELECT * FROM t WHERE b = 2 and a > 0 ORDER BY a limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─Selection 6.00 cop[tikv] gt(test.t.a, 0)", + " └─IndexRangeScan 6.00 cop[tikv] table:t, index:idx_bc(b, c) range:[2,2], keep order:false" + ] + }, + { + "SQL": [ + "drop table t", + "create table t(a int primary key, b int, c int, d bigint default 2147483648, e bigint default 2147483648, f bigint default 2147483648, index idx(b,d,a,c))", + "insert into t(a, b, c) values (1, 1, 1),(2, 1, 2),(3, 1, 1),(4, 1, 2),(5, 1, 1),(6, 1, 2),(7, 1, 1),(8, 1, 2),(9, 1, 1),(10, 1, 2),(11, 1, 1),(12, 1, 2),(13, 1, 1),(14, 1, 2),(15, 1, 1),(16, 1, 2),(17, 1, 1),(18, 1, 2),(19, 1, 1),(20, 2, 2),(21, 2, 1),(22, 2, 2),(23, 2, 1),(24, 2, 2),(25, 2, 1)", + "analyze table t", + "explain format = 'brief' SELECT a FROM t WHERE b = 2 and c > 0 ORDER BY a limit 1" + ], + "Plan": [ + "TopN 1.00 root test.t.a, offset:0, count:1", + "└─IndexReader 1.00 root index:TopN", + " └─TopN 1.00 cop[tikv] test.t.a, offset:0, count:1", + " └─Selection 7.00 cop[tikv] gt(test.t.c, 0)", + " └─IndexRangeScan 7.00 cop[tikv] table:t, index:idx(b, d, a, c) range:[2,2], keep order:false" + ] + } + ] + }, + { + "Name": "TestIssue9562", + "Cases": [ + { + "SQL": [ + "create table t1(a bigint, b bigint, c bigint)", + "create table t2(a bigint, b bigint, c bigint, index idx(a, b, c))", + "explain format = 'brief' select /*+ TIDB_INLJ(t2) */ * from t1 join t2 on t2.a=t1.a and t2.b>t1.b-1 and t2.bColumn#26", + " └─HashJoin 10.00 root inner join, equal:[eq(test.t.a, test.t.a)]", + " ├─IndexReader(Build) 10.00 root index:Selection", + " │ └─Selection 10.00 cop[tikv] eq(test.t.a, test.t.a), not(isnull(test.t.a))", + " │ └─IndexFullScan 100.00 cop[tikv] table:t1, index:idx(c, b, a) keep order:false", + " └─IndexReader(Probe) 10.00 root index:Selection", + " └─Selection 10.00 cop[tikv] eq(test.t.a, test.t.a), not(isnull(test.t.a))", + " └─IndexFullScan 100.00 cop[tikv] table:s, index:idx(c, b, a) keep order:false" + ], + [ + "Projection 10.00 root Column#16", + "└─Apply 10.00 root CARTESIAN left outer join, left side:IndexReader", + " ├─IndexReader(Build) 10.00 root index:IndexFullScan", + " │ └─IndexFullScan 10.00 cop[tikv] table:t, index:idx(c, b, a) keep order:false", + " └─MaxOneRow(Probe) 10.00 root ", + " └─Projection 10.00 root concat(cast(test.t.a, var_string(20)), ,, cast(test.t.b, var_string(20)))->Column#16", + " └─IndexReader 10.00 root index:Selection", + " └─Selection 10.00 cop[tikv] eq(test.t.a, test.t.a)", + " └─IndexRangeScan 10.00 cop[tikv] table:t1, index:idx(c, b, a) range: decided by [eq(test.t.c, test.t.c)], keep order:false" + ] + ] + }, + { + "Name": "TestLowSelIndexGreedySearch", + "Cases": [ + { + "SQL": "explain format = 'brief' select max(e) from t where a='T3382' and b='ECO' and c='TOPIC' and d='23660fa1ace9455cb7f3ee831e14a342'", + "Plan": [ + "StreamAgg 1.00 root funcs:max(test.t.e)->Column#8", + "└─TopN 1.00 root test.t.e:desc, offset:0, count:1", + " └─IndexLookUp 1.00 root ", + " ├─IndexRangeScan(Build) 1.25 cop[tikv] table:t, index:idx1(d, a) range:[\"23660fa1ace9455cb7f3ee831e14a342\" \"T3382\",\"23660fa1ace9455cb7f3ee831e14a342\" \"T3382\"], keep order:false", + " └─Selection(Probe) 1.00 cop[tikv] eq(test.t.b, \"ECO\"), eq(test.t.c, \"TOPIC\"), not(isnull(test.t.e))", + " └─TableRowIDScan 1.25 cop[tikv] table:t keep order:false" + ] + } + ] + }, + { + "Name": "TestEmptyTable", + "Cases": [ + "TableReader(Table(t)->Sel([le(test.t.c1, 50)]))", + "LeftHashJoin{TableReader(Table(t)->Sel([not(isnull(test.t.c1))]))->TableReader(Table(t1)->Sel([not(isnull(test.t1.c1))])->HashAgg)->HashAgg}(test.t.c1,test.t1.c1)", + "LeftHashJoin{TableReader(Table(t)->Sel([not(isnull(test.t.c1))]))->TableReader(Table(t1)->Sel([not(isnull(test.t1.c1))]))}(test.t.c1,test.t1.c1)", + "Dual" + ] + }, + { + "Name": "TestIndexRead", + "Cases": [ + "IndexReader(Index(t.e)[[NULL,+inf]]->StreamAgg)->StreamAgg", + "IndexReader(Index(t.e)[[-inf,10]]->StreamAgg)->StreamAgg", + "IndexReader(Index(t.e)[[-inf,50]]->StreamAgg)->StreamAgg", + "IndexReader(Index(t.b_c)[[NULL,+inf]]->Sel([gt(test.t.c, 1)])->StreamAgg)->StreamAgg", + "IndexLookUp(Index(t.e)[[1,1]], Table(t))->HashAgg", + "TableReader(Table(t)->Sel([gt(test.t.e, 1)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 20)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 30)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 40)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 50)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 100000000000)])->HashAgg)->HashAgg", + "TableReader(Table(t)->Sel([le(test.t.b, 40)]))", + "TableReader(Table(t)->Sel([le(test.t.b, 50)]))", + "TableReader(Table(t)->Sel([le(test.t.b, 10000000000)]))", + "TableReader(Table(t)->Sel([le(test.t.b, 50)]))", + "TableReader(Table(t)->Sel([le(test.t.b, 100)])->Limit)->Limit", + "TableReader(Table(t)->Sel([le(test.t.b, 1)])->Limit)->Limit", + "IndexLookUp(Index(t.b)[[1,1]], Table(t))", + "IndexLookUp(Index(t.d)[[-inf,1991-09-05 00:00:00)], Table(t))", + "IndexLookUp(Index(t.ts)[[-inf,1991-09-05 00:00:00)], Table(t))", + "IndexLookUp(Index(t1.idx)[[3,3]], Table(t1)->Sel([eq(test.t1.b, 100000)]))->Projection->Projection->StreamAgg->Limit" + ] + }, + { + "Name": "TestAnalyze", + "Cases": [ + "Analyze{Table(_tidb_rowid, a, b, _tidb_rowid)}", + "TableReader(Table(t)->Sel([le(test.t.a, 2)]))", + "IndexReader(Index(t.b)[[-inf,2)])", + "TableReader(Table(t)->Sel([eq(test.t.a, 1) le(test.t.b, 2)]))", + "TableReader(Table(t1)->Sel([le(test.t1.a, 2)]))", + "IndexLookUp(Index(t1.a)[[1,1]], Table(t1)->Sel([le(test.t1.b, 2)]))", + "TableReader(Table(t2)->Sel([le(test.t2.a, 2)]))", + "Analyze{Table(_tidb_rowid, a, b, _tidb_rowid)}", + "PartitionUnionAll{TableReader(Table(t4)->Sel([le(test.t4.a, 2)]))->TableReader(Table(t4)->Sel([le(test.t4.a, 2)]))}", + "PartitionUnionAll{IndexReader(Index(t4.b)[[-inf,2)])->IndexReader(Index(t4.b)[[-inf,2)])}", + "TableReader(Table(t4)->Sel([eq(test.t4.a, 1) le(test.t4.b, 2)]))" + ] + }, + { + "Name": "TestIndexEqualUnknown", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t where a = 7639902", + "Plan": [ + "IndexReader 5.95 root index:IndexRangeScan", + "└─IndexRangeScan 5.95 cop[tikv] table:t, index:PRIMARY(a, c, b) range:[7639902,7639902], keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' select c, b from t where a = 7639902 order by b asc limit 6", + "Plan": [ + "Projection 5.95 root test.t.c, test.t.b", + "└─TopN 5.95 root test.t.b, offset:0, count:6", + " └─IndexReader 5.95 root index:TopN", + " └─TopN 5.95 cop[tikv] test.t.b, offset:0, count:6", + " └─IndexRangeScan 5.95 cop[tikv] table:t, index:PRIMARY(a, c, b) range:[7639902,7639902], keep order:false" + ] + } + ] + }, + { + "Name": "TestLimitIndexEstimation", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from t where a <= 10000 order by b limit 1", + "Plan": [ + "TopN 1.00 root test.t.b, offset:0, count:1", + "└─TableReader 1.00 root data:TopN", + " └─TopN 1.00 cop[tikv] test.t.b, offset:0, count:1", + " └─Selection 10001.00 cop[tikv] le(test.t.a, 10000)", + " └─TableFullScan 1000000.00 cop[tikv] table:t keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' select * from t where a >= 999900 order by b limit 1", + "Plan": [ + "TopN 1.00 root test.t.b, offset:0, count:1", + "└─IndexLookUp 1.00 root ", + " ├─IndexRangeScan(Build) 102.00 cop[tikv] table:t, index:idx_a(a) range:[999900,+inf], keep order:false", + " └─TopN(Probe) 1.00 cop[tikv] test.t.b, offset:0, count:1", + " └─TableRowIDScan 102.00 cop[tikv] table:t keep order:false" + ] + } + ] + }, + { + "Name": "TestIssue59563", + "Cases": [ + { + "SQL": "EXPLAIN format = 'brief' SELECT * FROM `tbl_cardcore_transaction` `transactio0_` WHERE `transactio0_`.`period` = '202502' AND `transactio0_`.`account_number` = '1901040107462200' ORDER BY `transactio0_`.`transaction_status`, `transactio0_`.`account_number`, `transactio0_`.`entry_date` ASC, `transactio0_`.`id` ASC;", + "Plan": [ + "Sort 1.00 root cardcore_issuing.tbl_cardcore_transaction.transaction_status, cardcore_issuing.tbl_cardcore_transaction.account_number, cardcore_issuing.tbl_cardcore_transaction.entry_date, cardcore_issuing.tbl_cardcore_transaction.id", + "└─IndexLookUp 1.00 root ", + " ├─IndexRangeScan(Build) 16.16 cop[tikv] table:transactio0_, index:tbl_cardcore_transaction_ix10(account_number, entry_date, value_date) range:[\"1901040107462200\",\"1901040107462200\"], keep order:false", + " └─Selection(Probe) 1.00 cop[tikv] eq(cardcore_issuing.tbl_cardcore_transaction.period, \"202502\")", + " └─TableRowIDScan 16.16 cop[tikv] table:transactio0_ keep order:false" + ], + "Warn": null + } + ] + }, + { + "Name": "TestIssue61389", + "Cases": [ + { + "SQL": "EXPLAIN format=brief select /*+ stream_agg(), hash_join(t19f3e4f1) */ * from `t19f3e4f1` where `colc864` in ( select /*+ use_index(t19f3e4f1, ee56e6aa) */ `colc864` from `t19f3e4f1` where `colaadb` in ( select `colf2af`\n from `t0da79f8d` where not ( `t19f3e4f1`.`colc864` <> null ) ) ) limit 2837;", + "Plan": [ + "Limit 2.00 root offset:0, count:2837", + "└─HashJoin 2.00 root inner join, equal:[eq(test.t19f3e4f1.colc864, test.t19f3e4f1.colc864)]", + " ├─StreamAgg(Build) 1.60 root group by:test.t19f3e4f1.colc864, funcs:firstrow(test.t19f3e4f1.colc864)->test.t19f3e4f1.colc864", + " │ └─Apply 1.60 root semi join, left side:Projection, equal:[eq(test.t19f3e4f1.colaadb, test.t0da79f8d.colf2af)]", + " │ ├─Projection(Build) 2.00 root test.t19f3e4f1.colc864, test.t19f3e4f1.colaadb", + " │ │ └─IndexLookUp 2.00 root ", + " │ │ ├─IndexFullScan(Build) 2.00 cop[tikv] table:t19f3e4f1, index:ee56e6aa(colc864) keep order:true, stats:pseudo", + " │ │ └─Selection(Probe) 2.00 cop[tikv] not(isnull(test.t19f3e4f1.colaadb))", + " │ │ └─TableRowIDScan 2.00 cop[tikv] table:t19f3e4f1 keep order:false, stats:pseudo", + " │ └─TableDual(Probe) 0.00 root rows:0", + " └─IndexLookUp(Probe) 2.00 root ", + " ├─IndexFullScan(Build) 2.00 cop[tikv] table:t19f3e4f1, index:ee56e6aa(colc864) keep order:false, stats:pseudo", + " └─TableRowIDScan(Probe) 2.00 cop[tikv] table:t19f3e4f1 keep order:false, stats:pseudo" + ], + "Warn": null + }, + { + "SQL": "select /*+ stream_agg(), hash_join(t19f3e4f1) */ * from `t19f3e4f1` where `colc864` in ( select /*+ use_index(t19f3e4f1, ee56e6aa) */ `colc864` from `t19f3e4f1` where `colaadb` in ( select `colf2af`\n from `t0da79f8d` where not ( `t19f3e4f1`.`colc864` <> null ) ) ) limit 2837;", + "Plan": null, + "Warn": null + } + ] + }, + { + "Name": "TestIssue61792", + "Cases": [ + { + "SQL": "explain format = 'brief' select * from tbl_cardcore_statement s where s.latest_stmt_print_date = '2024-10-16';", + "Plan": [ + "IndexLookUp 53778.89 root ", + "├─IndexRangeScan(Build) 53778.89 cop[tikv] table:s, index:tbl_cardcore_statement_ix7(latest_stmt_print_date) range:[2024-10-16,2024-10-16], keep order:false", + "└─TableRowIDScan(Probe) 53778.89 cop[tikv] table:s keep order:false" + ], + "Warn": null + } + ] + }, + { + "Name": "TestIssue62438", + "Cases": [ + { + "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", + "Plan": [ + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + ] + }, + { + "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ limit_to_cop(), QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", + "Plan": [ + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + ] + } + ] + }, + { + "Name": "TestReproHashJoinIssue", + "Cases": [ + { + "SQL": "explain format = 'brief' SELECT COUNT(*) FROM t_small s STRAIGHT_JOIN t_big b ON b.id1 = s.id4 WHERE s.id1 = '10001' AND s.id2 = 0 AND s.id3 = 123456789 AND b.id2 = 10001 AND b.id3 = 0 AND b.id4 = 20991231;", + "Ratio": 0, + "Plan": [ + "HashAgg 1.00 root funcs:count(1)->Column#16", + "└─IndexHashJoin 100000.00 root inner join, inner:IndexReader, outer key:repro_hash_join_issue.t_small.id4, inner key:repro_hash_join_issue.t_big.id1, equal cond:eq(repro_hash_join_issue.t_small.id4, repro_hash_join_issue.t_big.id1)", + " ├─TableReader(Build) 1000.00 root data:Selection", + " │ └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_small.id1, \"10001\"), eq(repro_hash_join_issue.t_small.id2, 0), eq(repro_hash_join_issue.t_small.id3, 123456789)", + " │ └─TableFullScan 1000.00 cop[tikv] table:s keep order:false", + " └─IndexReader(Probe) 100000.00 root index:IndexRangeScan", + " └─IndexRangeScan 100000.00 cop[tikv] table:b, index:idx_id1_id2_id3_id4_id5(id1, id2, id3, id4, id5) range: decided by [eq(repro_hash_join_issue.t_big.id1, repro_hash_join_issue.t_small.id4) eq(repro_hash_join_issue.t_big.id2, 10001) eq(repro_hash_join_issue.t_big.id3, 0) eq(repro_hash_join_issue.t_big.id4, 20991231)], keep order:false" + ] + }, + { + "SQL": "explain format = 'brief' SELECT COUNT(*) FROM t_small s STRAIGHT_JOIN t_big b ON b.id1 = s.id4 WHERE s.id1 = '10001' AND s.id2 = 0 AND s.id3 = 123456789 AND b.id2 = 10001 AND b.id3 = 0 AND b.id4 = 20991231;", + "Ratio": 0.5, + "Plan": [ + "HashAgg 1.00 root funcs:count(1)->Column#16", + "└─HashJoin 100000.00 root inner join, equal:[eq(repro_hash_join_issue.t_small.id4, repro_hash_join_issue.t_big.id1)]", + " ├─TableReader(Build) 1000.00 root data:Selection", + " │ └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_small.id1, \"10001\"), eq(repro_hash_join_issue.t_small.id2, 0), eq(repro_hash_join_issue.t_small.id3, 123456789)", + " │ └─TableFullScan 1000.00 cop[tikv] table:s keep order:false", + " └─IndexReader(Probe) 1000.00 root index:Selection", + " └─Selection 1000.00 cop[tikv] eq(repro_hash_join_issue.t_big.id2, 10001), eq(repro_hash_join_issue.t_big.id3, 0), eq(repro_hash_join_issue.t_big.id4, 20991231)", + " └─IndexFullScan 1000.00 cop[tikv] table:b, index:idx_id1_id2_id3_id4_id5(id1, id2, id3, id4, id5) keep order:false" + ] + } + ] + }, + { + "Name": "TestIndexJoinPreferIndexCoversMoreJoinKeyCols", + "Cases": [ + { + "SQL": "explain format ='brief' SELECT \n mp.col1,\n mp.col2,\n mp.col3,\n mp.col4,\n mp.col5,\n mp.col6\nFROM\n mp\n LEFT JOIN ab ON ab.col1 = mp.col2\n AND ab.col2 = mp.col6\nWHERE\n mp.col5 = 'PKR'\n AND mp.col3 = 1\n AND mp.col6 IN ('1685', '2239')\n AND mp.col4 >= 1764788400\n AND mp.col4 <= 1767466799\n AND ab.col3 IN ('cj9dkqw7', 'del_cj9dkqw7')\nORDER BY\n mp.col4 DESC\nLIMIT\n 100;", + "Plan": [ + "TopN 84.77 root test.mp.col4:desc, offset:0, count:100", + "└─IndexHashJoin 84.77 root inner join, inner:IndexLookUp, outer key:test.ab.col1, test.ab.col2, inner key:test.mp.col2, test.mp.col6, equal cond:eq(test.ab.col1, test.mp.col2), eq(test.ab.col2, test.mp.col6)", + " ├─Batch_Point_Get(Build) 4.00 root table:ab, index:idx_1(col3, col2) keep order:false, desc:false", + " └─IndexLookUp(Probe) 84.77 root ", + " ├─Selection(Build) 382.27 cop[tikv] in(test.mp.col6, \"1685\", \"2239\")", + " │ └─IndexRangeScan 382.27 cop[tikv] table:mp, index:idx_1(col2, col6, col7) range: decided by [eq(test.mp.col2, test.ab.col1) eq(test.mp.col6, test.ab.col2)], keep order:false", + " └─Selection(Probe) 84.77 cop[tikv] eq(test.mp.col3, 1), eq(test.mp.col5, \"PKR\"), ge(test.mp.col4, 1764788400), le(test.mp.col4, 1767466799)", + " └─TableRowIDScan 382.27 cop[tikv] table:mp keep order:false" + ] + } + ] + } +] diff --git a/pkg/planner/core/casetest/testdata/integration_suite_out.json b/pkg/planner/core/casetest/testdata/integration_suite_out.json index d6c8bb918ec52..b0cfc24ed3c44 100644 --- a/pkg/planner/core/casetest/testdata/integration_suite_out.json +++ b/pkg/planner/core/casetest/testdata/integration_suite_out.json @@ -67,18 +67,30 @@ "SQL": "explain format = 'verbose' select * from t3 order by a limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.a, offset:0, count:1", +<<<<<<< HEAD "└─TableReader_15 1.00 54.34 root data:TopN_14", " └─TopN_14 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", " └─TableFullScan_13 3.00 681.92 cop[tikv] table:t3 keep order:false" +======= + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ] }, { "SQL": "explain format = 'verbose' select * from t3 order by b limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.b, offset:0, count:1", +<<<<<<< HEAD "└─TableReader_15 1.00 54.34 root data:TopN_14", " └─TopN_14 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", " └─TableFullScan_13 3.00 681.92 cop[tikv] table:t3 keep order:false" +======= + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) ] }, { diff --git a/pkg/planner/core/casetest/testdata/integration_suite_xut.json b/pkg/planner/core/casetest/testdata/integration_suite_xut.json new file mode 100644 index 0000000000000..fb6561bd583b4 --- /dev/null +++ b/pkg/planner/core/casetest/testdata/integration_suite_xut.json @@ -0,0 +1,1380 @@ +[ + { + "Name": "TestVerboseExplain", + "Cases": [ + { + "SQL": "explain format = 'verbose' select count(*) from t3", + "Plan": [ + "StreamAgg_20 1.00 102.69 root funcs:count(Column#10)->Column#5", + "└─IndexReader_21 1.00 52.79 root index:StreamAgg_8", + " └─StreamAgg_8 1.00 760.20 cop[tikv] funcs:count(1)->Column#10", + " └─IndexFullScan_19 3.00 610.50 cop[tikv] table:t3, index:c(b) keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select count(*) from t2", + "Plan": [ + "StreamAgg_27 1.00 107.45 root funcs:count(Column#8)->Column#5", + "└─TableReader_28 1.00 57.55 root data:StreamAgg_10", + " └─StreamAgg_10 1.00 831.62 cop[tikv] funcs:count(1)->Column#8", + " └─TableFullScan_25 3.00 681.92 cop[tikv] table:t2 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select * from t3 order by a", + "Plan": [ + "Sort_4 3.00 318.27 root test.t3.a", + "└─TableReader_8 3.00 70.81 root data:TableFullScan_7", + " └─TableFullScan_7 3.00 681.92 cop[tikv] table:t3 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select * from t3 order by b", + "Plan": [ + "Sort_4 3.00 318.27 root test.t3.b", + "└─TableReader_8 3.00 70.81 root data:TableFullScan_7", + " └─TableFullScan_7 3.00 681.92 cop[tikv] table:t3 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select * from t3 order by a limit 1", + "Plan": [ + "TopN_7 1.00 60.74 root test.t3.a, offset:0, count:1", + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select * from t3 order by b limit 1", + "Plan": [ + "TopN_7 1.00 60.74 root test.t3.b, offset:0, count:1", + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select count(*) from t2 group by a", + "Plan": [ + "HashAgg_8 3.00 1729.13 root group by:test.t2.a, funcs:count(1)->Column#5", + "└─TableReader_17 3.00 58.13 root data:TableFullScan_16", + " └─TableFullScan_16 3.00 681.92 cop[tikv] table:t2 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select count(*) from t3 where b = 0", + "Plan": [ + "StreamAgg_10 1.00 64.98 root funcs:count(1)->Column#5", + "└─IndexReader_15 1.00 15.08 root index:IndexRangeScan_14", + " └─IndexRangeScan_14 1.00 162.80 cop[tikv] table:t3, index:c(b) range:[0,0], keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select /*+ use_index(t3, c) */ count(a) from t3 where b = 0", + "Plan": [ + "StreamAgg_10 1.00 2001.63 root funcs:count(test.t3.a)->Column#5", + "└─IndexLookUp_17 1.00 1951.73 root ", + " ├─IndexRangeScan_15(Build) 1.00 203.50 cop[tikv] table:t3, index:c(b) range:[0,0], keep order:false", + " └─TableRowIDScan_16(Probe) 1.00 227.31 cop[tikv] table:t3 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select count(*) from t2 where a = 0", + "Plan": [ + "StreamAgg_12 1.00 109.57 root funcs:count(1)->Column#5", + "└─TableReader_20 1.00 59.67 root data:Selection_19", + " └─Selection_19 1.00 831.62 cop[tikv] eq(test.t2.a, 0)", + " └─TableFullScan_18 3.00 681.92 cop[tikv] table:t2 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select count(*) from t3 t join t3 on t.a = t3.b", + "Plan": [ + "StreamAgg_14 1.00 2128.93 root funcs:count(1)->Column#9", + "└─HashJoin_37 3.00 1979.23 root inner join, equal:[eq(test.t3.a, test.t3.b)]", + " ├─IndexReader_36(Build) 3.00 45.23 root index:IndexFullScan_35", + " │ └─IndexFullScan_35 3.00 488.40 cop[tikv] table:t3, index:c(b) keep order:false", + " └─TableReader_31(Probe) 3.00 68.11 root data:Selection_30", + " └─Selection_30 3.00 831.62 cop[tikv] not(isnull(test.t3.a))", + " └─TableFullScan_29 3.00 681.92 cop[tikv] table:t keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select /*+ read_from_storage(tiflash[t1, t2]) */ count(*) from t1 join t2 on t1.a = t2.a", + "Plan": [ + "StreamAgg_16 1.00 62049.70 root funcs:count(1)->Column#9", + "└─TableReader_71 3.00 61900.00 root MppVersion: 3, data:ExchangeSender_70", + " └─ExchangeSender_70 3.00 928447.21 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection_69 3.00 928447.21 mpp[tiflash] test.t1.a", + " └─HashJoin_54 3.00 928447.20 mpp[tiflash] inner join, equal:[eq(test.t1.a, test.t2.a)]", + " ├─ExchangeReceiver_35(Build) 3.00 464290.40 mpp[tiflash] ", + " │ └─ExchangeSender_34 3.00 464146.40 mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection_33 3.00 464146.40 mpp[tiflash] not(isnull(test.t1.a))", + " │ └─TableFullScan_32 3.00 464139.20 mpp[tiflash] table:t1 keep order:false", + " └─Selection_37(Probe) 3.00 464146.40 mpp[tiflash] not(isnull(test.t2.a))", + " └─TableFullScan_36 3.00 464139.20 mpp[tiflash] table:t2 keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select /*+ read_from_storage(tiflash[t1, t2]) */ count(*) from t1 join t2 on t1.a = t2.a join t3 on t1.b = t3.b", + "Plan": [ + "StreamAgg_21 1.00 71701.80 root funcs:count(1)->Column#13", + "└─HashJoin_82 3.00 71552.10 root inner join, equal:[eq(test.t1.b, test.t3.b)]", + " ├─TableReader_59(Build) 3.00 69645.79 root MppVersion: 3, data:ExchangeSender_58", + " │ └─ExchangeSender_58 3.00 1044634.01 mpp[tiflash] ExchangeType: PassThrough", + " │ └─Projection_57 3.00 1044634.01 mpp[tiflash] test.t1.b", + " │ └─HashJoin_36 3.00 1044634.00 mpp[tiflash] inner join, equal:[eq(test.t1.a, test.t2.a)]", + " │ ├─ExchangeReceiver_54(Build) 3.00 580476.40 mpp[tiflash] ", + " │ │ └─ExchangeSender_53 3.00 580188.40 mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ │ └─Selection_52 3.00 580188.40 mpp[tiflash] not(isnull(test.t1.a)), not(isnull(test.t1.b))", + " │ │ └─TableFullScan_51 3.00 580174.00 mpp[tiflash] table:t1 keep order:false", + " │ └─Selection_56(Probe) 3.00 464146.40 mpp[tiflash] not(isnull(test.t2.a))", + " │ └─TableFullScan_55 3.00 464139.20 mpp[tiflash] table:t2 keep order:false", + " └─IndexReader_80(Probe) 3.00 45.23 root index:IndexFullScan_79", + " └─IndexFullScan_79 3.00 488.40 cop[tikv] table:t3, index:c(b) keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select (2) in (select /*+ read_from_storage(tiflash[t1]) */ count(*) from t1) from (select t.b < (select /*+ read_from_storage(tiflash[t2]) */ t.b from t2 limit 1 ) from t3 t) t; -- we do generate the agg pushed-down plan of mpp, but cost-cmp failed", + "Plan": [ + "HashJoin_17 3.00 32781.07 root CARTESIAN left outer semi join, left side:IndexReader_27", + "├─Selection_40(Build) 0.80 31149.25 root eq(2, Column#23)", + "│ └─StreamAgg_47 1.00 31099.35 root funcs:count(1)->Column#23", + "│ └─TableReader_59 3.00 30949.65 root MppVersion: 3, data:ExchangeSender_58", + "│ └─ExchangeSender_58 3.00 464139.20 mpp[tiflash] ExchangeType: PassThrough", + "│ └─TableFullScan_57 3.00 464139.20 mpp[tiflash] table:t1 keep order:false", + "└─IndexReader_27(Probe) 3.00 53.37 root index:IndexFullScan_26", + " └─IndexFullScan_26 3.00 610.50 cop[tikv] table:t, index:c(b) keep order:false" + ] + }, + { + "SQL": "explain format = 'verbose' select /*+ merge_join(t1), read_from_storage(tiflash[t1, t2]) */ count(*) from t1 join t2 on t1.a = t2.a", + "Plan": [ + "StreamAgg_16 1.00 62546.70 root funcs:count(1)->Column#9", + "└─MergeJoin_34 3.00 62397.00 root inner join, left key:test.t1.a, right key:test.t2.a", + " ├─Sort_30(Build) 3.00 31197.00 root test.t2.a", + " │ └─TableReader_29 3.00 30950.13 root MppVersion: 3, data:ExchangeSender_28", + " │ └─ExchangeSender_28 3.00 464146.40 mpp[tiflash] ExchangeType: PassThrough", + " │ └─Selection_27 3.00 464146.40 mpp[tiflash] not(isnull(test.t2.a))", + " │ └─TableFullScan_26 3.00 464139.20 mpp[tiflash] table:t2 keep order:false", + " └─Sort_25(Probe) 3.00 31197.00 root test.t1.a", + " └─TableReader_24 3.00 30950.13 root MppVersion: 3, data:ExchangeSender_23", + " └─ExchangeSender_23 3.00 464146.40 mpp[tiflash] ExchangeType: PassThrough", + " └─Selection_22 3.00 464146.40 mpp[tiflash] not(isnull(test.t1.a))", + " └─TableFullScan_21 3.00 464139.20 mpp[tiflash] table:t1 keep order:false" + ] + }, + { + "SQL": "set @@session.tidb_allow_tiflash_cop=ON", + "Plan": null + }, + { + "SQL": "explain format = 'plan_tree' /*+ set_var(tidb_allow_mpp=0) */ select count(*) from t31240;", + "Plan": [ + "StreamAgg root funcs:count(Column)->Column", + "└─TableReader root data:StreamAgg", + " └─StreamAgg batchCop[tiflash] funcs:count(test.t31240._tidb_rowid)->Column", + " └─TableFullScan batchCop[tiflash] table:t31240 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' /*+ set_var(tidb_isolation_read_engines='tiflash,tidb'), set_var(tidb_allow_mpp=0) */ select count(*) from t31240;", + "Plan": [ + "StreamAgg root funcs:count(Column)->Column", + "└─TableReader root data:StreamAgg", + " └─StreamAgg batchCop[tiflash] funcs:count(test.t31240._tidb_rowid)->Column", + " └─TableFullScan batchCop[tiflash] table:t31240 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "set @@session.tidb_allow_tiflash_cop=OFF", + "Plan": null + }, + { + "SQL": "explain format = 'plan_tree' /*+ set_var(tidb_allow_mpp=1), set_var(tidb_enforce_mpp=1) */ select sum(ps_supplycost) from partsupp, supplier where ps_suppkey = s_suppkey;", + "Plan": [ + "HashAgg root funcs:sum(Column)->Column", + "└─TableReader root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─HashAgg mpp[tiflash] funcs:sum(test.partsupp.ps_supplycost)->Column", + " └─Projection mpp[tiflash] test.partsupp.ps_supplycost", + " └─HashJoin mpp[tiflash] inner join, equal:[eq(test.supplier.s_suppkey, test.partsupp.ps_suppkey)]", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─TableFullScan mpp[tiflash] table:supplier keep order:false, stats:pseudo", + " └─TableFullScan(Probe) mpp[tiflash] table:partsupp keep order:false, stats:pseudo" + ] + } + ] + }, + { + "Name": "TestIsolationReadDoNotFilterSystemDB", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select * from metrics_schema.tidb_query_duration where time >= '2019-12-23 16:10:13' and time <= '2019-12-23 16:30:13'", + "Plan": [ + "MemTableScan root table:tidb_query_duration PromQL:histogram_quantile(0.9, sum(rate(tidb_server_handle_query_duration_seconds_bucket{}[60s])) by (le,sql_type,instance)), start_time:2019-12-23 16:10:13, end_time:2019-12-23 16:30:13, step:1m0s" + ] + }, + { + "SQL": "explain format = 'plan_tree' select * from information_schema.tables", + "Plan": [ + "MemTableScan root table:TABLES " + ] + }, + { + "SQL": "explain format = 'plan_tree' select * from mysql.stats_meta", + "Plan": [ + "TableReader root data:TableFullScan", + "└─TableFullScan cop[tikv] table:stats_meta keep order:false, stats:pseudo" + ] + } + ] + }, + { + "Name": "TestIsolationReadTiFlashNotChoosePointGet", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select * from t where t.a = 1", + "Result": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TableRangeScan mpp[tiflash] table:t range:[1,1], keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select * from t where t.a in (1, 2)", + "Result": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TableRangeScan mpp[tiflash] table:t range:[1,1], [2,2], keep order:false, stats:pseudo" + ] + } + ] + }, + { + "Name": "TestMergeContinuousSelections", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' SELECT table2 . `col_char_64` AS field1 FROM `ts` AS table2 INNER JOIN (SELECT DISTINCT SUBQUERY3_t1 . * FROM `ts` AS SUBQUERY3_t1 LEFT OUTER JOIN `ts` AS SUBQUERY3_t2 ON SUBQUERY3_t2 . `col_varchar_64_not_null` = SUBQUERY3_t1 . `col_varchar_key`) AS table3 ON (table3 . `col_varchar_key` = table2 . `col_varchar_64`) WHERE table3 . `col_char_64_not_null` >= SOME (SELECT SUBQUERY4_t1 . `col_varchar_64` AS SUBQUERY4_field1 FROM `ts` AS SUBQUERY4_t1) GROUP BY field1 ;", + "Plan": [ + "HashAgg root group by:test.ts.col_char_64, funcs:firstrow(test.ts.col_char_64)->test.ts.col_char_64", + "└─HashJoin root CARTESIAN semi join, left side:TableReader, other cond:or(ge(test.ts.col_char_64_not_null, Column), if(ne(Column, 0), NULL, 0))", + " ├─Selection(Build) root ne(Column, 0)", + " │ └─HashAgg root funcs:min(Column)->Column, funcs:sum(Column)->Column, funcs:count(Column)->Column", + " │ └─TableReader root MppVersion: 3, data:ExchangeSender", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " │ └─HashAgg mpp[tiflash] funcs:min(Column)->Column, funcs:sum(Column)->Column, funcs:count(1)->Column", + " │ └─Projection mpp[tiflash] test.ts.col_varchar_64->Column, cast(isnull(test.ts.col_varchar_64), decimal(20,0) BINARY)->Column", + " │ └─TableFullScan mpp[tiflash] table:SUBQUERY4_t1 keep order:false, stats:pseudo", + " └─TableReader(Probe) root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] test.ts.col_char_64, test.ts.col_char_64_not_null", + " └─HashJoin mpp[tiflash] inner join, equal:[eq(test.ts.col_varchar_64, test.ts.col_varchar_key)]", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection mpp[tiflash] not(isnull(test.ts.col_varchar_64))", + " │ └─TableFullScan mpp[tiflash] table:table2 keep order:false, stats:pseudo", + " └─Selection(Probe) mpp[tiflash] not(isnull(test.ts.col_varchar_key))", + " └─TableFullScan mpp[tiflash] table:SUBQUERY3_t1 keep order:false, stats:pseudo" + ] + } + ] + }, + { + "Name": "TestPushDownGroupConcatToTiFlash", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id) from ts", + "Plan": [ + "HashAgg 1.00 root funcs:group_concat(Column separator \",\")->Column", + "└─TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column, Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:test.ts.col_0, test.ts.col_1, test.ts.id, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id order by col_0) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column, Column order by Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id order by col_0) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column order by Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:test.ts.col_0, test.ts.col_1, test.ts.id, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id order by col_0),count(*),min(col_1) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column, Column order by Column separator \",\")->Column, funcs:count(1)->Column, funcs:min(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_1->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id order by col_0),count(*),max(col_0) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column order by Column separator \",\")->Column, funcs:sum(Column)->Column, funcs:max(Column)->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, Column->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:test.ts.col_0, test.ts.col_1, test.ts.id, funcs:count(1)->Column, funcs:max(test.ts.col_0)->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column, Column separator \",\")->Column", + " └─Projection 8000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 8000.00 mpp[tiflash] ", + " └─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─HashAgg 8000.00 mpp[tiflash] group by:test.ts.col_0, test.ts.col_1, test.ts.col_2, test.ts.id, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id order by col_0) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column order by Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id order by col_0) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column, Column order by Column separator \",\")->Column", + " └─Projection 8000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 8000.00 mpp[tiflash] ", + " └─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─HashAgg 8000.00 mpp[tiflash] group by:test.ts.col_0, test.ts.col_1, test.ts.col_2, test.ts.id, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_1, id order by col_0) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column order by Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_1, id order by col_0) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column order by Column separator \",\")->Column", + " └─Projection 8000.00 mpp[tiflash] test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 8000.00 mpp[tiflash] ", + " └─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─HashAgg 8000.00 mpp[tiflash] group by:test.ts.col_1, test.ts.col_2, test.ts.id, funcs:firstrow(test.ts.col_0)->test.ts.col_0", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id order by col_0),count(*),min(col_0),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column order by Column separator \",\")->Column, funcs:count(1)->Column, funcs:min(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id order by col_0),count(*),max(col_1),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column, Column order by Column separator \",\")->Column, funcs:count(1)->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id order by col_0),count(distinct id),min(col_0),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column, Column order by Column separator \",\")->Column, funcs:count(Column)->Column, funcs:min(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, test.ts.id->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id order by col_0),count(distinct id),max(col_1),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column order by Column separator \",\")->Column, funcs:sum(Column)->Column, funcs:max(Column)->Column, funcs:sum(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_0->Column, Column->Column, Column->Column, Column->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, Column, Column, funcs:count(Column)->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.id->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_0->Column, test.ts.col_1->Column, test.ts.id->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id),count(distinct id),min(col_0),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:min(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id),count(distinct id),max(col_1),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id),count(distinct id),min(col_0),avg(id) from ts", + "Plan": [ + "HashAgg 1.00 root funcs:group_concat(Column separator \",\")->Column, funcs:count(Column)->Column, funcs:min(Column)->Column, funcs:avg(Column, Column)->Column", + "└─TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:min(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id),count(distinct id),max(col_1),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column separator \",\")->Column, funcs:sum(Column)->Column, funcs:max(Column)->Column, funcs:sum(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, Column->Column, Column->Column, Column->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, Column, Column, funcs:count(Column)->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.id->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_0->Column, test.ts.col_1->Column, test.ts.id->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_1, id),count(distinct id),group_concat(col_0 order by 1),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:group_concat(Column order by Column separator \",\")->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_0->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0),count(distinct id),group_concat(col_1, id order by 1,2),avg(id) from ts group by col_2", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column separator \",\")->Column, funcs:count(Column)->Column, funcs:group_concat(Column, Column order by Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.id->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_1->Column, test.ts.id->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_2->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_2, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, id),count(distinct id),group_concat(col_1, id order by 1,2),min(col_0),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:group_concat(Column, Column order by Column, Column separator \",\")->Column, funcs:min(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_1->Column, test.ts.id->Column, test.ts.col_0->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id),count(distinct id),group_concat(col_1, id order by 1,2),max(col_1),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column separator \",\")->Column, funcs:count(Column)->Column, funcs:group_concat(Column, Column order by Column, Column separator \",\")->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_1->Column, test.ts.id->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id),count(distinct col_2),group_concat(col_1, id),max(col_1),avg(id) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column, Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column, Column separator \",\")->Column, funcs:count(distinct Column)->Column, funcs:group_concat(Column separator \",\")->Column, funcs:max(Column)->Column, funcs:sum(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 1.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_2->Column, Column->Column, Column->Column, Column->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, Column, Column, Column, funcs:group_concat(Column, Column separator \",\")->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_0->Column, test.ts.col_1->Column, test.ts.id->Column, test.ts.col_2->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0, col_1, id),count(distinct col_2),group_concat(col_1, id),max(col_1),avg(id) from ts group by col_0", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column, Column, Column, Column, div(Column, cast(case(eq(Column, 0), 1, Column), decimal(20,0) BINARY))->Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column, Column separator \",\")->Column, funcs:count(distinct Column)->Column, funcs:group_concat(Column, Column separator \",\")->Column, funcs:max(Column)->Column, funcs:count(Column)->Column, funcs:sum(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_2->Column, test.ts.col_1->Column, cast(test.ts.id, var_string(20))->Column, test.ts.col_1->Column, test.ts.id->Column, cast(test.ts.id, decimal(10,0) BINARY)->Column, test.ts.col_0->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_0, collate: utf8mb4_bin]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,'GG') from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"GG\", 0, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,'01') from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"01\", 0, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,1) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, cast(Column, var_string(20))->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:0, 1, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,0) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, cast(Column, var_string(20))->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:0, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,10) from ts group by '010'", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, cast(Column, var_string(20))->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: Column, collate: binary]", + " └─HashAgg 1.00 mpp[tiflash] group by:0, 1, 10, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,0) from ts group by '011'", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, cast(Column, var_string(20))->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: Column, collate: binary]", + " └─HashAgg 1.00 mpp[tiflash] group by:0, 1, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 0,'GG') from ts group by 'GG'", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] cast(Column, var_string(20))->Column, Column->Column, Column->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: Column, collate: binary]", + " └─HashAgg 1.00 mpp[tiflash] group by:\"GG\", 0, 1, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'", + "[types:1292]Truncated incorrect DOUBLE value: 'GG'" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 'GG','GG') from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"GG\", ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 'Gg','GG') from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"GG\", \"Gg\", ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct 'GG-10','GG') from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"GG\", \"GG-10\", ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct '1200-01-01 00:00:00.023',1200) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct Column, Column separator \",\")->Column", + " └─Projection 1.00 mpp[tiflash] Column->Column, cast(Column, var_string(20))->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:\"1200-01-01 00:00:00.023\", 1200, ", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable", + "[types:1292]Truncated incorrect DOUBLE value: '1200-01-01 00:00:00.023'", + "[types:1292]Truncated incorrect DOUBLE value: '1200-01-01 00:00:00.023'" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_0) from ts group by id", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:test.ts.id, funcs:group_concat(test.ts.col_0, test.ts.col_0 separator \",\")->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.id, collate: binary]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(col_0, col_0,id) from ts group by id", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(Column, Column, Column separator \",\")->Column", + " └─Projection 10000.00 mpp[tiflash] test.ts.col_0->Column, test.ts.col_0->Column, cast(test.ts.id, var_string(20))->Column, test.ts.id->Column", + " └─ExchangeReceiver 10000.00 mpp[tiflash] ", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.id, collate: binary]", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0 order by id<10) from ts", + "Plan": [ + "TableReader 1.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 1.00 mpp[tiflash] Column", + " └─HashAgg 1.00 mpp[tiflash] funcs:group_concat(distinct test.ts.col_0 order by Column separator \",\")->Column", + " └─ExchangeReceiver 1.00 mpp[tiflash] ", + " └─ExchangeSender 1.00 mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─HashAgg 1.00 mpp[tiflash] group by:Column, funcs:firstrow(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] lt(test.ts.id, 10)->Column, test.ts.col_0->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0 order by id<10) from ts group by col_1", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:test.ts.col_1, funcs:group_concat(distinct test.ts.col_0 order by Column separator \",\")->Column", + " └─ExchangeReceiver 8000.00 mpp[tiflash] ", + " └─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_1, collate: utf8mb4_bin]", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, Column, funcs:firstrow(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] lt(test.ts.id, 10)->Column, test.ts.col_1->Column, test.ts.col_0->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0>10 order by id<10) from ts group by col_1", + "Plan": [ + "TableReader 8000.00 root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─Projection 8000.00 mpp[tiflash] Column", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, funcs:group_concat(distinct Column order by Column separator \",\")->Column", + " └─Projection 8000.00 mpp[tiflash] cast(Column, var_string(20))->Column, Column->Column, test.ts.col_1->Column", + " └─ExchangeReceiver 8000.00 mpp[tiflash] ", + " └─ExchangeSender 8000.00 mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.ts.col_1, collate: utf8mb4_bin]", + " └─HashAgg 8000.00 mpp[tiflash] group by:Column, Column, funcs:firstrow(Column)->Column", + " └─Projection 10000.00 mpp[tiflash] lt(test.ts.id, 10)->Column, test.ts.col_1->Column, gt(cast(test.ts.col_0, double BINARY), 10)->Column", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable" + ] + }, + { + "SQL": "explain format = 'plan_tree' select /*+ hash_agg(),agg_to_cop() */ group_concat(distinct col_0 order by col_0<=>null) from ts", + "Plan": [ + "HashAgg 1.00 root funcs:group_concat(distinct Column order by Column separator \",\")->Column", + "└─Projection 10000.00 root test.ts.col_0->Column, nulleq(test.ts.col_0, )->Column", + " └─TableReader 10000.00 root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender 10000.00 mpp[tiflash] ExchangeType: PassThrough", + " └─TableFullScan 10000.00 mpp[tiflash] table:ts keep order:false, stats:pseudo" + ], + "Warning": [ + "[planner:1815]Optimizer Hint AGG_TO_COP is inapplicable", + "Scalar function 'nulleq'(signature: NullEQString, return type: bigint(1)) is not supported to push down to tiflash now.", + "Aggregation can not be pushed to tiflash because arguments of AggFunc `group_concat` contains unsupported exprs in order-by clause", + "Scalar function 'nulleq'(signature: NullEQString, return type: bigint(1)) is not supported to push down to tiflash now.", + "Aggregation can not be pushed to tiflash because arguments of AggFunc `group_concat` contains unsupported exprs in order-by clause" + ] + } + ] + }, + { + "Name": "TestTiFlashPartitionTableScan", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select * from rp_t where a = 1 or a = 20", + "Plan": [ + "TableReader root partition:p0,p3 MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Selection mpp[tiflash] or(eq(test.rp_t.a, 1), eq(test.rp_t.a, 20))", + " └─TableFullScan mpp[tiflash] table:rp_t keep order:false, stats:pseudo, PartitionTableScan:true" + ] + }, + { + "SQL": "explain format = 'plan_tree' select * from hp_t where a = 1 or a = 20", + "Plan": [ + "TableReader root partition:p0,p1 MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Selection mpp[tiflash] or(eq(test.hp_t.a, 1), eq(test.hp_t.a, 20))", + " └─TableFullScan mpp[tiflash] table:hp_t keep order:false, stats:pseudo, PartitionTableScan:true" + ] + }, + { + "SQL": "explain format = 'plan_tree' select count(*) from rp_t where a = 1 or a = 20", + "Plan": [ + "StreamAgg root funcs:count(1)->Column", + "└─TableReader root partition:p0,p3 MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Selection mpp[tiflash] or(eq(test.rp_t.a, 1), eq(test.rp_t.a, 20))", + " └─TableFullScan mpp[tiflash] table:rp_t keep order:false, stats:pseudo, PartitionTableScan:true" + ] + }, + { + "SQL": "explain format = 'plan_tree' select count(*) from hp_t where a = 1 or a = 20", + "Plan": [ + "StreamAgg root funcs:count(1)->Column", + "└─TableReader root partition:p0,p1 MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Selection mpp[tiflash] or(eq(test.hp_t.a, 1), eq(test.hp_t.a, 20))", + " └─TableFullScan mpp[tiflash] table:hp_t keep order:false, stats:pseudo, PartitionTableScan:true" + ] + } + ] + }, + { + "Name": "TestTiFlashFineGrainedShuffle", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select row_number() over w1 from t1 window w1 as (partition by c1 order by c1);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1, rank() over w2 from t1 window w1 as (partition by c1 order by c1), w2 as (partition by c2);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Window mpp[tiflash] rank()->Column over(partition by test.t1.c2), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Window mpp[tiflash] rank()->Column over(partition by test.t1.c2), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1, rank() over w2 from t1 window w1 as (partition by c1 order by c1), w2 as (partition by c2) order by 1, 2 limit 10;", + "Plan": [ + "Projection root Column, Column", + "└─TopN root Column, Column, offset:0, count:10", + " └─TableReader root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TopN mpp[tiflash] Column, Column, offset:0, count:10", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Window mpp[tiflash] rank()->Column over(partition by test.t1.c2), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "Projection root Column, Column", + "└─TopN root Column, Column, offset:?, count:?", + " └─TableReader root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TopN mpp[tiflash] Column, Column, offset:?, count:?", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Window mpp[tiflash] rank()->Column over(partition by test.t1.c2), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1, count(c2) from t1 group by c1 having c1 > 10 window w1 as (partition by c2 order by c2);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c2 order by test.t1.c2 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] Column, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:sum(Column)->Column, funcs:firstrow(Column)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:count(test.t1.c2)->Column, funcs:firstrow(test.t1.c2)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c1, 10)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c2 order by test.t1.c2 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c2, test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] Column, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:sum(Column)->Column, funcs:firstrow(Column)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:count(test.t1.c2)->Column, funcs:firstrow(test.t1.c2)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c1, ?)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1, count(c1) from t1 group by c2 having c2 > 10 window w1 as (partition by c1 order by c2);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c2 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] Column, test.t1.c1, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c2, funcs:sum(Column)->Column, funcs:firstrow(Column)->test.t1.c1, funcs:firstrow(test.t1.c2)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c2, funcs:count(test.t1.c1)->Column, funcs:firstrow(test.t1.c1)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c2, 10)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c2 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c2, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] Column, test.t1.c1, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c2, funcs:sum(Column)->Column, funcs:firstrow(Column)->test.t1.c1, funcs:firstrow(test.t1.c2)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c2, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c2, funcs:count(test.t1.c1)->Column, funcs:firstrow(test.t1.c1)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c2, ?)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1 from t1 a join t1 b on a.c1 = b.c2 window w1 as (partition by a.c1);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] test.t1.c1", + " └─HashJoin mpp[tiflash] inner join, equal:[eq(test.t1.c1, test.t1.c2)]", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection mpp[tiflash] not(isnull(test.t1.c1))", + " │ └─TableFullScan mpp[tiflash] table:a keep order:false, stats:pseudo", + " └─Selection(Probe) mpp[tiflash] not(isnull(test.t1.c2))", + " └─TableFullScan mpp[tiflash] table:b keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Projection mpp[tiflash] test.t1.c1", + " └─HashJoin mpp[tiflash] inner join, equal:[eq(test.t1.c1, test.t1.c2)]", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection mpp[tiflash] not(isnull(test.t1.c1))", + " │ └─TableFullScan mpp[tiflash] table:a keep order:false, stats:pseudo", + " └─Selection(Probe) mpp[tiflash] not(isnull(test.t1.c2))", + " └─TableFullScan mpp[tiflash] table:b keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1 from t1 where c1 < 100 window w1 as (partition by c1 order by c1);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Selection mpp[tiflash] lt(test.t1.c1, 100)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, stream_count: 8", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c1 rows between current row and current row), stream_count: 8", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c1, stream_count: 8", + " └─ExchangeReceiver mpp[tiflash] stream_count: 8", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary], stream_count: 8", + " └─Selection mpp[tiflash] lt(test.t1.c1, ?)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select * from t1;", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1 from t1 window w1 as (order by c1);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column", + " └─Window mpp[tiflash] row_number()->Column over(order by test.t1.c1 rows between current row and current row)", + " └─Sort mpp[tiflash] test.t1.c1", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column", + " └─Window mpp[tiflash] row_number()->Column over(order by test.t1.c1 rows between current row and current row)", + " └─Sort mpp[tiflash] test.t1.c1", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough, Compression: FAST", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select row_number() over w1, count(c2) from t1 group by c1 having c1 > 10 window w1 as (partition by c1 order by c2);", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c2 rows between current row and current row)", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c2", + " └─Projection mpp[tiflash] Column, test.t1.c1, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:sum(Column)->Column, funcs:firstrow(test.t1.c1)->test.t1.c1, funcs:firstrow(Column)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:count(test.t1.c2)->Column, funcs:firstrow(test.t1.c2)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c1, 10)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ], + "Redact": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] Column, Column", + " └─Window mpp[tiflash] row_number()->Column over(partition by test.t1.c1 order by test.t1.c2 rows between current row and current row)", + " └─Sort mpp[tiflash] test.t1.c1, test.t1.c2", + " └─Projection mpp[tiflash] Column, test.t1.c1, test.t1.c2", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:sum(Column)->Column, funcs:firstrow(test.t1.c1)->test.t1.c1, funcs:firstrow(Column)->test.t1.c2", + " └─ExchangeReceiver mpp[tiflash] ", + " └─ExchangeSender mpp[tiflash] ExchangeType: HashPartition, Compression: FAST, Hash Cols: [name: test.t1.c1, collate: binary]", + " └─HashAgg mpp[tiflash] group by:test.t1.c1, funcs:count(test.t1.c2)->Column, funcs:firstrow(test.t1.c2)->Column", + " └─Selection mpp[tiflash] gt(test.t1.c1, ?)", + " └─TableFullScan mpp[tiflash] table:t1 keep order:false, stats:pseudo" + ] + } + ] + }, + { + "Name": "TestTiFlashExtraColumnPrune", + "Cases": [ + { + "SQL": "explain format = 'plan_tree' select ta.c1 from t1 ta, t1 tb where ta.c1 * ta.c1 > ta.c2 + 10;", + "Plan": [ + "TableReader root MppVersion: 3, data:ExchangeSender", + "└─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─Projection mpp[tiflash] test.t1.c1", + " └─HashJoin mpp[tiflash] CARTESIAN inner join", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Projection mpp[tiflash] test.t1.c1", + " │ └─Selection mpp[tiflash] gt(mul(test.t1.c1, test.t1.c1), plus(test.t1.c2, 10))", + " │ └─TableFullScan mpp[tiflash] table:ta keep order:false, stats:pseudo", + " └─TableFullScan(Probe) mpp[tiflash] table:tb keep order:false, stats:pseudo" + ] + }, + { + "SQL": "explain format = 'plan_tree' select count(*) from t1 ta, t1 tb where ta.c1 * ta.c1 > ta.c2 + 10;", + "Plan": [ + "HashAgg root funcs:count(Column)->Column", + "└─TableReader root MppVersion: 3, data:ExchangeSender", + " └─ExchangeSender mpp[tiflash] ExchangeType: PassThrough", + " └─HashAgg mpp[tiflash] funcs:count(1)->Column", + " └─Projection mpp[tiflash] test.t1.c1", + " └─HashJoin mpp[tiflash] CARTESIAN inner join", + " ├─ExchangeReceiver(Build) mpp[tiflash] ", + " │ └─ExchangeSender mpp[tiflash] ExchangeType: Broadcast, Compression: FAST", + " │ └─Selection mpp[tiflash] gt(mul(test.t1.c1, test.t1.c1), plus(test.t1.c2, 10))", + " │ └─TableFullScan mpp[tiflash] table:ta keep order:false, stats:pseudo", + " └─TableFullScan(Probe) mpp[tiflash] table:tb keep order:false, stats:pseudo" + ] + } + ] + } +] diff --git a/pkg/planner/core/find_best_task.go b/pkg/planner/core/find_best_task.go index 802309d147a40..1c09614c4bb1c 100644 --- a/pkg/planner/core/find_best_task.go +++ b/pkg/planner/core/find_best_task.go @@ -683,7 +683,22 @@ type candidatePath struct { accessCondsColMap util.Col2Len // accessCondsColMap maps Column.UniqueID to column length for the columns in AccessConds. indexCondsColMap util.Col2Len // indexCondsColMap maps Column.UniqueID to column length for the columns in AccessConds and indexFilters. matchPropResult property.PhysicalPropMatchResult +<<<<<<< HEAD indexJoinCols int // how many index columns are used in access conditions in this IndexJoin. +======= + // partialOrderMatch records the partial order match result for TopN optimization. + // When the matched is true, it means this path can provide partial order using prefix index. + partialOrderMatchResult property.PartialOrderMatchResult // Result of matching partial order property + // matchWithAdvisorySortItems indicates the property matching used SortItemsHints + // (i.e. noSortItem && len(prop.SortItemsHints) > 0). Only relevant for IndexMerge. + matchWithAdvisorySortItems bool + // partialPathMatchResults stores each partial path's matchProperty result. + // Length equals len(path.PartialIndexPaths). Only set for IndexMerge paths. + partialPathMatchResults []property.PhysicalPropMatchResult + indexJoinCols int // how many index columns are used in access conditions in this IndexJoin. + isFullRange bool // cached result of whether this path covers the full scan range. + eqOrInCount int // cached result of equalPredicateCount(). +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) } func compareBool(l, r bool) int { @@ -1086,43 +1101,104 @@ func GroupRangesByCols(ranges []*ranger.Range, groupByColIdxs []int) ([][]*range // // at last, according to determinedIndexPartialPaths to rewrite their real countAfterAccess, this part is move from deriveStats to // here. -func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) (*util.AccessPath, property.PhysicalPropMatchResult) { +func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) (*util.AccessPath, []property.PhysicalPropMatchResult, bool, property.PhysicalPropMatchResult) { // target: // 1: index merge case, try to match the every alternative partial path to the order property as long as // possible, and generate that property-matched index merge path out if any. - // 2: If the prop is empty (means no sort requirement), we will generate a random index partial combination - // path from all alternatives in case that no index merge path comes out. + // 2: If the prop is empty (means no sort requirement) but AdvisorySortItems is set, prefer alternatives + // that can satisfy the SoftSortItems for potential Limit pushdown. + // 3: If neither, generate a random index partial combination path from all alternatives. // Execution part doesn't support the merge operation for intersection case yet. if path.IndexMergeIsIntersection { - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } noSortItem := prop.IsSortItemEmpty() allSame, _ := prop.AllSameOrder() if !allSame { - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched + } + + // When SortItems is empty and AdvisorySortItems is set, use hints as soft sort + // requirements. Alternatives that satisfy AdvisorySortItems can benefit from + // Limit pushdown. + useAdvisorySortItems := noSortItem && len(prop.AdvisorySortItems) > 0 + var hintsProp *property.PhysicalProperty + if useAdvisorySortItems { + hintsProp = prop.CloneEssentialFields() + hintsProp.SortItems = hintsProp.AdvisorySortItems } + // step1: match the property from all the index partial alternative paths. determinedIndexPartialPaths := make([]*util.AccessPath, 0, len(path.PartialAlternativeIndexPaths)) + partialPathMatchResults := make([]property.PhysicalPropMatchResult, 0, len(path.PartialAlternativeIndexPaths)) usedIndexMap := make(map[int64]struct{}, 1) for _, oneORBranch := range path.PartialAlternativeIndexPaths { matchIdxes := make([]int, 0, 1) +<<<<<<< HEAD for i, oneAlternative := range oneORBranch { // if there is some sort items and this path doesn't match this prop, continue. // Satisfying the property by a merge sort is not supported for partial paths of index merge. if !noSortItem && matchProperty(ds, oneAlternative, prop) != property.PropMatched { continue +======= + // altMatchResults[i] stores matchProperty results for each access path + // in oneORBranch[i]. Populated during hints or fallback matching. + var altMatchResults [][]property.PhysicalPropMatchResult + + if useAdvisorySortItems { + // First pass: prefer alternatives that satisfy SortItemsHints. + altMatchResults = make([][]property.PhysicalPropMatchResult, len(oneORBranch)) + for i, oneAlternative := range oneORBranch { + match := true + results := make([]property.PhysicalPropMatchResult, 0, len(oneAlternative)) + for _, oneAccessPath := range oneAlternative { + result := matchProperty(ds, oneAccessPath, hintsProp) + results = append(results, result) + if !result.Matched() { + match = false + } + } + altMatchResults[i] = results + if match { + matchIdxes = append(matchIdxes, i) + } + } + } + + // If no matches found with hints, fall back to default matching logic. + if len(matchIdxes) == 0 { + altMatchResults = make([][]property.PhysicalPropMatchResult, len(oneORBranch)) + for i, oneAlternative := range oneORBranch { + // if there is some sort items and this path doesn't match this prop, continue. + match := true + results := make([]property.PhysicalPropMatchResult, 0, len(oneAlternative)) + for _, oneAccessPath := range oneAlternative { + var result property.PhysicalPropMatchResult + if !noSortItem { + result = matchProperty(ds, oneAccessPath, prop) + } + results = append(results, result) + if !noSortItem && !result.Matched() { + match = false + } + } + altMatchResults[i] = results + if !match { + continue + } + // two possibility here: + // 1. no sort items requirement. + // 2. matched with sorted items. + matchIdxes = append(matchIdxes, i) +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) } - // two possibility here: - // 1. no sort items requirement. - // 2. matched with sorted items. - matchIdxes = append(matchIdxes, i) } if len(matchIdxes) == 0 { // if all index alternative of one of the cnf item's couldn't match the sort property, // the entire index merge union path can be ignored for this sort property, return false. - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } if len(matchIdxes) > 1 { // if matchIdxes greater than 1, we should sort this match alternative path by its CountAfterAccess. @@ -1152,7 +1228,12 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc }) } lowestCountAfterAccessIdx := matchIdxes[0] +<<<<<<< HEAD determinedIndexPartialPaths = append(determinedIndexPartialPaths, oneORBranch[lowestCountAfterAccessIdx]) +======= + determinedIndexPartialPaths = append(determinedIndexPartialPaths, sliceutil.DeepClone(oneORBranch[lowestCountAfterAccessIdx])...) + partialPathMatchResults = append(partialPathMatchResults, altMatchResults[lowestCountAfterAccessIdx]...) +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) // record the index usage info to avoid choosing a single index for all partial paths var indexID int64 if oneORBranch[lowestCountAfterAccessIdx].IsTablePath() { @@ -1167,7 +1248,7 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc // since ds index merge hints will prune other path ahead, lift the all single index limitation here. if len(usedIndexMap) == 1 && len(ds.IndexMergeHints) <= 0 { // if all partial path are using a same index, meaningless and fail over. - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } // step2: gen a new **concrete** index merge path. indexMergePath := &util.AccessPath{ @@ -1223,27 +1304,35 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc if noSortItem { // since there is no sort property, index merge case is generated by random combination, each alternative with the lower/lowest // countAfterAccess, here the returned matchProperty should be PropNotMatched. - return indexMergePath, property.PropNotMatched + return indexMergePath, partialPathMatchResults, useAdvisorySortItems, property.PropNotMatched } - return indexMergePath, property.PropMatched + return indexMergePath, partialPathMatchResults, useAdvisorySortItems, property.PropMatched } -func isMatchPropForIndexMerge(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) property.PhysicalPropMatchResult { +func isMatchPropForIndexMerge(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) ([]property.PhysicalPropMatchResult, property.PhysicalPropMatchResult) { // Execution part doesn't support the merge operation for intersection case yet. if path.IndexMergeIsIntersection { - return property.PropNotMatched + return nil, property.PropNotMatched } allSame, _ := prop.AllSameOrder() if !allSame { - return property.PropNotMatched + return nil, property.PropNotMatched } + results := make([]property.PhysicalPropMatchResult, 0, len(path.PartialIndexPaths)) for _, partialPath := range path.PartialIndexPaths { +<<<<<<< HEAD // Satisfying the property by a merge sort is not supported for partial paths of index merge. if matchProperty(ds, partialPath, prop) != property.PropMatched { return property.PropNotMatched +======= + result := matchProperty(ds, partialPath, prop) + results = append(results, result) + if !result.Matched() { + return nil, property.PropNotMatched +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) } } - return property.PropMatched + return results, property.PropMatched } func getTableCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { @@ -1278,18 +1367,49 @@ func getIndexCandidateForIndexJoin(sctx planctx.PlanContext, path *util.AccessPa } func convergeIndexMergeCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { - // since the all index path alternative paths is collected and undetermined, and we should determine a possible and concrete path for this prop. - possiblePath, match := matchPropForIndexMergeAlternatives(ds, path, prop) + possiblePath, partialMatchResults, matchWithAdvisory, match := matchPropForIndexMergeAlternatives(ds, path, prop) if possiblePath == nil { return nil } +<<<<<<< HEAD candidate := &candidatePath{path: possiblePath, matchPropResult: match} +======= + candidate := &candidatePath{ + path: possiblePath, + matchPropResult: match, + matchWithAdvisorySortItems: matchWithAdvisory, + partialPathMatchResults: partialMatchResults, + } + candidate.isFullRange = possiblePath.IsFullScanRange(ds.TableInfo) + candidate.eqOrInCount = candidate.equalPredicateCount() +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) return candidate } func getIndexMergeCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { candidate := &candidatePath{path: path} +<<<<<<< HEAD candidate.matchPropResult = isMatchPropForIndexMerge(ds, path, prop) +======= + + allSameOrder, _ := prop.AllSameOrder() + // When SortItems is empty and SortItemsHints is set, check which partial + // paths satisfy the hints (for Limit pushdown). + if prop.IsSortItemEmpty() && len(prop.AdvisorySortItems) > 0 && !path.IndexMergeIsIntersection && allSameOrder { + hintsProp := prop.CloneEssentialFields() + hintsProp.SortItems = hintsProp.AdvisorySortItems + candidate.matchWithAdvisorySortItems = true + candidate.partialPathMatchResults, _ = isMatchPropForIndexMerge(ds, path, hintsProp) + // When using hints, there are no real sort items, so the overall match + // against the original prop is PropNotMatched. + candidate.matchPropResult = property.PropNotMatched + } else { + candidate.partialPathMatchResults, candidate.matchPropResult = isMatchPropForIndexMerge(ds, path, prop) + } + + candidate.isFullRange = path.IsFullScanRange(ds.TableInfo) + candidate.eqOrInCount = candidate.equalPredicateCount() +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) return candidate } @@ -1891,6 +2011,17 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr Columns: ds.TblCols, ColumnNames: ds.OutputNames(), } +<<<<<<< HEAD +======= + cop.PhysPlanPartInfo = buildPhysPlanPartInfo(ds) + + advisoryProp := prop + if candidate.matchWithAdvisorySortItems { + advisoryProp = prop.CloneEssentialFields() + advisoryProp.SortItems = advisoryProp.AdvisorySortItems + } + +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) // Add sort items for index scan for merge-sort operation between partitions. byItems := make([]*util.ByItems, 0, len(prop.SortItems)) for _, si := range prop.SortItems { @@ -1900,13 +2031,27 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr }) } globalRemainingFilters := make([]expression.Expression, 0, 3) - for _, partPath := range path.PartialIndexPaths { + for i, partPath := range path.PartialIndexPaths { var scan base.PhysicalPlan + partMatchPropResult := property.PropNotMatched + if len(candidate.partialPathMatchResults) > 0 { + partMatchPropResult = candidate.partialPathMatchResults[i] + } + effectiveProp := prop + // If matchWithAdvisorySortItems is true and this partial path can match the property, + // it means it actually matches the advisory property. + if candidate.matchWithAdvisorySortItems && partMatchPropResult.Matched() { + effectiveProp = advisoryProp + } if partPath.IsTablePath() { - scan = convertToPartialTableScan(ds, prop, partPath, candidate.matchPropResult, byItems) + scan = convertToPartialTableScan(ds, effectiveProp, partPath, partMatchPropResult, byItems) } else { var remainingFilters []expression.Expression +<<<<<<< HEAD scan, remainingFilters, err = convertToPartialIndexScan(ds, cop.physPlanPartInfo, prop, partPath, candidate.matchPropResult, byItems) +======= + scan, remainingFilters, err = physicalop.ConvertToPartialIndexScan(ds, cop.PhysPlanPartInfo, effectiveProp, partPath, partMatchPropResult, byItems) +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) if err != nil { return base.InvalidTask, err } @@ -1939,6 +2084,16 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr cop.needExtraProj = true cop.originSchema = ds.Schema() } +<<<<<<< HEAD +======= + cop.KeepOrder = candidate.matchPropResult == property.PropMatched + cop.TablePlan = ts + cop.IdxMergePartPlans = scans + cop.IdxMergeIsIntersection = path.IndexMergeIsIntersection + cop.IdxMergeAccessMVIndex = path.IndexMergeAccessMVIndex + cop.IdxMergePartPlansMatchResults = candidate.partialPathMatchResults + cop.IdxMergeMatchWithAdvisorySortItems = candidate.matchWithAdvisorySortItems +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) if len(globalRemainingFilters) != 0 { cop.rootTaskConds = globalRemainingFilters } diff --git a/pkg/planner/core/operator/physicalop/physical_topn.go b/pkg/planner/core/operator/physicalop/physical_topn.go new file mode 100644 index 0000000000000..8532f0da25ff5 --- /dev/null +++ b/pkg/planner/core/operator/physicalop/physical_topn.go @@ -0,0 +1,473 @@ +// Copyright 2025 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package physicalop + +import ( + "bytes" + "fmt" + "math" + + perrors "github.com/pingcap/errors" + "github.com/pingcap/tidb/pkg/expression" + "github.com/pingcap/tidb/pkg/kv" + "github.com/pingcap/tidb/pkg/planner/core/base" + "github.com/pingcap/tidb/pkg/planner/core/operator/logicalop" + "github.com/pingcap/tidb/pkg/planner/property" + "github.com/pingcap/tidb/pkg/planner/util" + "github.com/pingcap/tidb/pkg/planner/util/costusage" + "github.com/pingcap/tidb/pkg/planner/util/utilfuncp" + "github.com/pingcap/tidb/pkg/util/plancodec" + "github.com/pingcap/tidb/pkg/util/size" + "github.com/pingcap/tipb/go-tipb" +) + +// PhysicalTopN is the physical operator of topN. +type PhysicalTopN struct { + PhysicalSchemaProducer + + ByItems []*util.ByItems + PartitionBy []property.SortItem + Offset uint64 + Count uint64 + + // PrefixCol is the prefix index column for partial order optimization. + // Used for both execution (via UniqueID) and explain (via column name). + // If prefix index optimization is not used, this field is nil. + PrefixCol *expression.Column + + // PrefixLen is the prefix index length (in bytes) for TiDB-side short-circuiting. + // If prefix index optimization is not used, this field is 0. + PrefixLen int +} + +// ExhaustPhysicalPlans4LogicalTopN exhausts PhysicalTopN plans from LogicalTopN. +func ExhaustPhysicalPlans4LogicalTopN(lt *logicalop.LogicalTopN, prop *property.PhysicalProperty) ([][]base.PhysicalPlan, bool, error) { + if MatchItems(prop, lt.ByItems) { + // PhysicalTopN and PhysicalLimit are in different slices + // so we can support preferring a specific LIMIT or TopN within one slice. + // For example, among all LIMIT tasks, we prefer the one pushed down to TiKV. + // Then we compare the preferred task from each slice by their actual cost. + return [][]base.PhysicalPlan{getPhysTopN(lt, prop), getPhysLimits(lt, prop)}, true, nil + } + return nil, true, nil +} + +// Init initializes PhysicalTopN. +func (p PhysicalTopN) Init(ctx base.PlanContext, stats *property.StatsInfo, offset int, props ...*property.PhysicalProperty) *PhysicalTopN { + p.BasePhysicalPlan = NewBasePhysicalPlan(ctx, plancodec.TypeTopN, &p, offset) + p.SetChildrenReqProps(props) + p.SetStats(stats) + return &p +} + +// GetPartitionBy returns partition by fields +func (p *PhysicalTopN) GetPartitionBy() []property.SortItem { + return p.PartitionBy +} + +// Clone implements op.PhysicalPlan interface. +func (p *PhysicalTopN) Clone(newCtx base.PlanContext) (base.PhysicalPlan, error) { + cloned := new(PhysicalTopN) + *cloned = *p + cloned.SetSCtx(newCtx) + base, err := p.PhysicalSchemaProducer.CloneWithSelf(newCtx, cloned) + if err != nil { + return nil, err + } + cloned.PhysicalSchemaProducer = *base + cloned.ByItems = make([]*util.ByItems, 0, len(p.ByItems)) + for _, it := range p.ByItems { + cloned.ByItems = append(cloned.ByItems, it.Clone()) + } + cloned.PartitionBy = make([]property.SortItem, 0, len(p.PartitionBy)) + for _, it := range p.PartitionBy { + cloned.PartitionBy = append(cloned.PartitionBy, it.Clone()) + } + return cloned, nil +} + +// ExtractCorrelatedCols implements op.PhysicalPlan interface. +func (p *PhysicalTopN) ExtractCorrelatedCols() []*expression.CorrelatedColumn { + corCols := make([]*expression.CorrelatedColumn, 0, len(p.ByItems)) + for _, item := range p.ByItems { + corCols = append(corCols, expression.ExtractCorColumns(item.Expr)...) + } + return corCols +} + +// MemoryUsage return the memory usage of PhysicalTopN +func (p *PhysicalTopN) MemoryUsage() (sum int64) { + if p == nil { + return + } + + sum = p.BasePhysicalPlan.MemoryUsage() + size.SizeOfSlice + + int64(cap(p.ByItems))*size.SizeOfPointer + + size.SizeOfUint64*2 + // Offset, Count + size.SizeOfInt64 + // PrefixColID + size.SizeOfInt // PrefixLen + for _, byItem := range p.ByItems { + sum += byItem.MemoryUsage() + } + for _, item := range p.PartitionBy { + sum += item.MemoryUsage() + } + return +} + +// ExplainInfo implements Plan interface. +func (p *PhysicalTopN) ExplainInfo() string { + ectx := p.SCtx().GetExprCtx().GetEvalCtx() + buffer := bytes.NewBufferString("") + if len(p.GetPartitionBy()) > 0 { + buffer = property.ExplainPartitionBy(ectx, buffer, p.GetPartitionBy(), false) + buffer.WriteString(" ") + } + if len(p.ByItems) > 0 { + // Add order by text to separate partition by. Otherwise, do not add order by to + // avoid breaking existing TopN tests. + if len(p.GetPartitionBy()) > 0 { + buffer.WriteString("order by ") + } + buffer = util.ExplainByItems(p.SCtx().GetExprCtx().GetEvalCtx(), buffer, p.ByItems) + } + switch p.SCtx().GetSessionVars().EnableRedactLog { + case perrors.RedactLogDisable: + fmt.Fprintf(buffer, ", offset:%v, count:%v", p.Offset, p.Count) + if p.PrefixCol != nil { + prefixColName := p.PrefixCol.ColumnExplainInfo(ectx, false) + fmt.Fprintf(buffer, ", prefix_col:%v, prefix_len:%v", + prefixColName, p.PrefixLen) + } + case perrors.RedactLogMarker: + fmt.Fprintf(buffer, ", offset:‹%v›, count:‹%v›", p.Offset, p.Count) + if p.PrefixCol != nil { + prefixColName := p.PrefixCol.ColumnExplainInfo(ectx, false) + fmt.Fprintf(buffer, ", prefix_col:‹%v›, prefix_len:‹%v›", + prefixColName, p.PrefixLen) + } + case perrors.RedactLogEnable: + fmt.Fprintf(buffer, ", offset:?, count:?") + if p.PrefixCol != nil { + fmt.Fprintf(buffer, ", prefix_col:?, prefix_len:?") + } + } + return buffer.String() +} + +// ExplainNormalizedInfo implements Plan interface. +func (p *PhysicalTopN) ExplainNormalizedInfo() string { + ectx := p.SCtx().GetExprCtx().GetEvalCtx() + buffer := bytes.NewBufferString("") + if len(p.GetPartitionBy()) > 0 { + buffer = property.ExplainPartitionBy(ectx, buffer, p.GetPartitionBy(), true) + buffer.WriteString(" ") + } + if len(p.ByItems) > 0 { + // Add order by text to separate partition by. Otherwise, do not add order by to + // avoid breaking existing TopN tests. + if len(p.GetPartitionBy()) > 0 { + buffer.WriteString("order by ") + } + buffer = explainNormalizedByItems(buffer, p.ByItems) + } + return buffer.String() +} + +func explainNormalizedByItems(buffer *bytes.Buffer, byItems []*util.ByItems) *bytes.Buffer { + for i, item := range byItems { + if item.Desc { + fmt.Fprintf(buffer, "%s:desc", item.Expr.ExplainNormalizedInfo()) + } else { + fmt.Fprintf(buffer, "%s", item.Expr.ExplainNormalizedInfo()) + } + + if i+1 < len(byItems) { + buffer.WriteString(", ") + } + } + return buffer +} + +// ToPB implements PhysicalPlan ToPB interface. +func (p *PhysicalTopN) ToPB(ctx *base.BuildPBContext, storeType kv.StoreType) (*tipb.Executor, error) { + client := ctx.GetClient() + topNExec := &tipb.TopN{ + Limit: p.Count, + } + evalCtx := ctx.GetExprCtx().GetEvalCtx() + for _, item := range p.ByItems { + topNExec.OrderBy = append(topNExec.OrderBy, expression.SortByItemToPB(evalCtx, client, item.Expr, item.Desc)) + } + for _, item := range p.PartitionBy { + topNExec.PartitionBy = append(topNExec.PartitionBy, expression.SortByItemToPB(evalCtx, client, item.Col.Clone(), item.Desc)) + } + executorID := "" + if storeType == kv.TiFlash { + var err error + topNExec.Child, err = p.Children()[0].ToPB(ctx, storeType) + if err != nil { + return nil, perrors.Trace(err) + } + executorID = p.ExplainID().String() + } + return &tipb.Executor{Tp: tipb.ExecType_TypeTopN, TopN: topNExec, ExecutorId: &executorID}, nil +} + +// GetCost computes cost of TopN operator itself. +func (p *PhysicalTopN) GetCost(count float64, isRoot bool) float64 { + heapSize := max(float64(p.Offset+p.Count), 2.0) + sessVars := p.SCtx().GetSessionVars() + // Ignore the cost of `doCompaction` in current implementation of `TopNExec`, since it is the + // special side-effect of our Chunk format in TiDB layer, which may not exist in coprocessor's + // implementation, or may be removed in the future if we change data format. + // Note that we are using worst complexity to compute CPU cost, because it is simpler compared with + // considering probabilities of average complexity, i.e, we may not need adjust heap for each input + // row. + var cpuCost float64 + if isRoot { + cpuCost = count * math.Log2(heapSize) * sessVars.GetCPUFactor() + } else { + cpuCost = count * math.Log2(heapSize) * sessVars.GetCopCPUFactor() + } + memoryCost := heapSize * sessVars.GetMemoryFactor() + return cpuCost + memoryCost +} + +// GetPlanCostVer1 calculates the cost of the plan if it has not been calculated yet and returns the cost. +func (p *PhysicalTopN) GetPlanCostVer1(taskType property.TaskType, option *costusage.PlanCostOption) (float64, error) { + return utilfuncp.GetPlanCostVer14PhysicalTopN(p, taskType, option) +} + +// GetPlanCostVer2 calculates the cost of the plan if it has not been calculated yet and returns a CostVer2 object. +func (p *PhysicalTopN) GetPlanCostVer2(taskType property.TaskType, option *costusage.PlanCostOption, isChildOfINL ...bool) (costusage.CostVer2, error) { + return utilfuncp.GetPlanCostVer24PhysicalTopN(p, taskType, option, isChildOfINL...) +} + +// ResolveIndices implements Plan interface. +func (p *PhysicalTopN) ResolveIndices() (err error) { + return utilfuncp.ResolveIndices4PhysicalTopN(p) +} + +// Attach2Task implements the PhysicalPlan interface. +func (p *PhysicalTopN) Attach2Task(tasks ...base.Task) base.Task { + return utilfuncp.Attach2Task4PhysicalTopN(p, tasks...) +} + +func getPhysTopN(lt *logicalop.LogicalTopN, prop *property.PhysicalProperty) []base.PhysicalPlan { + // topN should always generate rootTaskType for: + // case1: after v7.5, since tiFlash Cop has been banned, mppTaskType may return invalid task when there are some root conditions. + // case2: for index merge case which can only be run in root type, topN and limit can't be pushed to the inside index merge when it's an intersection. + // note: don't change the task enumeration order here. + allTaskTypes := []property.TaskType{property.CopSingleReadTaskType, property.CopMultiReadTaskType, property.RootTaskType} + // we move the pushLimitOrTopNForcibly check to attach2Task to do the prefer choice. + mppAllowed := lt.SCtx().GetSessionVars().IsMPPAllowed() + if mppAllowed { + allTaskTypes = append(allTaskTypes, property.MppTaskType) + } + ret := make([]base.PhysicalPlan, 0, len(allTaskTypes)+1) + + // When TopN is directly above a DataSource, set AdvisorySortItems so that + // IndexMerge can prefer partial paths that satisfy the ORDER BY. + // This enables pushing Limit to ordered partial paths. + var advisorySortItems []property.SortItem + if _, ok := lt.Children()[0].(*logicalop.DataSource); ok && len(lt.ByItems) > 0 { + advisorySortItems = make([]property.SortItem, 0, len(lt.ByItems)) + for _, byItem := range lt.ByItems { + col, ok := byItem.Expr.(*expression.Column) + if !ok { + advisorySortItems = nil + break + } + advisorySortItems = append(advisorySortItems, property.SortItem{ + Col: col, + Desc: byItem.Desc, + }) + } + } + + // Generate candidate plans for partial order optimization using prefix index FIRST. + // This is important because when use_index hint is used with a prefix index, + // we need to set ForcePartialOrder flag before other candidates are evaluated. + // Otherwise, normal TopN plans without partial order optimization might be selected. + if canUsePartialOrder4TopN(lt) { + topNWithPartialOrderProperty := getPhysTopNWithPartialOrderProperty(lt, prop) + ret = append(ret, topNWithPartialOrderProperty...) + } + + for _, tp := range allTaskTypes { + resultProp := &property.PhysicalProperty{TaskTp: tp, ExpectedCnt: math.MaxFloat64, + CTEProducerStatus: prop.CTEProducerStatus, NoCopPushDown: prop.NoCopPushDown} + topN := PhysicalTopN{ + ByItems: lt.ByItems, + PartitionBy: lt.PartitionBy, + Count: lt.Count, + Offset: lt.Offset, + }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), resultProp) + topN.SetSchema(lt.Schema()) + ret = append(ret, topN) + + // The AdvisorySortItems optimization may not fully succeed (e.g. the global filter blocks the LIMIT pushdown), + // in this case, it may generate a plan with unnecessary `keep order: true`. So we add this plan as an extra + // candidate instead of replacing the original plan. + if tp == property.CopMultiReadTaskType && len(advisorySortItems) > 0 { + resultProp = resultProp.CloneEssentialFields() + resultProp.AdvisorySortItems = advisorySortItems + topN := PhysicalTopN{ + ByItems: lt.ByItems, + PartitionBy: lt.PartitionBy, + Count: lt.Count, + Offset: lt.Offset, + }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), resultProp) + topN.SetSchema(lt.Schema()) + ret = append(ret, topN) + } + } + + // If we can generate MPP task and there's vector distance function in the order by column. + // We will try to generate a property for possible vector indexes. + if mppAllowed { + if len(lt.ByItems) != 1 { + return ret + } + vs := expression.InterpretVectorSearchExpr(lt.ByItems[0].Expr) + if vs == nil { + return ret + } + // Currently vector index only accept ascending order. + if lt.ByItems[0].Desc { + return ret + } + // Currently, we only deal with the case the TopN is directly above a DataSource. + ds, ok := lt.Children()[0].(*logicalop.DataSource) + if !ok { + return ret + } + // Reject any filters. + if len(ds.PushedDownConds) > 0 { + return ret + } + resultProp := &property.PhysicalProperty{ + TaskTp: property.MppTaskType, + ExpectedCnt: math.MaxFloat64, + CTEProducerStatus: prop.CTEProducerStatus, + } + resultProp.VectorProp.VSInfo = vs + resultProp.VectorProp.TopK = uint32(lt.Count + lt.Offset) + topN := PhysicalTopN{ + ByItems: lt.ByItems, + PartitionBy: lt.PartitionBy, + Count: lt.Count, + Offset: lt.Offset, + }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), resultProp) + topN.SetSchema(lt.Schema()) + ret = append(ret, topN) + } + return ret +} + +// canUsePartialOrder4TopN checks if the TopN's child tree satisfies the conditions +// for partial order optimization. +// Supported patterns: +// 1. TopN -> DataSource +// 2. TopN -> Selection -> DataSource +// 3. TopN -> Projection -> DataSource +// 4. TopN -> Projection -> Selection -> DataSource +// 5. TopN -> Selection -> Projection -> DataSource +// +// Note: This function only checks if the query pattern is supported. +// The actual check of whether PartialOrderInfo can pass through Projection +// is done in LogicalProjection.TryToGetChildProp during physical optimization. +func canUsePartialOrder4TopN(lt *logicalop.LogicalTopN) bool { + if !lt.SCtx().GetSessionVars().IsPartialOrderedIndexForTopNEnabled() { + return false + } + // Must have ORDER BY columns + if len(lt.ByItems) == 0 { + return false + } + + return checkPartialOrderPattern(lt.SCtx(), lt.Children()[0]) +} + +// checkPartialOrderPattern recursively checks if the plan tree matches +// a supported pattern for partial order optimization. +// Supported intermediate operators: Selection, Projection +// Terminal operator: DataSource +func checkPartialOrderPattern(ctx base.PlanContext, plan base.LogicalPlan) bool { + switch p := plan.(type) { + case *logicalop.DataSource: + return true + + case *logicalop.LogicalSelection: + // Selection can pass through, check its child + if len(p.Children()) != 1 { + return false + } + return checkPartialOrderPattern(ctx, p.Children()[0]) + + case *logicalop.LogicalProjection: + // Projection can pass through (actual column check is done in TryToGetChildProp) + if len(p.Children()) != 1 { + return false + } + return checkPartialOrderPattern(ctx, p.Children()[0]) + + default: + // Other operators are not supported + return false + } +} + +// getPhysTopNWithPartialOrderProperty generates PhysicalTopN plans with partial order property +// that use partial order optimization with prefix index. +func getPhysTopNWithPartialOrderProperty(lt *logicalop.LogicalTopN, prop *property.PhysicalProperty) []base.PhysicalPlan { + // Convert logical TopN ByItems to SortItems for PartialOrderInfo + sortItems := make([]*property.SortItem, 0, len(lt.ByItems)) + for _, byItem := range lt.ByItems { + col, ok := byItem.Expr.(*expression.Column) + if !ok { + // ORDER BY must be simple column references for partial order optimization + return nil + } + sortItems = append(sortItems, &property.SortItem{ + Col: col, + Desc: byItem.Desc, + }) + } + + // Create PhysicalProperty with PartialOrderInfo + // Use CopMultiReadTaskType for IndexLookUp + partialOrderProp := &property.PhysicalProperty{ + TaskTp: property.CopMultiReadTaskType, + // TODO: change it to limit + offset + N + ExpectedCnt: math.MaxFloat64, + PartialOrderInfo: &property.PartialOrderInfo{ + SortItems: sortItems, + }, + CTEProducerStatus: prop.CTEProducerStatus, + NoCopPushDown: prop.NoCopPushDown, + } + + topN := PhysicalTopN{ + ByItems: lt.ByItems, + PartitionBy: lt.PartitionBy, + Count: lt.Count, + Offset: lt.Offset, + }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), partialOrderProp) + topN.SetSchema(lt.Schema()) + + return []base.PhysicalPlan{topN} +} diff --git a/pkg/planner/core/operator/physicalop/task_base.go b/pkg/planner/core/operator/physicalop/task_base.go new file mode 100644 index 0000000000000..3704a7988fbd8 --- /dev/null +++ b/pkg/planner/core/operator/physicalop/task_base.go @@ -0,0 +1,605 @@ +// Copyright 2025 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package physicalop + +import ( + "math" + + "github.com/pingcap/tidb/pkg/expression" + "github.com/pingcap/tidb/pkg/kv" + "github.com/pingcap/tidb/pkg/planner/cardinality" + "github.com/pingcap/tidb/pkg/planner/core/base" + "github.com/pingcap/tidb/pkg/planner/core/cost" + "github.com/pingcap/tidb/pkg/planner/property" + "github.com/pingcap/tidb/pkg/planner/util" + "github.com/pingcap/tidb/pkg/statistics" + "github.com/pingcap/tidb/pkg/util/context" + "github.com/pingcap/tidb/pkg/util/logutil" + "github.com/pingcap/tidb/pkg/util/size" + "github.com/pingcap/tipb/go-tipb" + "go.uber.org/zap" +) + +var ( + _ base.Task = &RootTask{} + _ base.Task = &MppTask{} + _ base.Task = &CopTask{} +) + +// SimpleWarnings is a simple implementation of Warnings interface. +type SimpleWarnings struct { + warnings []*context.SQLWarn +} + +// WarningCount returns the number of warnings. +func (s *SimpleWarnings) WarningCount() int { + return len(s.warnings) +} + +// Copy implemented the simple warnings copy to avoid use the same warnings slice for different task instance. +func (s *SimpleWarnings) Copy(src *SimpleWarnings) { + warnings := make([]*context.SQLWarn, 0, len(src.warnings)) + warnings = append(warnings, src.warnings...) + s.warnings = warnings +} + +// CopyFrom copy the warnings from src to s. +func (s *SimpleWarnings) CopyFrom(src ...*SimpleWarnings) { + if src == nil { + return + } + length := 0 + for _, one := range src { + if one == nil { + continue + } + length += one.WarningCount() + } + s.warnings = make([]*context.SQLWarn, 0, length) + for _, one := range src { + if one == nil { + continue + } + s.warnings = append(s.warnings, one.warnings...) + } +} + +// AppendWarning appends a warning to the warnings slice. +func (s *SimpleWarnings) AppendWarning(warn error) { + if len(s.warnings) < math.MaxUint16 { + s.warnings = append(s.warnings, &context.SQLWarn{Level: context.WarnLevelWarning, Err: warn}) + } +} + +// AppendNote appends a note to the warnings slice. +func (s *SimpleWarnings) AppendNote(note error) { + if len(s.warnings) < math.MaxUint16 { + s.warnings = append(s.warnings, &context.SQLWarn{Level: context.WarnLevelNote, Err: note}) + } +} + +// GetWarnings returns the internal all stored warnings. +func (s *SimpleWarnings) GetWarnings() []context.SQLWarn { + // we just reuse and reorganize pointer of warning elem across different level's + // task warnings slice to avoid copy them totally leading mem cost. + // when best task is finished and final warnings is determined, we should convert + // pointer to struct to append it to session context. + warnings := make([]context.SQLWarn, 0, len(s.warnings)) + for _, w := range s.warnings { + warnings = append(warnings, *w) + } + return warnings +} + +// ************************************* RootTask Start ****************************************** + +// RootTask is the final sink node of a plan graph. It should be a single goroutine on tidb. +type RootTask struct { + p base.PhysicalPlan + + // For copTask and rootTask, when we compose physical tree bottom-up, index join need some special info + // fetched from underlying ds which built index range or table range based on these runtime constant. + IndexJoinInfo *IndexJoinInfo + + // Warnings passed through different task copy attached with more upper operator specific Warnings. (not concurrent safe) + Warnings SimpleWarnings +} + +// GetPlan returns the root task's plan. +func (t *RootTask) GetPlan() base.PhysicalPlan { + return t.p +} + +// SetPlan sets the root task's plan. +func (t *RootTask) SetPlan(p base.PhysicalPlan) { + t.p = p +} + +// Copy implements Task interface. +func (t *RootTask) Copy() base.Task { + nt := &RootTask{ + p: t.p, + + // when copying, just copy it out. + IndexJoinInfo: t.IndexJoinInfo, + } + // since *t will reuse the same warnings slice, we need to copy it out. + // because different task instance should have different warning slice. + nt.Warnings.Copy(&t.Warnings) + return nt +} + +// ConvertToRootTask implements Task interface. +func (t *RootTask) ConvertToRootTask(_ base.PlanContext) base.Task { + // root -> root, only copy another one instance. + // *p: a new pointer to pointer current task's physic plan + // warnings: a new slice to store current task-bound(p-bound) warnings. + // *indexInfo: a new pointer to inherit the index join info upward if necessary. + return t.Copy().(*RootTask) +} + +// Invalid implements Task interface. +func (t *RootTask) Invalid() bool { + return t.p == nil +} + +// Count implements Task interface. +func (t *RootTask) Count() float64 { + return t.p.StatsInfo().RowCount +} + +// Plan implements Task interface. +func (t *RootTask) Plan() base.PhysicalPlan { + return t.p +} + +// MemoryUsage return the memory usage of rootTask +func (t *RootTask) MemoryUsage() (sum int64) { + if t == nil { + return + } + sum = size.SizeOfInterface + size.SizeOfBool + if t.p != nil { + sum += t.p.MemoryUsage() + } + return sum +} + +// AppendWarning appends a warning +func (t *RootTask) AppendWarning(err error) { + t.Warnings.AppendWarning(err) +} + +// ************************************* RootTask End ****************************************** + +// ************************************* MPPTask Start ****************************************** + +// MppTask can not : +// 1. keep order +// 2. support double read +// 3. consider virtual columns. +// 4. TODO: partition prune after close +type MppTask struct { + p base.PhysicalPlan + + partTp property.MPPPartitionType + HashCols []*property.MPPPartitionColumn + + // rootTaskConds record filters of TableScan that cannot be pushed down to TiFlash. + + // For logical plan like: HashAgg -> Selection -> TableScan, if filters in Selection cannot be pushed to TiFlash. + // Planner will generate physical plan like: PhysicalHashAgg -> PhysicalSelection -> TableReader -> PhysicalTableScan(cop tiflash) + // Because planner will make mppTask invalid directly then use copTask directly. + + // But in DisaggregatedTiFlash mode, cop and batchCop protocol is disabled, so we have to consider this situation for mppTask. + // When generating PhysicalTableScan, if prop.TaskTp is RootTaskType, mppTask will be converted to rootTask, + // and filters in RootTaskConds will be added in a Selection which will be executed in TiDB. + // So physical plan be like: PhysicalHashAgg -> PhysicalSelection -> TableReader -> ExchangeSender -> PhysicalTableScan(mpp tiflash) + RootTaskConds []expression.Expression + tblColHists *statistics.HistColl + + // Warnings passed through different task copy attached with more upper operator specific Warnings. (not concurrent safe) + Warnings SimpleWarnings +} + +// NewMppTask creates a new mpp task. +func NewMppTask(p base.PhysicalPlan, partTp property.MPPPartitionType, hashCols []*property.MPPPartitionColumn, tblColHists *statistics.HistColl, warnings ...*SimpleWarnings) *MppTask { + mt := &MppTask{ + p: p, + partTp: partTp, + HashCols: hashCols, + tblColHists: tblColHists, + } + mt.Warnings.CopyFrom(warnings...) + return mt +} + +// GetPartitionType returns the partition type of the mpp task. +func (t *MppTask) GetPartitionType() property.MPPPartitionType { + return t.partTp +} + +// GetHashCols returns the hash columns of the mpp task. +func (t *MppTask) GetHashCols() []*property.MPPPartitionColumn { + return t.HashCols +} + +// GetWarnings returns the warnings of the mpp task. +func (t *MppTask) GetWarnings() *SimpleWarnings { + return &t.Warnings +} + +// GetTblColHists returns the table column statistics of the mpp task. +func (t *MppTask) GetTblColHists() *statistics.HistColl { + return t.tblColHists +} + +// Count implements Task interface. +func (t *MppTask) Count() float64 { + return t.p.StatsInfo().RowCount +} + +// Copy implements Task interface. +func (t *MppTask) Copy() base.Task { + nt := *t + // since *t will reuse the same warnings slice, we need to copy it out. + // cause different task instance should have different warning slice. + nt.Warnings.Copy(&t.Warnings) + return &nt +} + +// Plan implements Task interface. +func (t *MppTask) Plan() base.PhysicalPlan { + return t.p +} + +// Invalid implements Task interface. +func (t *MppTask) Invalid() bool { + return t.p == nil +} + +// ConvertToRootTask implements Task interface. +func (t *MppTask) ConvertToRootTask(ctx base.PlanContext) base.Task { + return t.Copy().(*MppTask).ConvertToRootTaskImpl(ctx) +} + +// MemoryUsage return the memory usage of mppTask +func (t *MppTask) MemoryUsage() (sum int64) { + if t == nil { + return + } + + sum = size.SizeOfInterface + size.SizeOfInt + size.SizeOfSlice + int64(cap(t.HashCols))*size.SizeOfPointer + if t.p != nil { + sum += t.p.MemoryUsage() + } + return +} + +// AppendWarning appends a warning +func (t *MppTask) AppendWarning(err error) { + t.Warnings.AppendWarning(err) +} + +// ConvertToRootTaskImpl implements Task interface. +func (t *MppTask) ConvertToRootTaskImpl(ctx base.PlanContext) (rt *RootTask) { + defer func() { + // mppTask should inherit the indexJoinInfo upward. + // because mpp task bottom doesn't form the indexJoin related cop task. + if t.Warnings.WarningCount() > 0 { + rt.Warnings.CopyFrom(&t.Warnings) + } + }() + // In disaggregated-tiflash mode, need to consider generated column. + tryExpandVirtualColumn(t.p) + sender := PhysicalExchangeSender{ + ExchangeType: tipb.ExchangeType_PassThrough, + }.Init(ctx, t.p.StatsInfo()) + sender.SetChildren(t.p) + + p := PhysicalTableReader{ + TablePlan: sender, + StoreType: kv.TiFlash, + }.Init(ctx, t.p.QueryBlockOffset()) + p.SetStats(t.p.StatsInfo()) + collectPartitionInfosFromMPPPlan(p, t.p) + // Preserve partition pruning metadata for single-table readers. + // The metadata is produced on the DataSource side and already aligned with + // ds.TblCols, so it can be reused by root-task fallback paths. + if len(p.TableScanAndPartitionInfos) == 1 { + if pi := p.TableScanAndPartitionInfos[0].PhysPlanPartInfo; pi != nil { + p.PlanPartInfo = pi.CloneForPlanCache() + } + } + rt = &RootTask{} + rt.SetPlan(p) + + if len(t.RootTaskConds) > 0 { + // Some Filter cannot be pushed down to TiFlash, need to add Selection in rootTask, + // so this Selection will be executed in TiDB. + _, isTableScan := t.p.(*PhysicalTableScan) + _, isSelection := t.p.(*PhysicalSelection) + if isSelection { + _, isTableScan = t.p.Children()[0].(*PhysicalTableScan) + } + if !isTableScan { + // Need to make sure oriTaskPlan is TableScan, because rootTaskConds is part of TableScan.FilterCondition. + // It's only for TableScan. This is ensured by converting mppTask to rootTask just after TableScan is built, + // so no other operators are added into this mppTask. + logutil.BgLogger().Error("expect Selection or TableScan for mppTask.p", zap.String("mppTask.p", t.p.TP())) + return base.InvalidTask.(*RootTask) + } + selectivity, err := cardinality.Selectivity(ctx, t.tblColHists, t.RootTaskConds, nil) + if err != nil { + logutil.BgLogger().Debug("calculate selectivity failed, use selection factor", zap.Error(err)) + selectivity = cost.SelectionFactor + } + sel := PhysicalSelection{Conditions: t.RootTaskConds}.Init(ctx, rt.GetPlan().StatsInfo().Scale(ctx.GetSessionVars(), selectivity), rt.GetPlan().QueryBlockOffset()) + sel.FromDataSource = true + sel.SetChildren(rt.GetPlan()) + rt.SetPlan(sel) + } + return rt +} + +// SetPlan sets the mpp task's plan. +func (t *MppTask) SetPlan(p base.PhysicalPlan) { + t.p = p +} + +// ************************************* MPPTask End ****************************************** + +// ************************************* CopTask Start ****************************************** + +// CopTask is a task that runs in a distributed kv store. +// TODO: In future, we should split copTask to indexTask and tableTask. +type CopTask struct { + IndexPlan base.PhysicalPlan + TablePlan base.PhysicalPlan + // Whether tries to push down index lookup to TiKV and where this action comes + IndexLookUpPushDownBy util.IndexLookUpPushDownByType + // IndexPlanFinished means we have finished index plan. + IndexPlanFinished bool + // KeepOrder indicates if the plan scans data by order. + KeepOrder bool + // NeedExtraProj means an extra prune is needed because + // in double read / index merge cases, they may output one more column for handle(row id). + NeedExtraProj bool + // OriginSchema is the target schema to be projected to when NeedExtraProj is true. + OriginSchema *expression.Schema + + ExtraHandleCol *expression.Column + CommonHandleCols []*expression.Column + // TblColHists stores the original stats of DataSource, it is used to get + // average row width when computing network cost. + TblColHists *statistics.HistColl + // TblCols stores the original columns of DataSource before being pruned, it + // is used to compute average row width when computing scan cost. + TblCols []*expression.Column + + IdxMergePartPlans []base.PhysicalPlan + IdxMergeIsIntersection bool + IdxMergeAccessMVIndex bool + // IdxMergeMatchWithAdvisorySortItems indicates the IndexMerge property matching + // used advisory sort items (i.e. no SortItems but SortItemsHints was set). + IdxMergeMatchWithAdvisorySortItems bool + // IdxMergePartPlansMatchResults stores each partial path's matchProperty result. + // Set by convertToIndexMergeScan. Length equals len(IdxMergePartPlans) or 0. + // 0 length may be caused by cases like Intersection type IndexMerge, which can't satisfy any order property. When + // its length is 0, it should be considered as all property.PropNotMatched. + IdxMergePartPlansMatchResults []property.PhysicalPropMatchResult + + // RootTaskConds stores select conditions containing virtual columns. + // These conditions can't push to TiKV, so we have to add a selection for rootTask + RootTaskConds []expression.Expression + + // For table partition. + PhysPlanPartInfo *PhysPlanPartInfo + + // ExpectCnt is the expected row count of upper task, 0 for unlimited. + // It's used for deciding whether using paging distsql. + ExpectCnt uint64 + + // For copTask and rootTask, when we compose physical tree bottom-up, index join need some special info + // fetched from underlying ds which built index range or table range based on these runtime constant. + IndexJoinInfo *IndexJoinInfo + + // PartialOrderMatchResult stores the match result for partial order optimization. + // Set by convertToIndexScan when a prefix index provides partial order for TopN. + PartialOrderMatchResult *property.PartialOrderMatchResult + + // Warnings passed through different task copy attached with more upper operator specific Warnings. (not concurrent safe) + Warnings SimpleWarnings +} + +// AppendWarning appends a warning +func (t *CopTask) AppendWarning(err error) { + t.Warnings.AppendWarning(err) +} + +// Invalid implements Task interface. +func (t *CopTask) Invalid() bool { + return t.TablePlan == nil && t.IndexPlan == nil && len(t.IdxMergePartPlans) == 0 +} + +// Count implements Task interface. +func (t *CopTask) Count() float64 { + if t.IndexPlanFinished { + return t.TablePlan.StatsInfo().RowCount + } + return t.IndexPlan.StatsInfo().RowCount +} + +// Copy implements Task interface. +func (t *CopTask) Copy() base.Task { + nt := *t + // since *t will reuse the same warnings slice, we need to copy it out. + // cause different task instance should have different warning slice. + nt.Warnings.Copy(&t.Warnings) + return &nt +} + +// Plan implements Task interface. +// copTask plan should be careful with indexMergeReader, whose real plan is stored in +// idxMergePartPlans, when its indexPlanFinished is marked with false. +func (t *CopTask) Plan() base.PhysicalPlan { + if t.IndexPlanFinished { + return t.TablePlan + } + return t.IndexPlan +} + +// MemoryUsage return the memory usage of copTask +func (t *CopTask) MemoryUsage() (sum int64) { + if t == nil { + return + } + + sum = size.SizeOfInterface*(2+int64(cap(t.IdxMergePartPlans)+cap(t.RootTaskConds))) + size.SizeOfBool*3 + size.SizeOfUint64 + + size.SizeOfPointer*(3+int64(cap(t.CommonHandleCols)+cap(t.TblCols))) + size.SizeOfSlice*4 + t.PhysPlanPartInfo.MemoryUsage() + if t.IndexPlan != nil { + sum += t.IndexPlan.MemoryUsage() + } + if t.TablePlan != nil { + sum += t.TablePlan.MemoryUsage() + } + if t.OriginSchema != nil { + sum += t.OriginSchema.MemoryUsage() + } + if t.ExtraHandleCol != nil { + sum += t.ExtraHandleCol.MemoryUsage() + } + + for _, col := range t.CommonHandleCols { + sum += col.MemoryUsage() + } + for _, col := range t.TblCols { + sum += col.MemoryUsage() + } + for _, p := range t.IdxMergePartPlans { + sum += p.MemoryUsage() + } + for _, expr := range t.RootTaskConds { + sum += expr.MemoryUsage() + } + return +} + +// ConvertToRootTask implements Task interface. +func (t *CopTask) ConvertToRootTask(ctx base.PlanContext) base.Task { + // copy one to avoid changing itself. + return t.Copy().(*CopTask).convertToRootTaskImpl(ctx) +} + +func (t *CopTask) convertToRootTaskImpl(ctx base.PlanContext) (rt *RootTask) { + defer func() { + if t.IndexJoinInfo != nil { + // return indexJoinInfo upward, when copTask is converted to rootTask. + rt.IndexJoinInfo = t.IndexJoinInfo + } + if t.Warnings.WarningCount() > 0 { + rt.Warnings.CopyFrom(&t.Warnings) + } + }() + // copTasks are run in parallel, to make the estimated cost closer to execution time, we amortize + // the cost to cop iterator workers. According to `CopClient::Send`, the concurrency + // is Min(DistSQLScanConcurrency, numRegionsInvolvedInScan), since we cannot infer + // the number of regions involved, we simply use DistSQLScanConcurrency. + t.FinishIndexPlan() + // Network cost of transferring rows of table scan to TiDB. + if t.TablePlan != nil { + tp := t.TablePlan + for len(tp.Children()) > 0 { + tp = tp.Children()[0] + } + ts := tp.(*PhysicalTableScan) + prevColumnLen := len(ts.Columns) + prevSchema := ts.Schema().Clone() + ts.Columns = ExpandVirtualColumn(ts.Columns, ts.Schema(), ts.Table.Columns) + if !t.NeedExtraProj && len(ts.Columns) > prevColumnLen { + // Add a projection to make sure not to output extract columns. + t.NeedExtraProj = true + t.OriginSchema = prevSchema + } + } + newTask := &RootTask{} + if t.IdxMergePartPlans != nil { + p := PhysicalIndexMergeReader{ + PartialPlansRaw: t.IdxMergePartPlans, + TablePlan: t.TablePlan, + IsIntersectionType: t.IdxMergeIsIntersection, + AccessMVIndex: t.IdxMergeAccessMVIndex, + KeepOrder: t.KeepOrder, + }.Init(ctx, t.IdxMergePartPlans[0].QueryBlockOffset()) + p.PlanPartInfo = t.PhysPlanPartInfo + newTask.SetPlan(p) + if t.NeedExtraProj { + schema := t.OriginSchema + proj := PhysicalProjection{Exprs: expression.Column2Exprs(schema.Columns)}.Init(ctx, p.StatsInfo(), t.IdxMergePartPlans[0].QueryBlockOffset(), nil) + proj.SetSchema(schema) + proj.SetChildren(p) + newTask.SetPlan(proj) + } + t.handleRootTaskConds(ctx, newTask) + return newTask + } + if t.IndexPlan != nil && t.TablePlan != nil { + newTask = BuildIndexLookUpTask(ctx, t) + } else if t.IndexPlan != nil { + p := PhysicalIndexReader{IndexPlan: t.IndexPlan}.Init(ctx, t.IndexPlan.QueryBlockOffset()) + p.PlanPartInfo = t.PhysPlanPartInfo + p.SetStats(t.IndexPlan.StatsInfo()) + newTask.SetPlan(p) + } else { + tp := t.TablePlan + for len(tp.Children()) > 0 { + tp = tp.Children()[0] + } + ts := tp.(*PhysicalTableScan) + p := PhysicalTableReader{ + TablePlan: t.TablePlan, + StoreType: ts.StoreType, + IsCommonHandle: ts.Table.IsCommonHandle, + }.Init(ctx, t.TablePlan.QueryBlockOffset()) + p.PlanPartInfo = t.PhysPlanPartInfo + p.SetStats(t.TablePlan.StatsInfo()) + + // If agg was pushed down in Attach2Task(), the partial agg was placed on the top of tablePlan, the final agg was + // placed above the PhysicalTableReader, and the schema should have been set correctly for them, the schema of + // partial agg contains the columns needed by the final agg. + // If we add the projection here, the projection will be between the final agg and the partial agg, then the + // schema will be broken, the final agg will fail to find needed columns in ResolveIndices(). + // Besides, the agg would only be pushed down if it doesn't contain virtual columns, so virtual column should not be affected. + aggPushedDown := false + switch p.TablePlan.(type) { + case *PhysicalHashAgg, *PhysicalStreamAgg: + aggPushedDown = true + } + + if t.NeedExtraProj && !aggPushedDown { + proj := PhysicalProjection{Exprs: expression.Column2Exprs(t.OriginSchema.Columns)}.Init(ts.SCtx(), ts.StatsInfo(), ts.QueryBlockOffset(), nil) + proj.SetSchema(t.OriginSchema) + proj.SetChildren(p) + newTask.SetPlan(proj) + } else { + newTask.SetPlan(p) + } + } + + t.handleRootTaskConds(ctx, newTask) + return newTask +} + +// ************************************* CopTask End ****************************************** diff --git a/pkg/planner/core/task.go b/pkg/planner/core/task.go index 02d9f398b293c..034a9c1e1c012 100644 --- a/pkg/planner/core/task.go +++ b/pkg/planner/core/task.go @@ -1051,7 +1051,28 @@ func (p *PhysicalTopN) Attach2Task(tasks ...base.Task) base.Task { return newTask } } +<<<<<<< HEAD if copTask, ok := t.(*CopTask); ok && needPushDown && p.canPushDownToTiKV(copTask) && len(copTask.rootTaskConds) == 0 { +======= + if copTask, ok := t.(*physicalop.CopTask); ok && needPushDown && canPushDownToTiKV(p, copTask) && len(copTask.RootTaskConds) == 0 { + // Handle IndexMerge with advisory sort items when some (but not all) + // partial paths satisfy the sort order. When all paths satisfy, the + // existing Limit pushdown via attach2Task4PhysicalLimit gives a better plan. + if len(copTask.IdxMergePartPlans) > 0 && !copTask.IndexPlanFinished && !copTask.IdxMergeIsIntersection && + copTask.IdxMergeMatchWithAdvisorySortItems { + intest.Assert(len(copTask.IdxMergePartPlans) == len(copTask.IdxMergePartPlansMatchResults)) + allSatisfy := true + for _, result := range copTask.IdxMergePartPlansMatchResults { + if !result.Matched() { + allSatisfy = false + break + } + } + if !allSatisfy { + return handleAdvisorySortItemsForIndexMerge(p, copTask) + } + } +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) // If all columns in topN are from index plan, we push it to index plan, otherwise we finish the index plan and // push it to table plan. var pushedDownTopN *PhysicalTopN @@ -1077,8 +1098,187 @@ func (p *PhysicalTopN) Attach2Task(tasks ...base.Task) base.Task { return attachPlan2Task(p, rootTask) } +<<<<<<< HEAD // Attach2Task implements the PhysicalPlan interface. func (p *PhysicalExpand) Attach2Task(tasks ...base.Task) base.Task { +======= +// handlePartialOrderTopN handles the partial order TopN scenario. +// It fills the partial-order-related fields on the TopN itself and, when possible, +// pushes down a special Limit with prefix information to the index side. +// There are two different cases: +// +// Case1: Two phase TopN, where TiDB keeps TopN and TiKV applies a partial-order Limit: +// +// TopN(with partial info) +// └─IndexLookUp +// └─Limit(with partial info) +// ... (other operators) +// +// Case2: One phase TopN, where the whole TopN can only be executed in TiDB: +// +// TopN(with partial info) +// ├─IndexPlan +// └─TablePlan +func handlePartialOrderTopN(p *physicalop.PhysicalTopN, copTask *physicalop.CopTask) base.Task { + matchResult := copTask.PartialOrderMatchResult + + // Init partial order params PrefixCol and PrefixLen. + // PartialOrderedLimit = Count + Offset. + // TopN(with partial order) executor will short-cut read when it already handle "p.Count + p.Offset" rows. + // Also it need to read X more rows (which prefix value is same as the last line prefix value) to ensure correctness. + partialOrderedLimit := p.Count + p.Offset + p.PrefixLen = matchResult.PrefixLen + // Find the corresponding prefix column in TopN's schema. + // matchResult.PrefixCol is from IndexScan's schema, but + // Projection operators may remap columns. We need to find the column in TopN's + // schema that has the same UniqueID as matchResult.PrefixCol. + // Column UniqueID remains unchanged even after Projection remapping. + p.PrefixCol = nil + for _, col := range p.Schema().Columns { + if col.UniqueID == matchResult.PrefixCol.UniqueID { + p.PrefixCol = col + break + } + } + // Fallback: if not found in rootTask schema (should not happen) + if p.PrefixCol == nil { + return base.InvalidTask + } + + // Decide whether we can push a special Limit down to the index plan. + // Conditions: + // - Not an IndexMerge. + // - IndexPlan is not finished (IndexPlanFinished == false). + // - No root task conditions. + // Since the output of the table plan is not ordered. So if limit can be pushed down, it must be pushed down to the index plan. + // Therefore, we performed this related check here. + canPushLimit := false + if len(copTask.IdxMergePartPlans) == 0 && + !copTask.IndexPlanFinished && + len(copTask.RootTaskConds) == 0 && + copTask.IndexPlan != nil { + canPushLimit = true + } + + if canPushLimit { + // Two-layer mode: TiDB TopN(with partial order info.) + TiKV limit(with partial order info.) + // The estRows of partial order TopN : N + X + // N: The partialOrderedLimit, N means the value of TopN, N = Count + Offset. + // X: The estimated extra rows to read to fulfill the TopN. + // We need to read more prefix values that are the same as the last line + // to ensure the correctness of the final calculation of the Top n rows. + maxX := estimateMaxXForPartialOrder() + estimatedRows := float64(partialOrderedLimit) + float64(maxX) + childProfile := copTask.IndexPlan.StatsInfo() + limitStats := property.DeriveLimitStats(childProfile, estimatedRows) + + pushedDownLimit := physicalop.PhysicalLimit{ + Count: partialOrderedLimit, + PrefixCol: p.PrefixCol, + PrefixLen: matchResult.PrefixLen, + }.Init(p.SCtx(), limitStats, p.QueryBlockOffset()) + pushedDownLimit.SetChildren(copTask.IndexPlan) + pushedDownLimit.SetSchema(copTask.IndexPlan.Schema()) + copTask.IndexPlan = pushedDownLimit + } + + // Always keep TopN in TiDB as the upper layer. + rootTask := copTask.ConvertToRootTask(p.SCtx()) + return attachPlan2Task(p, rootTask) +} + +// estimateMaxXForPartialOrder estimates the extra rows X to read for partial order optimization. +// This value is used for statistics (row count estimation). +func estimateMaxXForPartialOrder() uint64 { + // TODO: implement it by TopN/buckets and adjust it by session variable. + return 0 +} + +// handleAdvisorySortItemsForIndexMerge handles TopN pushdown when IndexMerge +// has advisory sort items satisfaction info. It pushes Limit to partial paths +// that satisfy the sort order and TopN to those that don't, then keeps a root +// TopN for final merge. +func handleAdvisorySortItemsForIndexMerge(p *physicalop.PhysicalTopN, copTask *physicalop.CopTask) base.Task { + newCount := p.Offset + p.Count + + cols := make([]*expression.Column, 0, len(p.ByItems)) + for _, item := range p.ByItems { + cols = append(cols, expression.ExtractColumns(item.Expr)...) + } + newPartitionBy := make([]property.SortItem, 0, len(p.GetPartitionBy())) + for _, expr := range p.GetPartitionBy() { + newPartitionBy = append(newPartitionBy, expr.Clone()) + } + + for i, partialPlan := range copTask.IdxMergePartPlans { + if copTask.IdxMergePartPlansMatchResults[i].Matched() { + // This partial path satisfies the sort order, push Limit. + childProfile := partialPlan.StatsInfo() + stats := property.DeriveLimitStats(childProfile, float64(newCount)) + pushedDownLimit := physicalop.PhysicalLimit{ + Count: newCount, + PartitionBy: newPartitionBy, + }.Init(p.SCtx(), stats, p.QueryBlockOffset()) + pushedDownLimit.SetChildren(partialPlan) + pushedDownLimit.SetSchema(partialPlan.Schema()) + copTask.IdxMergePartPlans[i] = pushedDownLimit + } else if canPushToIndexPlan(partialPlan, cols) { + // This partial path does not satisfy the sort order, push TopN. + pushedDownTopN, _ := getPushedDownTopN(p, partialPlan, copTask.GetStoreType()) + copTask.IdxMergePartPlans[i] = pushedDownTopN + } + } + + // Push TopN to the table plan side if it exists. + if copTask.TablePlan != nil { + pushedDownTopN, _ := getPushedDownTopN(p, copTask.TablePlan, copTask.GetStoreType()) + copTask.TablePlan = pushedDownTopN + } + + // Keep the root TopN as the final merge layer. + rootTask := copTask.ConvertToRootTask(p.SCtx()) + if len(p.GetPartitionBy()) > 0 { + return rootTask + } + return attachPlan2Task(p, rootTask) +} + +// attach2Task4PhysicalProjection implements PhysicalPlan interface. +func attach2Task4PhysicalProjection(pp base.PhysicalPlan, tasks ...base.Task) base.Task { + p := pp.(*physicalop.PhysicalProjection) + t := tasks[0].Copy() + // when it's a copTask, we can push down projection to indexPlan or tablePlan respectively, say logical plan: proj->ds, when ds can supply the needed columns to proj + // doesn't mean its index plan can supply needed columns to proj when it's double read and index plan is not finished. When it's not, we should finish the index plan + // immediately and push down projection to table plan if possible. For the case of index merge, we can only push down projection to table plan since index plan and table + // plan will be union-ed together and the final output will be used by projection, so both of them should provide needed columns to projection. + if cop, ok := t.(*physicalop.CopTask); ok { + if (len(cop.RootTaskConds) == 0 && len(cop.IdxMergePartPlans) == 0) && expression.CanExprsPushDown(util.GetPushDownCtx(p.SCtx()), p.Exprs, cop.GetStoreType()) { + if !cop.IndexPlanFinished { + // when index plan is not finished, and index plan can not supply the columns the proj needed. + if !canPushToIndexPlan(cop.IndexPlan, expression.ExtractColumnsFromExpressions(p.Exprs, nil)) { + // finish index plan and push down projection to table plan. + cop.FinishIndexPlan() + } + } + copTask := attachPlan2Task(p, cop) + return copTask + } + } else if mpp, ok := t.(*physicalop.MppTask); ok { + if expression.CanExprsPushDown(util.GetPushDownCtx(p.SCtx()), p.Exprs, kv.TiFlash) { + p.SetChildren(mpp.Plan()) + mpp.SetPlan(p) + return mpp + } + } + t = t.ConvertToRootTask(p.SCtx()) + t = attachPlan2Task(p, t) + return t +} + +// attach2Task4PhysicalExpand implements PhysicalPlan interface. +func attach2Task4PhysicalExpand(pp base.PhysicalPlan, tasks ...base.Task) base.Task { + p := pp.(*physicalop.PhysicalExpand) +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) t := tasks[0].Copy() // current expand can only be run in MPP TiFlash mode or Root Tidb mode. // if expr inside could not be pushed down to tiFlash, it will error in converting to pb side. diff --git a/pkg/planner/property/physical_property.go b/pkg/planner/property/physical_property.go index 7b95959d71dfe..71dffb0baf790 100644 --- a/pkg/planner/property/physical_property.go +++ b/pkg/planner/property/physical_property.go @@ -287,6 +287,97 @@ type PhysicalProperty struct { *expression.VectorHelper TopK uint32 } +<<<<<<< HEAD +======= + + IndexJoinProp *IndexJoinRuntimeProp + + // NoCopPushDown indicates if planner must not push this agg down to coprocessor. + // It is true when the agg is in the outer child tree of apply. + NoCopPushDown bool + + // PartialOrderInfo is used for TopN's partial order optimization. + // When this field is not nil, it indicates that prefix index can be used + // to provide partial order for TopN. + // For example: + // query: order by a, b limit 10 + // partialOrderInfo: sortItems: [a, b] + // The partialOrderInfo property will pass through to the datasource and try to matchPartialOrderProperty such as: + // index: (a, b(10) ) + PartialOrderInfo *PartialOrderInfo + + // AdvisorySortItems contains sort items that are preferred but not required. + // When SortItems is empty and AdvisorySortItems is not, DataSource can try to + // generate paths that satisfy these sort items, enabling Limit pushdown to + // partial paths of IndexMerge. + // Currently only set when TopN is directly above a DataSource. + AdvisorySortItems []SortItem +} + +// PartialOrderInfo records information needed for partial order optimization. +// When PhysicalProperty.PartialOrderInfo is not nil, it indicates that +// prefix index can be used to provide partial order. +type PartialOrderInfo struct { + // SortItems are the ORDER BY columns from TopN + SortItems []*SortItem +} + +// AllSameOrder checks if all the items have same order. +func (p *PartialOrderInfo) AllSameOrder() (isSame bool, desc bool) { + if len(p.SortItems) == 0 { + return true, false + } + for i := 1; i < len(p.SortItems); i++ { + if p.SortItems[i].Desc != p.SortItems[i-1].Desc { + return + } + } + return true, p.SortItems[0].Desc +} + +// PartialOrderMatchResult records the result of matching partial order property with an access path. +// It is stored in candidatePath to allow each path to have its own match result. +type PartialOrderMatchResult struct { + // Matched indicates whether this path can provide partial order + Matched bool + // PrefixCol is the last and only one prefix column ID of index, only used for executor part + // For example: + // Query ORDER BY a,b,c + // Index: a, b, c(10) + // PrefixCol: c, the col c + // PrefixLen: 10, the col length of c in index + PrefixCol *expression.Column + + // PrefixLen is the prefix length in bytes for prefix index, only used for executor part + PrefixLen int +} + +// IndexJoinRuntimeProp is the inner runtime property for index join. +type IndexJoinRuntimeProp struct { + // for complete the last col range access, cuz its runtime constant. + OtherConditions []expression.Expression + // for filling the range msg info + OuterJoinKeys []*expression.Column + // for inner ds/index to detect the range, cuz its runtime constant. + InnerJoinKeys []*expression.Column + // AvgInnerRowCnt is computed from join.EqualCondCount / outerChild.RowCount. + // since ds only can build empty range before seeing runtime data, the so inner + // ds can get an accurate countAfterAccess. Once index join prop pushed to the + // deeper side like through join, the deeper DS's countAfterAccess should be + // thought twice. + AvgInnerRowCnt float64 + // since tableRangeScan and indexRangeScan can't be told which one is better at + // copTask phase because of the latter attached operators into cop and the single + // and double reader cost consideration. Therefore, we introduce another bool to + // indicate prefer tableRangeScan or indexRangeScan each at a time. + TableRangeScan bool +} + +// CloneEssentialFields clone the essential fields for IndexJoinRuntimeProp. +func (ijr *IndexJoinRuntimeProp) CloneEssentialFields() *IndexJoinRuntimeProp { + one := *ijr + return &one +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) } // NewPhysicalProperty builds property from columns. @@ -393,7 +484,16 @@ func (p *PhysicalProperty) HashCode() []byte { if p.hashcode != nil { return p.hashcode } +<<<<<<< HEAD hashcodeSize := 8 + 8 + 8 + (16+8)*len(p.SortItems) + 8 +======= + hashcodeSize := 8 + 8 + 8 + (16+8)*len(p.SortItems) + 8 + (16+8)*len(p.AdvisorySortItems) + 8 + if p.PartialOrderInfo != nil { + hashcodeSize += (16 + 8) * len(p.PartialOrderInfo.SortItems) + } else { + hashcodeSize += 8 + } +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) p.hashcode = make([]byte, 0, hashcodeSize) if p.CanAddEnforcer { p.hashcode = codec.EncodeInt(p.hashcode, 1) @@ -423,6 +523,56 @@ func (p *PhysicalProperty) HashCode() []byte { } } p.hashcode = append(p.hashcode, codec.EncodeInt(nil, int64(p.CTEProducerStatus))...) +<<<<<<< HEAD +======= + // encode indexJoinProp into physical prop's hashcode. + if p.IndexJoinProp != nil { + for _, expr := range p.IndexJoinProp.OtherConditions { + p.hashcode = append(p.hashcode, expr.HashCode()...) + } + for _, col := range p.IndexJoinProp.OuterJoinKeys { + p.hashcode = append(p.hashcode, col.HashCode()...) + } + for _, col := range p.IndexJoinProp.InnerJoinKeys { + p.hashcode = append(p.hashcode, col.HashCode()...) + } + p.hashcode = codec.EncodeFloat(p.hashcode, p.IndexJoinProp.AvgInnerRowCnt) + if p.IndexJoinProp.TableRangeScan { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + } + // encode NoCopPushDown into physical prop's hashcode. + if p.NoCopPushDown { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + // encode PartialOrderInfo into physical prop's hashcode. + if p.PartialOrderInfo != nil { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + for _, item := range p.PartialOrderInfo.SortItems { + p.hashcode = append(p.hashcode, item.Col.HashCode()...) + if item.Desc { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + } + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + // encode SortItemsHints into physical prop's hashcode. + for _, item := range p.AdvisorySortItems { + p.hashcode = append(p.hashcode, item.Col.HashCode()...) + if item.Desc { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + } +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) return p.hashcode } @@ -443,6 +593,14 @@ func (p *PhysicalProperty) CloneEssentialFields() *PhysicalProperty { MPPPartitionCols: p.MPPPartitionCols, RejectSort: p.RejectSort, CTEProducerStatus: p.CTEProducerStatus, +<<<<<<< HEAD +======= + NoCopPushDown: p.NoCopPushDown, + PartialOrderInfo: p.PartialOrderInfo, // Copy PartialOrderInfo for TopN partial order optimization + AdvisorySortItems: p.AdvisorySortItems, + // we default not to clone basic indexJoinProp by default. + // and only call admitIndexJoinProp to inherit the indexJoinProp for special pattern operators. +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) } return prop } @@ -478,5 +636,8 @@ func (p *PhysicalProperty) MemoryUsage() (sum int64) { for _, mppCol := range p.MPPPartitionCols { sum += mppCol.MemoryUsage() } + for _, sortItem := range p.AdvisorySortItems { + sum += sortItem.MemoryUsage() + } return } diff --git a/tests/integrationtest/r/executor/index_lookup_pushdown.result b/tests/integrationtest/r/executor/index_lookup_pushdown.result index 5057078dc437e..98f408de63c31 100644 --- a/tests/integrationtest/r/executor/index_lookup_pushdown.result +++ b/tests/integrationtest/r/executor/index_lookup_pushdown.result @@ -363,6 +363,7 @@ B 2 5050 500 50 explain select /*+ index_lookup_pushdown(t2, i) */ * from t2 where b != 3 order by a desc, b desc limit 4; id estRows task access object operator info TopN_9 4.00 root executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 +<<<<<<< HEAD └─IndexLookUp_17 4.00 root ├─LocalIndexLookUp_19(Build) 4.00 cop[tikv] index handle offsets:[] │ ├─TopN_16(Build) 4.00 cop[tikv] executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 @@ -370,6 +371,15 @@ TopN_9 4.00 root executor__index_lookup_pushdown.t2.a:desc, executor__index_loo │ │ └─IndexFullScan_13 10000.00 cop[tikv] table:t2, index:i(c) keep order:false, stats:pseudo │ └─TableRowIDScan_18(Probe) 4.00 cop[tikv] table:t2 keep order:false, stats:pseudo └─TableRowIDScan_14(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo +======= +└─IndexLookUp_20 4.00 root + ├─LocalIndexLookUp_22(Build) 4.00 cop[tikv] index handle offsets:[] + │ ├─TopN_19(Build) 4.00 cop[tikv] executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 + │ │ └─Selection_18 6656.67 cop[tikv] ne(executor__index_lookup_pushdown.t2.b, 3) + │ │ └─IndexFullScan_16 10000.00 cop[tikv] table:t2, index:i(c) keep order:false, stats:pseudo + │ └─TableRowIDScan_21(Probe) 4.00 cop[tikv] table:t2 keep order:false, stats:pseudo + └─TableRowIDScan_17(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) select /*+ index_lookup_pushdown(t2, i) */ * from t2 where b != 3 order by a desc, b desc limit 4; a b c d e C 2 2080 800 20 diff --git a/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result b/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result index deb8445017822..8809d8982f804 100644 --- a/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result +++ b/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result @@ -23,12 +23,21 @@ a b c explain select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 where b < 10 order by a limit 3; id estRows task access object operator info TopN_9 3.00 root executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 +<<<<<<< HEAD └─IndexLookUp_16 3.00 root partition:all ├─LocalIndexLookUp_18(Build) 3.00 cop[tikv] index handle offsets:[1] │ ├─TopN_15(Build) 3.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 │ │ └─IndexRangeScan_13 3323.33 cop[tikv] table:tp1, index:b(b) range:[-inf,10), keep order:false, stats:pseudo │ └─TableRowIDScan_17(Probe) 3.00 cop[tikv] table:tp1 keep order:false, stats:pseudo └─TableRowIDScan_14(Probe) 0.00 cop[tikv] table:tp1 keep order:false, stats:pseudo +======= +└─IndexLookUp_19 3.00 root partition:all + ├─LocalIndexLookUp_21(Build) 3.00 cop[tikv] index handle offsets:[1] + │ ├─TopN_18(Build) 3.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 + │ │ └─IndexRangeScan_16 3323.33 cop[tikv] table:tp1, index:b(b) range:[-inf,10), keep order:false, stats:pseudo + │ └─TableRowIDScan_20(Probe) 3.00 cop[tikv] table:tp1 keep order:false, stats:pseudo + └─TableRowIDScan_17(Probe) 0.00 cop[tikv] table:tp1 keep order:false, stats:pseudo +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 where b < 10 order by a limit 3; a b c 2 9 20 @@ -207,6 +216,7 @@ explain select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 order by c limit id estRows task access object operator info TopN_19 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 └─PartitionUnion_24 20.00 root +<<<<<<< HEAD ├─TopN_26 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 │ └─IndexLookUp_33 5.00 root │ ├─TopN_34(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 @@ -239,6 +249,40 @@ TopN_19 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, co │ └─TableRowIDScan_71(Probe) 10000.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo └─TopN_68(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 └─TableRowIDScan_67 0.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo +======= + ├─TopN_27 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─IndexLookUp_37 5.00 root + │ ├─TopN_38(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_40 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_34(Build) 10000.00 cop[tikv] table:tp1, partition:p0, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_39(Probe) 10000.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo + │ └─TopN_36(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_35 0.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo + ├─TopN_54 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─IndexLookUp_64 5.00 root + │ ├─TopN_65(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_67 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_61(Build) 10000.00 cop[tikv] table:tp1, partition:p1, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_66(Probe) 10000.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo + │ └─TopN_63(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_62 0.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo + ├─TopN_81 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─IndexLookUp_91 5.00 root + │ ├─TopN_92(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_94 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_88(Build) 10000.00 cop[tikv] table:tp1, partition:p2, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_93(Probe) 10000.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo + │ └─TopN_90(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_89 0.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo + └─TopN_108 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + └─IndexLookUp_118 5.00 root + ├─TopN_119(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─LocalIndexLookUp_121 10000.00 cop[tikv] index handle offsets:[1] + │ ├─IndexFullScan_115(Build) 10000.00 cop[tikv] table:tp1, partition:p3, index:b(b) keep order:false, stats:pseudo + │ └─TableRowIDScan_120(Probe) 10000.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo + └─TopN_117(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + └─TableRowIDScan_116 0.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 order by c limit 5; a b c 1 10 10 diff --git a/tests/integrationtest/r/executor/issues.result b/tests/integrationtest/r/executor/issues.result index 00f29c396331d..842c1ee7112b7 100644 --- a/tests/integrationtest/r/executor/issues.result +++ b/tests/integrationtest/r/executor/issues.result @@ -931,6 +931,7 @@ Limit_8 64.00 root NULL NULL offset:0, count:100000 └─TableFullScan_10 256.00 cop[tikv] table:t NULL keep order:false explain analyze select * from t order by id limit 100; # expected distsql concurrency 1 id estRows actRows task access object execution info operator info memory disk +<<<<<<< HEAD Limit_10 100.00 root NULL NULL offset:0, count:100 └─TableReader_17 100.00 root NULL max_distsql_concurrency: 1 NULL └─Limit_16 100.00 cop[tikv] NULL NULL offset:0, count:100 @@ -948,6 +949,25 @@ Limit_11 1.00 root NULL NULL offset:0, count:100 └─Limit_19 1.00 cop[tikv] NULL NULL offset:0, count:100 └─Selection_18 1.00 cop[tikv] NULL NULL eq(executor__issues.t.c, "abd") └─TableFullScan_17 256.00 cop[tikv] table:t NULL keep order:true +======= +Limit_12 100.00 root NULL NULL offset:0, count:100 +└─TableReader_22 100.00 root NULL max_distsql_concurrency: 1 NULL + └─Limit_21 100.00 cop[tikv] NULL NULL offset:0, count:100 + └─TableFullScan_20 101.56 cop[tikv] table:t NULL keep order:true +explain analyze select * from t order by id limit 100000; +id estRows actRows task access object execution info operator info memory disk +Limit_12 256.00 root NULL NULL offset:0, count:100000 +└─TableReader_22 256.00 root NULL max_distsql_concurrency: 15 NULL + └─Limit_21 256.00 cop[tikv] NULL NULL offset:0, count:100000 + └─TableFullScan_20 256.00 cop[tikv] table:t NULL keep order:true +explain analyze select * from t where c = 'abd' order by id limit 100; +id estRows actRows task access object execution info operator info memory disk +Limit_13 1.00 root NULL NULL offset:0, count:100 +└─TableReader_26 1.00 root NULL max_distsql_concurrency: 15 NULL + └─Limit_25 1.00 cop[tikv] NULL NULL offset:0, count:100 + └─Selection_24 1.00 cop[tikv] NULL NULL eq(executor__issues.t.c, "abd") + └─TableFullScan_23 256.00 cop[tikv] table:t NULL keep order:true +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) select @@tidb_partition_prune_mode; @@tidb_partition_prune_mode dynamic @@ -983,6 +1003,7 @@ Limit_8 1.00 root NULL NULL offset:0, count:100000 └─TableFullScan_10 256.00 cop[tikv] table:pt NULL keep order:false explain analyze select * from pt order by id limit 100; # expected distsql concurrency 7, but currently get 1, see issue #55190 id estRows actRows task access object execution info operator info memory disk +<<<<<<< HEAD Limit_10 100.00 root NULL NULL offset:0, count:100 └─TableReader_17 100.00 root partition:all max_distsql_concurrency: 1 NULL └─Limit_16 100.00 cop[tikv] NULL NULL offset:0, count:100 @@ -1001,6 +1022,26 @@ Limit_11 1.00 root NULL NULL offset:0, count:100 └─Selection_18 1.00 cop[tikv] NULL NULL eq(executor__issues.pt.val, 126) └─TableFullScan_17 256.00 cop[tikv] table:pt NULL keep order:true explain analyze select /*+ set_var(tidb_distsql_scan_concurrency=5)*/ * from t order by id; # expected distsql concurrency 5 +======= +Limit_12 100.00 root NULL NULL offset:0, count:100 +└─TableReader_22 100.00 root partition:all max_distsql_concurrency: 1 NULL + └─Limit_21 100.00 cop[tikv] NULL NULL offset:0, count:100 + └─TableFullScan_20 101.56 cop[tikv] table:pt NULL keep order:true +explain analyze select * from pt order by id limit 100000; +id estRows actRows task access object execution info operator info memory disk +Limit_12 256.00 root NULL NULL offset:0, count:100000 +└─TableReader_22 256.00 root partition:all max_distsql_concurrency: 15 NULL + └─Limit_21 256.00 cop[tikv] NULL NULL offset:0, count:100000 + └─TableFullScan_20 256.00 cop[tikv] table:pt NULL keep order:true +explain analyze select * from pt where val = 126 order by id limit 100; +id estRows actRows task access object execution info operator info memory disk +Limit_13 1.00 root NULL NULL offset:0, count:100 +└─TableReader_26 1.00 root partition:all max_distsql_concurrency: 15 NULL + └─Limit_25 1.00 cop[tikv] NULL NULL offset:0, count:100 + └─Selection_24 1.00 cop[tikv] NULL NULL eq(executor__issues.pt.val, 126) + └─TableFullScan_23 256.00 cop[tikv] table:pt NULL keep order:true +explain analyze select /*+ set_var(tidb_distsql_scan_concurrency=5)*/ * from t order by id; +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) id estRows actRows task access object execution info operator info memory disk TableReader_11 256.00 root NULL max_distsql_concurrency: 5 NULL └─TableFullScan_10 256.00 cop[tikv] table:t NULL keep order:true diff --git a/tests/integrationtest/r/index_merge.result b/tests/integrationtest/r/index_merge.result index 9975bd9d12d8f..e07dca9c4ef40 100644 --- a/tests/integrationtest/r/index_merge.result +++ b/tests/integrationtest/r/index_merge.result @@ -864,3 +864,109 @@ c1 c2 c3 8 8 8 9 9 9 10 10 10 +<<<<<<< HEAD +======= +///// IndexMerge with IN conditions and ORDER BY LIMIT (merge sort for partial paths) +drop table if exists t_im_in; +create table t_im_in ( +id int not null, +a int default null, +b int default null, +c int default null, +padding varchar(100) default null, +primary key (id) clustered, +key idx_a_c (a, c), +key idx_b_c (b, c) +); +insert into t_im_in values +(1, 1, 1, 10, 'row1'), (2, 1, 2, 20, 'row2'), (3, 2, 1, 30, 'row3'), +(4, 2, 2, 40, 'row4'), (5, 1, 3, 50, 'row5'), (6, 3, 1, 60, 'row6'), +(7, 3, 2, 70, 'row7'), (8, 1, 1, 5, 'row8'), (9, 2, 3, 15, 'row9'), +(10, 3, 3, 25, 'row10'); +analyze table t_im_in; +# Case 1: a=1 keeps order directly, b IN (1,2) needs merge sort +explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; +id estRows task access object operator info +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 2.44 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true +├─Limit_34(Build) 4.27 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; +id a b c padding +8 1 1 5 row8 +1 1 1 10 row1 +2 1 2 20 row2 +3 2 1 30 row3 +4 2 2 40 row4 +# Case 2: Both partial paths need merge sort +explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a in (1, 2) or b in (1, 2) order by c limit 5; +id estRows task access object operator info +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 3.85 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 3.85 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], [2,2], keep order:true +├─Limit_34(Build) 3.85 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 3.85 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +select /*+ use_index_merge(t_im_in) */ * from t_im_in where a in (1, 2) or b in (1, 2) order by c limit 5; +id a b c padding +8 1 1 5 row8 +1 1 1 10 row1 +9 2 3 15 row9 +2 1 2 20 row2 +3 2 1 30 row3 +# Case 3: DESC ordering with IN condition +explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c desc limit 5; +id estRows task access object operator info +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 2.44 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true, desc +├─Limit_34(Build) 4.27 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true, desc +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c desc limit 5; +id a b c padding +7 3 2 70 row7 +6 3 1 60 row6 +5 1 3 50 row5 +4 2 2 40 row4 +3 2 1 30 row3 +# Case 4: UnionScan - verify correctness with uncommitted data +begin; +insert into t_im_in values (11, 1, 1, 1, 'uncommitted'); +explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; +id estRows task access object operator info +Limit_15 5.00 root offset:0, count:5 +└─UnionScan_21 5.00 root or(eq(index_merge.t_im_in.a, 1), in(index_merge.t_im_in.b, 1, 2)) + └─IndexMerge_25 5.00 root type: union + ├─IndexRangeScan_22(Build) 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true + ├─IndexRangeScan_23(Build) 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true + └─TableRowIDScan_24(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; +id a b c padding +11 1 1 1 uncommitted +8 1 1 5 row8 +1 1 1 10 row1 +2 1 2 20 row2 +3 2 1 30 row3 +rollback; +# Case 5: PREPARE/EXECUTE with different parameter values +prepare stmt from 'select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = ? or b in (1, 2) order by c limit 5'; +set @a = 1; +execute stmt using @a; +id a b c padding +8 1 1 5 row8 +1 1 1 10 row1 +2 1 2 20 row2 +3 2 1 30 row3 +4 2 2 40 row4 +set @a = 3; +execute stmt using @a; +id a b c padding +8 1 1 5 row8 +1 1 1 10 row1 +2 1 2 20 row2 +10 3 3 25 row10 +3 2 1 30 row3 +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) diff --git a/tests/integrationtest/r/planner/core/indexmerge_path.result b/tests/integrationtest/r/planner/core/indexmerge_path.result index 96641803fc32c..f9466205c0b0f 100644 --- a/tests/integrationtest/r/planner/core/indexmerge_path.result +++ b/tests/integrationtest/r/planner/core/indexmerge_path.result @@ -1212,6 +1212,7 @@ EXPLAIN format = brief SELECT /*+ use_index_merge(t1, mvi4) */ * from t1 where c = 1 and json_contains(j, '[4,5]') and d not like '%test%'; +<<<<<<< HEAD id estRows task access object operator info IndexMerge 0.01 root type: intersection ├─Selection(Build) 0.00 cop[tikv] not(like(planner__core__indexmerge_path.t1.d, "%test%", 92)) @@ -1220,3 +1221,178 @@ IndexMerge 0.01 root type: intersection │ └─IndexRangeScan 10.00 cop[tikv] table:t1, index:mvi4(cast(`j` as unsigned array), d) range:[5,5], keep order:false, stats:pseudo └─Selection(Probe) 0.01 cop[tikv] eq(planner__core__indexmerge_path.t1.c, 1) └─TableRowIDScan 0.01 cop[tikv] table:t1 keep order:false, stats:pseudo +======= +id task access object operator info +IndexMerge root type: intersection +├─Selection(Build) cop[tikv] not(like(planner__core__indexmerge_path.t1.d, "%test%", 92)) +│ └─IndexRangeScan cop[tikv] table:t1, index:mvi4(cast(`j` as unsigned array), d) range:[4,4], keep order:false, stats:pseudo +├─Selection(Build) cop[tikv] not(like(planner__core__indexmerge_path.t1.d, "%test%", 92)) +│ └─IndexRangeScan cop[tikv] table:t1, index:mvi4(cast(`j` as unsigned array), d) range:[5,5], keep order:false, stats:pseudo +└─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t1.c, 1) + └─TableRowIDScan cop[tikv] table:t1 keep order:false, stats:pseudo +drop table if exists t; +create table t (a int, b int, c int, d int, +index iab(a,b), +index iac(a,c), +index iad(a,d)); +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a = 1 and (b = 2 or c = 3 or d = 4); +id task access object operator info +IndexMerge root type: union +├─IndexRangeScan(Build) cop[tikv] table:t, index:iab(a, b) range:[1 2,1 2], keep order:false, stats:pseudo +├─IndexRangeScan(Build) cop[tikv] table:t, index:iac(a, c) range:[1 3,1 3], keep order:false, stats:pseudo +├─IndexRangeScan(Build) cop[tikv] table:t, index:iad(a, d) range:[1 4,1 4], keep order:false, stats:pseudo +└─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(eq(planner__core__indexmerge_path.t.b, 2), or(eq(planner__core__indexmerge_path.t.c, 3), eq(planner__core__indexmerge_path.t.d, 4))) + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +drop table if exists t; +create table t(a int, b int, c int, d int, +index iab(b,a), +index iac(c,a,d), +index iad(a,d)); +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a = 1 and (b = 2 or (c = 3 and d = 4) or d = 5); +id task access object operator info +IndexMerge root type: union +├─IndexRangeScan(Build) cop[tikv] table:t, index:iab(b, a) range:[2 1,2 1], keep order:false, stats:pseudo +├─IndexRangeScan(Build) cop[tikv] table:t, index:iac(c, a, d) range:[3 1 4,3 1 4], keep order:false, stats:pseudo +├─IndexRangeScan(Build) cop[tikv] table:t, index:iad(a, d) range:[1 5,1 5], keep order:false, stats:pseudo +└─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t.a, 1), or(eq(planner__core__indexmerge_path.t.b, 2), or(and(eq(planner__core__indexmerge_path.t.c, 3), eq(planner__core__indexmerge_path.t.d, 4)), eq(planner__core__indexmerge_path.t.d, 5))) + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +drop table if exists t; +create table t (a int, b int, c int, j1 json, j2 json, +key mvi1((cast(j1 as unsigned array)), a), +key mvi2((cast(j1 as unsigned array)), b), +key mvi3((cast(j2 as unsigned array)), a), +key mvi4((cast(j2 as unsigned array)), b) ); +explain format='plan_tree' select * from t where 1 member of (j1) or 2 member of (j2) order by a; +id task access object operator info +Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 +└─IndexMerge root type: union + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a) range:[1,1], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi3(cast(`j2` as unsigned array), a) range:[2,2], keep order:true, stats:pseudo + └─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo +explain format='plan_tree' select * from t where 1 member of (j1) or 2 member of (j2) order by b; +id task access object operator info +Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 +└─IndexMerge root type: union + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi2(cast(`j1` as unsigned array), b) range:[1,1], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi4(cast(`j2` as unsigned array), b) range:[2,2], keep order:true, stats:pseudo + └─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo +explain format='plan_tree' select * from t where json_overlaps(j1, '[1,3,5]') or 2 member of (j2) order by a desc limit 10; +id task access object operator info +Limit root offset:0, count:10 +└─Selection root or(json_overlaps(planner__core__indexmerge_path.t.j1, cast("[1,3,5]", json BINARY)), json_memberof(cast(2, json BINARY), planner__core__indexmerge_path.t.j2)) + └─Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 + └─IndexMerge root type: union + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a) range:[1,1], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a) range:[3,3], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a) range:[5,5], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi3(cast(`j2` as unsigned array), a) range:[2,2], keep order:true, desc, stats:pseudo + └─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo +explain format='plan_tree' select * from t where 1 member of (j1) or json_contains(j2, '[5]') order by b limit 10000; +id task access object operator info +Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 +└─IndexMerge root type: union, limit embedded(offset:0, count:10000) + ├─Limit(Build) cop[tikv] offset:0, count:10000 + │ └─IndexRangeScan cop[tikv] table:t, index:mvi2(cast(`j1` as unsigned array), b) range:[1,1], keep order:true, stats:pseudo + ├─Limit(Build) cop[tikv] offset:0, count:10000 + │ └─IndexRangeScan cop[tikv] table:t, index:mvi4(cast(`j2` as unsigned array), b) range:[5,5], keep order:true, stats:pseudo + └─TableRowIDScan(Probe) cop[tikv] table:t keep order:false, stats:pseudo +INSERT INTO t VALUES (2, 2, 2, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (5, 1, 5, '[]', '[2]'); +INSERT INTO t VALUES (10, 10, 10, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (1, 2, 3, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (4, 5, 6, '[7,8,5]', '[10,11,2]'); +INSERT INTO t VALUES (7, 8, 9, '[13,14,15]', '[16,17,18]'); +INSERT INTO t VALUES (10, 11, 12, '[19,20,21]', '[22,23,24]'); +INSERT INTO t VALUES (13, 14, 15, '[25,1,27]', '[2,29,30]'); +INSERT INTO t VALUES (16, 17, 18, '[31,32,33]', '[34,35,36]'); +INSERT INTO t VALUES (19, 20, 21, '[37,38,39]', '[40,2,42]'); +INSERT INTO t VALUES (22, 23, 24, '[5,44,45]', '[46,47,48]'); +INSERT INTO t VALUES (25, 26, 27, '[49,50,3]', '[52,53,54]'); +INSERT INTO t VALUES (28, 29, 30, '[5,56,57]', '[58,2,60]'); +INSERT INTO t VALUES (31, 32, 33, '[61,3,63]', '[64,65,66]'); +INSERT INTO t VALUES (34, 35, 36, '[67,68,69]', '[70,71,72]'); +INSERT INTO t VALUES (37, 38, 39, '[73,74,2]', '[76,2,2]'); +select /*+ use_index_merge(t) */ * from t where json_overlaps(j1, '[1,3,5]') or 2 member of (j2) order by a limit 2; +a b c j1 j2 +1 2 3 [1, 2, 3] [4, 5, 6] +2 2 2 [1, 2, 3] [4, 5, 6] +select /*+ use_index_merge(t) */ * from t where 1 member of (j1) or 2 member of (j2) order by b desc; +a b c j1 j2 +37 38 39 [73, 74, 2] [76, 2, 2] +28 29 30 [5, 56, 57] [58, 2, 60] +19 20 21 [37, 38, 39] [40, 2, 42] +13 14 15 [25, 1, 27] [2, 29, 30] +10 10 10 [1, 2, 3] [4, 5, 6] +4 5 6 [7, 8, 5] [10, 11, 2] +2 2 2 [1, 2, 3] [4, 5, 6] +1 2 3 [1, 2, 3] [4, 5, 6] +5 1 5 [] [2] +drop table if exists t; +create table t (a int, b int, c int, d int, e int, j1 json, j2 json, +key mvi1((cast(j1 as unsigned array)), a, e), +key idx1(a, b, e), +key mvi2(b, (cast(j2 as unsigned array)), e), +key idx2(d, e, c) ); +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where +a = 5 and +(1 member of (j1) or b = 3 or b = 4 and json_overlaps(j2, '[3,4,5]') or d = 10) +order by e; +id task access object operator info +Selection root or(or(json_memberof(cast(1, json BINARY), planner__core__indexmerge_path.t.j1), eq(planner__core__indexmerge_path.t.b, 3)), or(and(eq(planner__core__indexmerge_path.t.b, 4), json_overlaps(planner__core__indexmerge_path.t.j2, cast("[3,4,5]", json BINARY))), eq(planner__core__indexmerge_path.t.d, 10))) +└─Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.d, planner__core__indexmerge_path.t.e, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 + └─IndexMerge root type: union + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a, e) range:[1 5,1 5], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx1(a, b, e) range:[5 3,5 3], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx1(a, b, e) range:[5 4,5 4], keep order:true, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx2(d, e, c) range:[10,10], keep order:true, stats:pseudo + └─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t.a, 5) + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where +a = 5 and +(1 member of (j1) or b = 3 or b = 4 and json_overlaps(j2, '[3,4,5]') or d = 10) +order by e desc limit 10; +id task access object operator info +Limit root offset:0, count:10 +└─Selection root or(or(json_memberof(cast(1, json BINARY), planner__core__indexmerge_path.t.j1), eq(planner__core__indexmerge_path.t.b, 3)), or(and(eq(planner__core__indexmerge_path.t.b, 4), json_overlaps(planner__core__indexmerge_path.t.j2, cast("[3,4,5]", json BINARY))), eq(planner__core__indexmerge_path.t.d, 10))) + └─Projection root planner__core__indexmerge_path.t.a, planner__core__indexmerge_path.t.b, planner__core__indexmerge_path.t.c, planner__core__indexmerge_path.t.d, planner__core__indexmerge_path.t.e, planner__core__indexmerge_path.t.j1, planner__core__indexmerge_path.t.j2 + └─IndexMerge root type: union + ├─IndexRangeScan(Build) cop[tikv] table:t, index:mvi1(cast(`j1` as unsigned array), a, e) range:[1 5,1 5], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx1(a, b, e) range:[5 3,5 3], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx1(a, b, e) range:[5 4,5 4], keep order:true, desc, stats:pseudo + ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx2(d, e, c) range:[10,10], keep order:true, desc, stats:pseudo + └─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t.a, 5) + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +drop table if exists t; +create table t(a int, b int, c int, index idx_ac(a, c), index idx_bc(b, c)); +insert into t values(1, 1, 100), (1, 2, 50), (1, 3, 150), (6, 6, 10), (7, 7, 20), (8, 8, 30), (9, 9, 5); +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +id task access object operator info +TopN root planner__core__indexmerge_path.t.c, offset:0, count:3 +└─IndexMerge root type: union + ├─Limit(Build) cop[tikv] offset:0, count:3 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_ac(a, c) range:[1,1], keep order:true, stats:pseudo + ├─TopN(Build) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:3 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_bc(b, c) range:(5,+inf], keep order:false, stats:pseudo + └─TopN(Probe) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:3 + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +a b c +9 9 5 +6 6 10 +7 7 20 +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +id task access object operator info +TopN root planner__core__indexmerge_path.t.c, offset:1, count:3 +└─IndexMerge root type: union + ├─Limit(Build) cop[tikv] offset:0, count:4 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_ac(a, c) range:[1,1], keep order:true, stats:pseudo + ├─TopN(Build) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:4 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_bc(b, c) range:(5,+inf], keep order:false, stats:pseudo + └─TopN(Probe) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:4 + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +a b c +6 6 10 +7 7 20 +8 8 30 +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) diff --git a/tests/integrationtest/r/planner/core/plan_cost_ver2.result b/tests/integrationtest/r/planner/core/plan_cost_ver2.result index 6255384544a4c..8de3c2902e0e5 100644 --- a/tests/integrationtest/r/planner/core/plan_cost_ver2.result +++ b/tests/integrationtest/r/planner/core/plan_cost_ver2.result @@ -5,6 +5,7 @@ analyze table t all columns; explain format='verbose' select /*+ limit_to_cop() */ * from t where a=1 order by a limit 1; id estRows estCost task access object operator info TopN_8 1.00 24.53 root planner__core__plan_cost_ver2.t.a, offset:0, count:1 +<<<<<<< HEAD └─TableReader_16 1.00 21.33 root data:TopN_15 └─TopN_15 1.00 256.60 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1 └─Selection_14 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) @@ -16,6 +17,19 @@ TopN_8 1.00 24.53 root planner__core__plan_cost_ver2.t.a, offset:0, count:10000 └─TopN_15 1.00 256.60 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 └─Selection_14 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) └─TableFullScan_13 1.00 203.50 cop[tikv] table:t keep order:false +======= +└─TableReader_19 1.00 21.33 root data:TopN_18 + └─TopN_18 1.00 256.60 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1 + └─Selection_17 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) + └─TableFullScan_16 1.00 203.50 cop[tikv] table:t keep order:false +explain format='verbose' select /*+ limit_to_cop() */ * from t where a=1 order by a limit 1000000000; +id estRows estCost task access object operator info +TopN_8 1.00 716.08 root planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 +└─TableReader_19 1.00 64.55 root data:TopN_18 + └─TopN_18 1.00 904.93 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 + └─Selection_17 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) + └─TableFullScan_16 1.00 203.50 cop[tikv] table:t keep order:false +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772)) drop table if exists t; create table t (a int primary key, b int, c int, key(b)); insert into t values (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8), (9, 9, 9), (10, 10, 10), (11, 11, 11), (12, 12, 12), (13, 13, 13), (14, 14, 14), (15, 15, 15), (16, 16, 16), (17, 17, 17), (18, 18, 18), (19, 19, 19), (20, 20, 20), (21, 21, 21), (22, 22, 22), (23, 23, 23), (24, 24, 24), (25, 25, 25), (26, 26, 26), (27, 27, 27), (28, 28, 28), (29, 29, 29), (30, 30, 30), (31, 31, 31), (32, 32, 32), (33, 33, 33), (34, 34, 34), (35, 35, 35), (36, 36, 36), (37, 37, 37), (38, 38, 38), (39, 39, 39), (40, 40, 40), (41, 41, 41), (42, 42, 42), (43, 43, 43), (44, 44, 44), (45, 45, 45), (46, 46, 46), (47, 47, 47), (48, 48, 48), (49, 49, 49), (50, 50, 50), (51, 51, 51), (52, 52, 52), (53, 53, 53), (54, 54, 54), (55, 55, 55), (56, 56, 56), (57, 57, 57), (58, 58, 58), (59, 59, 59), (60, 60, 60), (61, 61, 61), (62, 62, 62), (63, 63, 63), (64, 64, 64), (65, 65, 65), (66, 66, 66), (67, 67, 67), (68, 68, 68), (69, 69, 69), (70, 70, 70), (71, 71, 71), (72, 72, 72), (73, 73, 73), (74, 74, 74), (75, 75, 75), (76, 76, 76), (77, 77, 77), (78, 78, 78), (79, 79, 79), (80, 80, 80), (81, 81, 81), (82, 82, 82), (83, 83, 83), (84, 84, 84), (85, 85, 85), (86, 86, 86), (87, 87, 87), (88, 88, 88), (89, 89, 89), (90, 90, 90), (91, 91, 91), (92, 92, 92), (93, 93, 93), (94, 94, 94), (95, 95, 95), (96, 96, 96), (97, 97, 97), (98, 98, 98), (99, 99, 99); diff --git a/tests/integrationtest/t/planner/core/indexmerge_path.test b/tests/integrationtest/t/planner/core/indexmerge_path.test index a7e73e60c0be0..7fc8eb1354e22 100644 --- a/tests/integrationtest/t/planner/core/indexmerge_path.test +++ b/tests/integrationtest/t/planner/core/indexmerge_path.test @@ -517,3 +517,83 @@ EXPLAIN format = brief SELECT /*+ use_index_merge(t1, mvi4) */ * from t1 where c = 1 and json_contains(j, '[4,5]') and d not like '%test%'; +<<<<<<< HEAD +======= + +# TestIssue58361 + +# Test non-MV index OR IndexMerge can collect usable filters (only support eq now) from the top level AND filters +drop table if exists t; +create table t (a int, b int, c int, d int, +index iab(a,b), +index iac(a,c), +index iad(a,d)); +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a = 1 and (b = 2 or c = 3 or d = 4); + +drop table if exists t; +create table t(a int, b int, c int, d int, +index iab(b,a), +index iac(c,a,d), +index iad(a,d)); +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a = 1 and (b = 2 or (c = 3 and d = 4) or d = 5); + +# Test MV index OR IndexMerge can satisfy Sort property when there are multiple indexes to choose from +drop table if exists t; +create table t (a int, b int, c int, j1 json, j2 json, +key mvi1((cast(j1 as unsigned array)), a), +key mvi2((cast(j1 as unsigned array)), b), +key mvi3((cast(j2 as unsigned array)), a), +key mvi4((cast(j2 as unsigned array)), b) ); +explain format='plan_tree' select * from t where 1 member of (j1) or 2 member of (j2) order by a; +explain format='plan_tree' select * from t where 1 member of (j1) or 2 member of (j2) order by b; +explain format='plan_tree' select * from t where json_overlaps(j1, '[1,3,5]') or 2 member of (j2) order by a desc limit 10; +explain format='plan_tree' select * from t where 1 member of (j1) or json_contains(j2, '[5]') order by b limit 10000; + +INSERT INTO t VALUES (2, 2, 2, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (5, 1, 5, '[]', '[2]'); +INSERT INTO t VALUES (10, 10, 10, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (1, 2, 3, '[1,2,3]', '[4,5,6]'); +INSERT INTO t VALUES (4, 5, 6, '[7,8,5]', '[10,11,2]'); +INSERT INTO t VALUES (7, 8, 9, '[13,14,15]', '[16,17,18]'); +INSERT INTO t VALUES (10, 11, 12, '[19,20,21]', '[22,23,24]'); +INSERT INTO t VALUES (13, 14, 15, '[25,1,27]', '[2,29,30]'); +INSERT INTO t VALUES (16, 17, 18, '[31,32,33]', '[34,35,36]'); +INSERT INTO t VALUES (19, 20, 21, '[37,38,39]', '[40,2,42]'); +INSERT INTO t VALUES (22, 23, 24, '[5,44,45]', '[46,47,48]'); +INSERT INTO t VALUES (25, 26, 27, '[49,50,3]', '[52,53,54]'); +INSERT INTO t VALUES (28, 29, 30, '[5,56,57]', '[58,2,60]'); +INSERT INTO t VALUES (31, 32, 33, '[61,3,63]', '[64,65,66]'); +INSERT INTO t VALUES (34, 35, 36, '[67,68,69]', '[70,71,72]'); +INSERT INTO t VALUES (37, 38, 39, '[73,74,2]', '[76,2,2]'); + +select /*+ use_index_merge(t) */ * from t where json_overlaps(j1, '[1,3,5]') or 2 member of (j2) order by a limit 2; +select /*+ use_index_merge(t) */ * from t where 1 member of (j1) or 2 member of (j2) order by b desc; + +drop table if exists t; +create table t (a int, b int, c int, d int, e int, j1 json, j2 json, +key mvi1((cast(j1 as unsigned array)), a, e), +key idx1(a, b, e), +key mvi2(b, (cast(j2 as unsigned array)), e), +key idx2(d, e, c) ); + +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where +a = 5 and +(1 member of (j1) or b = 3 or b = 4 and json_overlaps(j2, '[3,4,5]') or d = 10) +order by e; + +explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where +a = 5 and +(1 member of (j1) or b = 3 or b = 4 and json_overlaps(j2, '[3,4,5]') or d = 10) +order by e desc limit 10; + +# TestIndexMergeSortItemsHints +# When one partial path can keep order (a=1 on idx_ac) and another cannot (b>5 on idx_bc), +# Limit should be pushed to the ordered partial path and TopN to the unordered one. +drop table if exists t; +create table t(a int, b int, c int, index idx_ac(a, c), index idx_bc(b, c)); +insert into t values(1, 1, 100), (1, 2, 50), (1, 3, 150), (6, 6, 10), (7, 7, 20), (8, 8, 30), (9, 9, 5); +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +>>>>>>> 2310c3d99f3 (planner: support pushing Limit and TopN to individual partial paths of IndexMerge (#68772))