diff --git a/pkg/sql/opt/exec/execbuilder/testdata/distsql_single_flow b/pkg/sql/opt/exec/execbuilder/testdata/distsql_single_flow index 943cc94adc6b..07bbd7945ea5 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/distsql_single_flow +++ b/pkg/sql/opt/exec/execbuilder/testdata/distsql_single_flow @@ -1,16 +1,18 @@ # LogicTest: 5node statement ok -CREATE TABLE t (a INT PRIMARY KEY, b INT, c INT) +CREATE TABLE t1 (a INT PRIMARY KEY, b INT, c INT); +CREATE TABLE t2 (a INT PRIMARY KEY, b INT, c INT); # Move the single range to a remote node. statement ok -ALTER TABLE t EXPERIMENTAL_RELOCATE VALUES (ARRAY[2], 2); +ALTER TABLE t1 EXPERIMENTAL_RELOCATE VALUES (ARRAY[2], 2); +ALTER TABLE t2 EXPERIMENTAL_RELOCATE VALUES (ARRAY[2], 2); # There are no stats on the table, so the single flow should stay on the remote # node. query T -EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b +EXPLAIN (VEC) SELECT * FROM t1, t2 WHERE t1.a = t2.b ---- │ ├ Node 1 @@ -22,7 +24,7 @@ EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b └ *colfetcher.ColBatchScan query T -EXPLAIN (DISTSQL) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b +EXPLAIN (DISTSQL) SELECT * FROM t1, t2 WHERE t1.a = t2.b ---- distribution: full vectorized: true @@ -33,20 +35,42 @@ vectorized: true │ ├── • scan │ missing stats -│ table: t@t_pkey +│ table: t1@t1_pkey │ spans: FULL SCAN │ └── • scan missing stats - table: t@t_pkey + table: t2@t2_pkey spans: FULL SCAN · -Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJysklFr2zAQx9_3KcQ9JUNpLDnbg6Dg0njUI026OLDBKEWxL4mpY3nSma6EfPdhp10T06TbmB6MdTr97v8_3QbcjxwUhN9uRhfRmHWGUTyLv4y6LA5H4eWMvWefppNrRuwiZiT4049kX6_CachInGl2zkiezYFDYVIc6zU6UN9BAAcJtxxKaxJ0ztg6vGmSovQnKI9DVpQV1eFbDomxCGoDlFGOoGBseqbs-8AhRdJZ3qRtOZiKXi450ksENdirEg1B-Vu-V0icLjTT8xynqFO0fe-gHFBAd-U9PgKHS5NX68Ippjmbc5YAh7jUdaAHx2SJlizvX2WJ_ypLtmSJo7Je1FSFsSlaTNvv8XbKK96utFt9NlmBti8PreW4oE4guuc2W66oE8gucJhUpFggeCB54PNgwIMPPPh41J_f8icP_L0xdlN0pSkc_tHcea1KPVG7xXSJu-45U9kEb6xJmtzddtKAmkCKjnang90mKp6PHFnU699Ts08SJ0n-AUmcJMm_IMl9kmiT_JMk77g7WXdskZuHuywFBd7T6r3yeV5QX9BLVz9bvDIPDXb2WNZNX-jcIYdrfY9DJLTrrMgcZQkoshVut-9-BQAA__9iPZLx +Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJykkt9v2jAQx9_3V1j3VKajxA7bg6VKmQpTmSh0gLRJU1WZ5ICoIc7si7oK8b9PCesKUaH74QdLPp8_9_2ebwP-ewYa-l9vhh8GI3HWG0xn08_Dlpj2h_3LmXgrPk7G14IlClbiy1V_0hcsz424EKzO54CQ24RGZk0e9DeQgKDgFqFwNibvravCmzppkPwAHSCkeVFyFb5FiK0j0BvglDMCDSPbtkUnBISE2KRZnbZFsCU_P_JslgS6u1dl0AMdbnGvkDxdaGbmGU3IJOQ6wUE5YBmxvCvu6REQLm1WrnOvhUExRxEDwrQwVaANx4TJhrDgX4XJhjAVsfofYaohTB4V9qynzK1LyFHS_JPXU15wd2X86pNNc3IddWguowWfRbJ14dLlis8i1QKEcclaRBIjhVGIURejdxi9P-ovbPhTB_5eGb0J-cLmnv5o9oJGpbas3FKypF33vC1dTDfOxnXu7jiuQXUgIc-72-7uMMifrjw7Muvfc7NPkidJ4QFJniSpvyCpfZJsksKTpOC4O1V1bJHZh7s0AQ3Br9V-YXtaUD0wS19923RlH2rs7LGomr4wmSeEa3NPPWJy6zRPPacxaHYlbbdvfgYAAP__MJ2RJw== # Inject stats so that column 'b' has few unique values whereas column 'c' has # many unique values. statement ok -ALTER TABLE t INJECT STATISTICS '[ +ALTER TABLE t1 INJECT STATISTICS '[ + { + "columns": ["a"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 10000, + "distinct_count": 10000 + }, + { + "columns": ["b"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 10000, + "distinct_count": 3 + }, + { + "columns": ["c"], + "created_at": "2018-01-01 1:00:00.00000+00:00", + "row_count": 10000, + "distinct_count": 100 + } +]' + +statement ok +ALTER TABLE t2 INJECT STATISTICS '[ { "columns": ["a"], "created_at": "2018-01-01 1:00:00.00000+00:00", @@ -69,7 +93,7 @@ ALTER TABLE t INJECT STATISTICS '[ # Now check that the single flow with a join is moved to the gateway. query T -EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b +EXPLAIN (VEC) SELECT * FROM t1, t2 WHERE t1.a = t2.b ---- │ └ Node 1 @@ -78,7 +102,7 @@ EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b └ *colfetcher.ColBatchScan query T -EXPLAIN (DISTSQL) SELECT * FROM t AS t1, t AS t2 WHERE t1.a = t2.b +EXPLAIN (DISTSQL) SELECT * FROM t1, t2 WHERE t1.a = t2.b ---- distribution: local vectorized: true @@ -90,20 +114,20 @@ vectorized: true │ ├── • scan │ estimated row count: 10,000 (100% of the table; stats collected ago) -│ table: t@t_pkey +│ table: t1@t1_pkey │ spans: FULL SCAN │ └── • scan estimated row count: 10,000 (100% of the table; stats collected ago) - table: t@t_pkey + table: t2@t2_pkey spans: FULL SCAN · -Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJysktFr2zAQxt_3V4h7asaltZRtD4KCS5NRj7Tp4sAGwxTFviSmjuVJZ7oS8r8PO92amHSjY3ow1un0u-_70Ab89wI0jL7eji-iG3EyjOJZ_HncE_FoPLqcibfi43RyLVhcxIIlPv0o8eVqNB0JlqdGnAtWp3NAKG1GN2ZNHvQ3kJAgVM6m5L11TWnTNkTZD9ABQl5WNTflBCG1jkBvgHMuCDTMzLygKZmM3FkACBmxyYsWyyHfVff0CAiXtqjXpdfCoJijSAEhrkxT6EOyRbA1P4_wbJYEWu5pioaggy3-myz5X2Wpjiz5oqxnNXVpXUaOsgMlSXPzby1HvF0Zv_pk85LcmTq0VtCCT0LZO3f5csUnoeoBwqRmLUKJocJwgOE7DN9j-OFFf4OOP_Wa2KfkK1t66vo8OinoTOrLxi1lS9ql523tUrp1Nm17d9tJC2oLGXnenardJirbI9lMcGTWv1_NPkm-gqT2SbJLUn8kDQ5IwaGmBGFR2Ie7PAMNwdPqH_n8WtBcMEvfhB2v7EOLnT1WTVQLU3hCuDb3NCQmt87L3HOegmZX03b75mcAAAD__wrGV6A= +Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJykklFr2zAQx9_3KcQ9JePSWsq2B0FBo8loRtp0SWCDYYpiXxJTx_KkM10J-e7DzrYmJt3opgeDTtLvfv_DWwjfctAw_HI7fj-6EZ3BaDaffRp3xWw4Hl7OxWvxYTq5FixRsBKfr4bToWB5ZsWFYHW2AITCpXRjNxRAfwUJMULpXUIhOF-Xts2FUfoddISQFWXFdTlGSJwn0FvgjHMCDXO7yGlKNiV_HgFCSmyzvMGyNCzvynt6BIRLl1ebImhhUSxQJIAwK21d6EG8Q3AVPzUJbFcEWh5YjQagox3-m5hsiSnD6n_EVEtMPiv25FMVzqfkKT1yieuXf7tyIt2VDeuPLivIn6vjcDktuWNk98JnqzV3jOoCwqRiLYxEo9D00bxB8xbNu2fz9Vv51EsGP6VQuiJQO-fJTlGrU0_WaSld0X56wVU-oVvvkubufjtpQE0hpcD7U7XfjIrmSNYdPNnN7__mkCRfQFKHJNkmqT-S-kek6NgpRljm7uEuS0FD9HP1Tnx-Lagf2FWohz1bu4cGO38s61EtbR4I4dre04CY_CYrssBZApp9Rbvdqx8BAAD__-o0VdY= # If we add a not very selective filter, the flow is still moved to the gateway. query T -EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.b = 1 AND t1.a = t2.a +EXPLAIN (VEC) SELECT * FROM t1, t2 WHERE t1.b = 1 AND t1.a = t2.a ---- │ └ Node 1 @@ -113,7 +137,7 @@ EXPLAIN (VEC) SELECT * FROM t AS t1, t AS t2 WHERE t1.b = 1 AND t1.a = t2.a └ *colfetcher.ColBatchScan query T -EXPLAIN (DISTSQL) SELECT * FROM t AS t1, t AS t2 WHERE t1.b = 1 AND t1.a = t2.a +EXPLAIN (DISTSQL) SELECT * FROM t1, t2 WHERE t1.b = 1 AND t1.a = t2.a ---- distribution: local vectorized: true @@ -126,7 +150,7 @@ vectorized: true │ ├── • scan │ estimated row count: 10,000 (100% of the table; stats collected ago) -│ table: t@t_pkey +│ table: t2@t2_pkey │ spans: FULL SCAN │ └── • filter @@ -135,14 +159,14 @@ vectorized: true │ └── • scan estimated row count: 10,000 (100% of the table; stats collected ago) - table: t@t_pkey + table: t1@t1_pkey spans: FULL SCAN · -Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJyskl9r2zAUxd_3KcR9ajalsWRnDEHAoUmZR_50cWCDEYpi36SmjuVJMl0J-e7DdrcmJsmWMT8Y6Ur6nXN0tQXzPQUBw693o34wIVeDIJyHn0ctEg5Hw5s5eUtuZ9MxsaQfEsvoy4CTLx-HsyG5sux6SXqEtUh_MqimkvSI5deyBRQyFeNEbtCA-AYMFhRyrSI0RumytK02BPEPEA6FJMsLW5YXFCKlEcQWbGJTBAFzuUxxhjJG3XGAQoxWJmmFtb69zx_xGSjcqLTYZEYQScmSkggohLksC21Y7Ciowr5KGCvXCILteQoGIJwd_Tdb7L_a4g1b7BJbt0lqUaPu8ENPdV0Qn5ctE0IEk_mHkxbchgV-0sKrcpEpHaPG-EB4UZ7805YjOcao1_hJJRnqjnsYJcWVvfLZu1ZPJ-uHeggUpoUVxPeo36X-e-oz6nPquycjeo2I7iW3PFFtlXe8ZtKjQt2GkHeJ0AxNrjKDf6XkNJTarLxZjNdYd8qoQkd4p1VU7a2n0wpUFWI0tl5160mQVUusVNAoN79f4z6JnSXxA5KzT3KaJH6BJ75P4k2Se5bknfbkNkneWVL3XLoFhVWqnu6TGAQ4L1_7yO_XB-UBuTblAwgf1FOFnT_nZftWMjVIYSwfcYAW9SbJEmOTCITVBe52b34GAAD__75k2MI= +Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJykkl9v2jAUxd_3Kaz7BJspsROmyRJSqkK1TPzpAGmTJlSZ5JJGDXFmO-oqxHefkmwrRMDG6ocovrZ_5xxfb8F8T0HA8Ovd6DqYkNYgmC_mn0dtMh-OhjcL8pbczqZjYhkllpMvH4ezIWlZdrUifcLa5HoyqKaS9InlV7INFDIV4URu0ID4BgyWFHKtQjRG6bK0rTYE0Q8QDoUkywtblpcUQqURxBZsYlMEAQu5SnGGMkLddYBChFYmaYW13Lf8Pn_EZ6Bwo9JikxlBJCUrSkKgMM9lWejAckdBFfZFxFgZIwi25yoYgHB29P-MsYYx5lv2GmO8YYxdYuw2SS1q1F1-6KquC-Lzsm1CiGCy-HDSgtuwwE9aeFEuMqUj1BgdCC_Lk3_bciTHGHWMn1SSoe66h1FSXNuWz961-zqJH-pfoDAtrCC-R_0e9d9Tn1GfU989GdFrRHQvueWJ6qi86zWTHhXqNYS8S4RmaHKVGfwnJaeh1GHlzWIUY90powod4p1WYbW3nk4rUFWI0Nh61a0nQVYtsVJBo9z8eY37JHaWxA9Izj7JaZL4BZ74Pok3Se5Zknfak9skeWdJvXPplhTWqXq6TyIQ4PwanSOf3wPKAzI25QOYP6inCrt4zsv2rWVqkMJYPuIALepNkiXGJiEIqwvc7d78DAAA___NVNb4 # However, if we add a selective filter, the flow is kept on the remote node. query T -EXPLAIN (VEC) SELECT * FROM t AS t1 INNER MERGE JOIN t AS t2 ON t1.a = t2.a WHERE t1.c = 1 +EXPLAIN (VEC) SELECT * FROM t1 INNER MERGE JOIN t2 ON t1.a = t2.a WHERE t1.c = 1 ---- │ ├ Node 1 @@ -155,7 +179,7 @@ EXPLAIN (VEC) SELECT * FROM t AS t1 INNER MERGE JOIN t AS t2 ON t1.a = t2.a WHER └ *colfetcher.ColBatchScan query T -EXPLAIN (DISTSQL) SELECT * FROM t AS t1 INNER MERGE JOIN t AS t2 ON t1.a = t2.a WHERE t1.c = 1 +EXPLAIN (DISTSQL) SELECT * FROM t1 INNER MERGE JOIN t2 ON t1.a = t2.a WHERE t1.c = 1 ---- distribution: full vectorized: true @@ -172,12 +196,12 @@ vectorized: true │ │ │ └── • scan │ estimated row count: 10,000 (100% of the table; stats collected ago) -│ table: t@t_pkey +│ table: t1@t1_pkey │ spans: FULL SCAN │ └── • scan estimated row count: 10,000 (100% of the table; stats collected ago) - table: t@t_pkey + table: t2@t2_pkey spans: FULL SCAN · -Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJysktFr2zAQxt_3V4h7ajelsWx3DEHBpXU3l8Tp7MAGoxTVvqSmjuVJMl0J-d-H7HZNTJMuY34I8afT777vfEvQP0vgEH6_Gp1GMTk4j9Jp-nV0SNJwFJ5NyXtykUzGxJDTlBhGojgOEzIOk88huZxE8dOBSyYxMexIkBNi3CNBvn0Jk9AqGTkhDChUMsdYLFAD_wFWcOGaQq1khlpLZeVlWxTlv4A7FIqqboyVrylkUiHwJZjClAgcYjmQ9dAHCjkaUZRt2YqCbMzLJW3EHIEfr3WJzoH7K7rWiO1uNBW3JSYoclRDZ6MdmMDc1Pf4CBTOZNksKs2JoOSWkgwopLWwwgC22WI9W84-ti6K0qBCNWSbnjqdk8CzU-ecR_H001YLbs8C-9fJuP91Ml7PlrvV1oubppIqR4V5fyXeLnkl2xjVHC9lUaEaepvZSpyZg4B9ODxRxfyu-wsUJo3hJGA0cGng0cCnwTENPm6N6PciehsR31j-BHUtK41_tf1Or9OA2cCYz7EboJaNyvBKyayt7V4nLagVctSmOz3uXqLq-UgbhWLxZ3fXSWwnyd2D5O4k-Rsktk5ifZK3B8ldJ7l9kr-T5GxP59nZz0r5cFPkwMF5egav_Dw_YC-IubYLkN7JhxY7fazt55uJUiOFsbjHczSoFkVVaFNkwI1qcLV69zsAAP__78Xc4A== +Diagram: https://cockroachdb.github.io/distsqlplan/decode.html#eJykk1Fr2zAUhd_3K8R9ajelsWRnDEHAo3E3l8TpnMAGoxTVvklNHcuTZLoS8t-H7XVNTJMumx9CfHT13XOupTWYHzkICL5djT-GETkZhbP57Mv4lMyCcXA-J2_JRTydEMtIGEVBTCZB_Ckgl9MwIpaTaUQsO5NkSCw_k-Tr5yAOaiUhQ8KAQqFSjOQKDYjvUAscrimUWiVojNK1vG6KwvQnCIdCVpSVreVrConSCGINNrM5goBI9VTZ94BCilZmeVO2oaAq-7zJWLlEEIOtLuEIhLehW43Y4UZzeZtjjDJF3Xd22oFlvmU35T0-AoVzlVerwggiKbmlJAEKs1LWQg_2GWMdY84xxi6y3KJG3We7rlpdEN-t5y6ECKP5h70WeMcC-9fZ8M5suG_5_8zG7Rjje409-6kKpVPUmHaPxeslL6SboF7ipcoK1H13N12OC3vis3enQ50t79q_QGFaWUF8Rn1OfZf6HvUH1H-_N6LXiejuRHzlAsRoSlUY_Ksb4HQ69VgdGNMltgM0qtIJXmmVNLXt67QBNUKKxrarg_YlLJ6WjNUoV39O7zaJHSTxI0j8IMnbIbFtEuuS3CNIfJvEuyTvIMnZn86tZ7_I1cNNloIA5_fTe-Hn6YF6g1ya-gDM7tRDg50_lvXnW8jcIIWJvMcRWtSrrMiMzRIQVle42bz5FQAA__89HtsW diff --git a/pkg/sql/opt/exec/execbuilder/testdata/join b/pkg/sql/opt/exec/execbuilder/testdata/join index 411d16ade524..193a5d5264a3 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/join +++ b/pkg/sql/opt/exec/execbuilder/testdata/join @@ -939,32 +939,30 @@ vectorized: true spans: FULL SCAN query T -EXPLAIN (VERBOSE) SELECT * FROM pkBAC AS l JOIN pkBAC AS r USING(a, b, c) +EXPLAIN (VERBOSE) SELECT * FROM pkBAC AS l JOIN pkBAC AS r USING(a, b) ---- distribution: local vectorized: true · • project -│ columns: (a, b, c, d, d) +│ columns: (a, b, c, d, c, d) │ └── • merge join (inner) │ columns: (a, b, c, d, a, b, c, d) - │ estimated row count: 1 (missing stats) - │ equality: (b, a, c) = (b, a, c) - │ left cols are key - │ right cols are key - │ merge ordering: +"(b=b)",+"(a=a)",+"(c=c)" + │ estimated row count: 100 (missing stats) + │ equality: (b, a) = (b, a) + │ merge ordering: +"(b=b)",+"(a=a)" │ ├── • scan │ columns: (a, b, c, d) - │ ordering: +b,+a,+c + │ ordering: +b,+a │ estimated row count: 1,000 (missing stats) │ table: pkbac@pkbac_pkey │ spans: FULL SCAN │ └── • scan columns: (a, b, c, d) - ordering: +b,+a,+c + ordering: +b,+a estimated row count: 1,000 (missing stats) table: pkbac@pkbac_pkey spans: FULL SCAN diff --git a/pkg/sql/opt/exec/execbuilder/testdata/sql_activity_stats_compaction b/pkg/sql/opt/exec/execbuilder/testdata/sql_activity_stats_compaction index d2e8f79dc57a..9e837c118008 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/sql_activity_stats_compaction +++ b/pkg/sql/opt/exec/execbuilder/testdata/sql_activity_stats_compaction @@ -267,33 +267,19 @@ vectorized: true └── • project │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) │ - └── • project - │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) + └── • lookup join (inner) + │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) + │ estimated row count: 0 + │ table: statement_statistics@primary + │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8,aggregated_ts,fingerprint_id,transaction_fingerprint_id,plan_hash,app_name,node_id) + │ equality cols are key │ - └── • lookup join (inner) - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) - │ estimated row count: 0 - │ table: statement_statistics@primary - │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8,aggregated_ts,fingerprint_id,transaction_fingerprint_id,plan_hash,app_name,node_id) - │ equality cols are key - │ - └── • render - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq: mod(fnv32(crdb_internal.datums_to_bytes(aggregated_ts, app_name, fingerprint_id, node_id, plan_hash, transaction_fingerprint_id)), 8) - │ render aggregated_ts: aggregated_ts - │ render fingerprint_id: fingerprint_id - │ render transaction_fingerprint_id: transaction_fingerprint_id - │ render plan_hash: plan_hash - │ render app_name: app_name - │ render node_id: node_id - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8: crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8 - │ - └── • scan - columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) - estimated row count: 1,024 (0.10% of the table; stats collected ago) - table: statement_statistics@primary - spans: /0-/0/2022-05-04T15:59:59.999999001Z - limit: 1024 + └── • scan + columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) + estimated row count: 1,024 (0.10% of the table; stats collected ago) + table: statement_statistics@primary + spans: /0-/0/2022-05-04T15:59:59.999999001Z + limit: 1024 statement ok ALTER TABLE system.transaction_statistics INJECT STATISTICS '[ @@ -481,31 +467,19 @@ vectorized: true └── • project │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, app_name, node_id) │ - └── • project - │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) + └── • lookup join (inner) + │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) + │ estimated row count: 0 + │ table: transaction_statistics@primary + │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8,aggregated_ts,fingerprint_id,app_name,node_id) + │ equality cols are key │ - └── • lookup join (inner) - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) - │ estimated row count: 0 - │ table: transaction_statistics@primary - │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8,aggregated_ts,fingerprint_id,app_name,node_id) - │ equality cols are key - │ - └── • render - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq: mod(fnv32(crdb_internal.datums_to_bytes(aggregated_ts, app_name, fingerprint_id, node_id)), 8) - │ render aggregated_ts: aggregated_ts - │ render fingerprint_id: fingerprint_id - │ render app_name: app_name - │ render node_id: node_id - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8: crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8 - │ - └── • scan - columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) - estimated row count: 1,024 (0.10% of the table; stats collected ago) - table: transaction_statistics@primary - spans: /0-/0/2022-05-04T15:59:59.999999001Z - limit: 1024 + └── • scan + columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) + estimated row count: 1,024 (0.10% of the table; stats collected ago) + table: transaction_statistics@primary + spans: /0-/0/2022-05-04T15:59:59.999999001Z + limit: 1024 query T EXPLAIN (VERBOSE) @@ -568,33 +542,19 @@ vectorized: true └── • project │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) │ - └── • project - │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) + └── • lookup join (inner) + │ columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) + │ estimated row count: 0 + │ table: statement_statistics@primary + │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8,aggregated_ts,fingerprint_id,transaction_fingerprint_id,plan_hash,app_name,node_id) + │ equality cols are key │ - └── • lookup join (inner) - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, statistics, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) - │ estimated row count: 0 - │ table: statement_statistics@primary - │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8,aggregated_ts,fingerprint_id,transaction_fingerprint_id,plan_hash,app_name,node_id) - │ equality cols are key - │ - └── • render - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq, aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8_eq: mod(fnv32(crdb_internal.datums_to_bytes(aggregated_ts, app_name, fingerprint_id, node_id, plan_hash, transaction_fingerprint_id)), 8) - │ render aggregated_ts: aggregated_ts - │ render fingerprint_id: fingerprint_id - │ render transaction_fingerprint_id: transaction_fingerprint_id - │ render plan_hash: plan_hash - │ render app_name: app_name - │ render node_id: node_id - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8: crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8 - │ - └── • scan - columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) - estimated row count: 1,024 (0.10% of the table; stats collected ago) - table: statement_statistics@primary - spans: /0/2022-05-04T14:00:00Z/"123"/"234"/"345"/"test"/1-/0/2022-05-04T15:59:59.999999001Z - limit: 1024 + └── • scan + columns: (aggregated_ts, fingerprint_id, transaction_fingerprint_id, plan_hash, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8) + estimated row count: 1,024 (0.10% of the table; stats collected ago) + table: statement_statistics@primary + spans: /0/2022-05-04T14:00:00Z/"123"/"234"/"345"/"test"/1-/0/2022-05-04T15:59:59.999999001Z + limit: 1024 query T EXPLAIN (VERBOSE) @@ -652,31 +612,19 @@ vectorized: true └── • project │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, app_name, node_id) │ - └── • project - │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) + └── • lookup join (inner) + │ columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) + │ estimated row count: 0 + │ table: transaction_statistics@primary + │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8,aggregated_ts,fingerprint_id,app_name,node_id) + │ equality cols are key │ - └── • lookup join (inner) - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8, execution_count, service_latency, cpu_sql_nanos, contention_time, total_estimated_execution_time, p99_latency) - │ estimated row count: 0 - │ table: transaction_statistics@primary - │ equality: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id) = (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8,aggregated_ts,fingerprint_id,app_name,node_id) - │ equality cols are key - │ - └── • render - │ columns: (crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq, aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8_eq: mod(fnv32(crdb_internal.datums_to_bytes(aggregated_ts, app_name, fingerprint_id, node_id)), 8) - │ render aggregated_ts: aggregated_ts - │ render fingerprint_id: fingerprint_id - │ render app_name: app_name - │ render node_id: node_id - │ render crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8: crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8 - │ - └── • scan - columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) - estimated row count: 1,024 (0.10% of the table; stats collected ago) - table: transaction_statistics@primary - spans: /0/2022-05-04T14:00:00Z/"123"/"test"/2-/0/2022-05-04T15:59:59.999999001Z - limit: 1024 + └── • scan + columns: (aggregated_ts, fingerprint_id, app_name, node_id, crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8) + estimated row count: 1,024 (0.10% of the table; stats collected ago) + table: transaction_statistics@primary + spans: /0/2022-05-04T14:00:00Z/"123"/"test"/2-/0/2022-05-04T15:59:59.999999001Z + limit: 1024 statement ok RESET CLUSTER SETTING sql.stats.flush.interval diff --git a/pkg/sql/opt/exec/execbuilder/testdata/update_from b/pkg/sql/opt/exec/execbuilder/testdata/update_from index 3f30507397d3..e387ffa7a9b6 100644 --- a/pkg/sql/opt/exec/execbuilder/testdata/update_from +++ b/pkg/sql/opt/exec/execbuilder/testdata/update_from @@ -17,20 +17,13 @@ vectorized: true │ └── • render │ - └── • merge join - │ equality: (a) = (a) - │ left cols are key - │ right cols are key - │ - ├── • scan - │ missing stats - │ table: abc@abc_pkey - │ spans: FULL SCAN + └── • render │ └── • scan missing stats table: abc@abc_pkey spans: FULL SCAN + locking strength: for update # Update from another table. statement ok @@ -86,20 +79,13 @@ vectorized: true │ └── • render │ - └── • merge join - │ equality: (a) = (a) - │ left cols are key - │ right cols are key - │ - ├── • scan - │ missing stats - │ table: abc@abc_pkey - │ spans: FULL SCAN + └── • render │ └── • scan missing stats table: abc@abc_pkey spans: FULL SCAN + locking strength: for update # Check if RETURNING * returns everything query T @@ -126,27 +112,21 @@ vectorized: true │ render b: b │ render c: c │ - └── • merge join (inner) + └── • render │ columns: (a, b, c, a, b, c) - │ estimated row count: 1,000 (missing stats) - │ equality: (a) = (a) - │ left cols are key - │ right cols are key - │ merge ordering: +"(a=a)" - │ - ├── • scan - │ columns: (a, b, c) - │ ordering: +a - │ estimated row count: 1,000 (missing stats) - │ table: abc@abc_pkey - │ spans: FULL SCAN + │ render a: a + │ render b: b + │ render c: c + │ render a: a + │ render b: b + │ render c: c │ └── • scan columns: (a, b, c) - ordering: +a estimated row count: 1,000 (missing stats) table: abc@abc_pkey spans: FULL SCAN + locking strength: for update # Update values of table from values expression query T diff --git a/pkg/sql/opt/memo/logical_props_builder.go b/pkg/sql/opt/memo/logical_props_builder.go index b37ebbb63cd0..aa2194d911b2 100644 --- a/pkg/sql/opt/memo/logical_props_builder.go +++ b/pkg/sql/opt/memo/logical_props_builder.go @@ -24,6 +24,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/types" "github.com/cockroachdb/cockroach/pkg/util/buildutil" + "github.com/cockroachdb/cockroach/pkg/util/intsets" "github.com/cockroachdb/errors" "github.com/cockroachdb/redact" ) @@ -2513,6 +2514,69 @@ func (h *joinPropsHelper) setFuncDeps(rel *props.Relational) { // created new possibilities for simplifying removed columns. rel.FuncDeps.ProjectCols(rel.OutputCols) } + h.addSelfJoinImpliedFDs(rel) +} + +// addSelfJoinImpliedFDs adds any extra equality FDs that are implied by a self +// join equality between key columns on a table. +func (h *joinPropsHelper) addSelfJoinImpliedFDs(rel *props.Relational) { + md := h.join.Memo().Metadata() + leftCols, rightCols := h.leftProps.OutputCols, h.rightProps.OutputCols + if !rel.FuncDeps.ComputeEquivClosure(leftCols).Intersects(rightCols) { + // There are no equalities between left and right columns. + return + } + // Map from the table ID to the column ordinals within the table. + getTables := func(cols opt.ColSet) map[opt.TableID]intsets.Fast { + var tables map[opt.TableID]intsets.Fast + cols.ForEach(func(col opt.ColumnID) { + if tab := md.ColumnMeta(col).Table; tab != opt.TableID(0) { + if tables == nil { + tables = make(map[opt.TableID]intsets.Fast) + } + colOrds := tables[tab] + colOrds.Add(tab.ColumnOrdinal(col)) + tables[tab] = colOrds + } + }) + return tables + } + leftTables := getTables(leftCols) + if leftTables == nil { + return + } + rightTables := getTables(rightCols) + if rightTables == nil { + return + } + for leftTable, leftTableOrds := range leftTables { + for rightTable, rightTableOrds := range rightTables { + if md.TableMeta(leftTable).Table.ID() != md.TableMeta(rightTable).Table.ID() { + continue + } + // This is a self-join. If there are equalities between columns at the + // same ordinal positions in each (meta) table and those columns form a + // key on each input, *every* pair of columns at the same ordinal position + // is equal. + var eqCols opt.ColSet + colOrds := leftTableOrds.Intersection(rightTableOrds) + for colOrd, ok := colOrds.Next(0); ok; colOrd, ok = colOrds.Next(colOrd + 1) { + leftCol, rightCol := leftTable.ColumnID(colOrd), rightTable.ColumnID(colOrd) + if rel.FuncDeps.AreColsEquiv(leftCol, rightCol) { + eqCols.Add(leftCol) + eqCols.Add(rightCol) + } + } + if !eqCols.Empty() && h.leftProps.FuncDeps.ColsAreStrictKey(eqCols) && + h.rightProps.FuncDeps.ColsAreStrictKey(eqCols) { + // Add equalities between each pair of columns at the same ordinal + // position, ignoring those that aren't part of the output. + for colOrd, ok := colOrds.Next(0); ok; colOrd, ok = colOrds.Next(colOrd + 1) { + rel.FuncDeps.AddEquivalency(leftTable.ColumnID(colOrd), rightTable.ColumnID(colOrd)) + } + } + } + } } func (h *joinPropsHelper) cardinality() props.Cardinality { diff --git a/pkg/sql/opt/memo/testdata/logprops/join b/pkg/sql/opt/memo/testdata/logprops/join index d2ef3a87543a..41d3768c0057 100644 --- a/pkg/sql/opt/memo/testdata/logprops/join +++ b/pkg/sql/opt/memo/testdata/logprops/join @@ -23,6 +23,10 @@ exec-ddl CREATE TABLE abc (a INT, b INT, c INT, PRIMARY KEY (a, b, c)) ---- +exec-ddl +CREATE TABLE xyz (x INT, y INT, z INT, PRIMARY KEY (x, y)) +---- + exec-ddl CREATE TABLE ref ( r1 INT NOT NULL, @@ -2243,14 +2247,14 @@ full-join (hash) # Self-join case. Since the condition is equating a key column with itself, # every row from both inputs is guaranteed to be included in the join output # exactly once. -norm +norm disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight) SELECT * FROM xysd INNER JOIN xysd AS a ON xysd.x = a.x ---- inner-join (hash) ├── columns: x:1(int!null) y:2(int) s:3(string) d:4(decimal!null) x:7(int!null) y:8(int) s:9(string) d:10(decimal!null) ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── key: (7) - ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(8-10), (9,10)~~>(7,8), (1)==(7), (7)==(1) + ├── fd: (1)-->(2-4), (3,4)~~>(1,2), (7)-->(8-10), (9,10)~~>(7,8), (1)==(7), (7)==(1), (2)==(8), (8)==(2), (3)==(9), (9)==(3), (4)==(10), (10)==(4) ├── prune: (2-4,8-10) ├── interesting orderings: (+1) (-3,+4,+1) (+7) (-9,+10,+7) ├── scan xysd @@ -2275,7 +2279,7 @@ inner-join (hash) # Self-join case with a constant equality filter. The filter pushed down on both # sides of the join is redundant, so all rows on the left side of the join will # be preserved. -norm +norm disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight) SELECT * FROM xysd INNER JOIN xysd AS a ON xysd.x = a.x WHERE xysd.x = 10 ---- inner-join (hash) @@ -2283,7 +2287,7 @@ inner-join (hash) ├── cardinality: [0 - 1] ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── key: () - ├── fd: ()-->(1-4,7-10), (7)==(1), (1)==(7) + ├── fd: ()-->(1-4,7-10), (7)==(1), (1)==(7), (2)==(8), (8)==(2), (3)==(9), (9)==(3), (4)==(10), (10)==(4) ├── prune: (2-4,8-10) ├── select │ ├── columns: xysd.x:1(int!null) xysd.y:2(int) xysd.s:3(string) xysd.d:4(decimal!null) @@ -2372,7 +2376,7 @@ inner-join (cross) └── filters (true) # Case with a self-join in the input of an InnerJoin. -norm +norm disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight) SELECT * FROM fk INNER JOIN (SELECT * FROM xysd INNER JOIN xysd AS a ON xysd.x = a.x) f(x) ON r1 = f.x ---- @@ -2380,7 +2384,7 @@ inner-join (hash) ├── columns: k:1(int!null) v:2(int) r1:3(int!null) r2:4(int) x:7(int!null) y:8(int) s:9(string) d:10(decimal!null) x:13(int!null) y:14(int) s:15(string) d:16(decimal!null) ├── multiplicity: left-rows(exactly-one), right-rows(zero-or-more) ├── key: (1) - ├── fd: (1)-->(2-4), (7)-->(8-10), (9,10)~~>(7,8), (13)-->(14-16), (15,16)~~>(13,14), (7)==(3,13), (13)==(3,7), (3)==(7,13) + ├── fd: (1)-->(2-4), (7)-->(8-10), (9,10)~~>(7,8), (13)-->(14-16), (15,16)~~>(13,14), (7)==(3,13), (13)==(3,7), (8)==(14), (14)==(8), (9)==(15), (15)==(9), (10)==(16), (16)==(10), (3)==(7,13) ├── prune: (1,2,4,8-10,14-16) ├── interesting orderings: (+1) (+7) (-9,+10,+7) (+13) (-15,+16,+13) ├── scan fk @@ -2394,7 +2398,7 @@ inner-join (hash) │ ├── columns: xysd.x:7(int!null) xysd.y:8(int) xysd.s:9(string) xysd.d:10(decimal!null) a.x:13(int!null) a.y:14(int) a.s:15(string) a.d:16(decimal!null) │ ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) │ ├── key: (13) - │ ├── fd: (7)-->(8-10), (9,10)~~>(7,8), (13)-->(14-16), (15,16)~~>(13,14), (7)==(13), (13)==(7) + │ ├── fd: (7)-->(8-10), (9,10)~~>(7,8), (13)-->(14-16), (15,16)~~>(13,14), (7)==(13), (13)==(7), (8)==(14), (14)==(8), (9)==(15), (15)==(9), (10)==(16), (16)==(10) │ ├── prune: (8-10,14-16) │ ├── interesting orderings: (+7) (-9,+10,+7) (+13) (-15,+16,+13) │ ├── unfiltered-cols: (7-18) @@ -2883,3 +2887,166 @@ left-join (hash) └── eq [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)] ├── variable: r_b:2 [type=int] └── variable: b:7 [type=int] + +# It is possible to infer equality filters for a self-join on key columns. +norm disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight) +SELECT * FROM xyz INNER JOIN xyz foo ON xyz.x = foo.x AND xyz.y = foo.y +---- +inner-join (hash) + ├── columns: x:1(int!null) y:2(int!null) z:3(int) x:6(int!null) y:7(int!null) z:8(int) + ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) + ├── key: (6,7) + ├── fd: (1,2)-->(3), (6,7)-->(8), (1)==(6), (6)==(1), (2)==(7), (7)==(2), (3)==(8), (8)==(3) + ├── prune: (3,8) + ├── interesting orderings: (+1,+2) (+6,+7) + ├── scan xyz + │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ ├── key: (1,2) + │ ├── fd: (1,2)-->(3) + │ ├── prune: (1-3) + │ ├── interesting orderings: (+1,+2) + │ └── unfiltered-cols: (1-5) + ├── scan xyz [as=foo] + │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ ├── key: (6,7) + │ ├── fd: (6,7)-->(8) + │ ├── prune: (6-8) + │ ├── interesting orderings: (+6,+7) + │ └── unfiltered-cols: (6-10) + └── filters + ├── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + │ ├── variable: xyz.x:1 [type=int] + │ └── variable: foo.x:6 [type=int] + └── eq [type=bool, outer=(2,7), constraints=(/2: (/NULL - ]; /7: (/NULL - ]), fd=(2)==(7), (7)==(2)] + ├── variable: xyz.y:2 [type=int] + └── variable: foo.y:7 [type=int] + +# Self-join filters cannot be inferred if not joining on a key. +norm +SELECT * FROM xyz INNER JOIN xyz foo ON xyz.x = foo.x +---- +inner-join (hash) + ├── columns: x:1(int!null) y:2(int!null) z:3(int) x:6(int!null) y:7(int!null) z:8(int) + ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) + ├── key: (2,6,7) + ├── fd: (1,2)-->(3), (6,7)-->(8), (1)==(6), (6)==(1) + ├── prune: (2,3,7,8) + ├── interesting orderings: (+1,+2) (+6,+7) + ├── scan xyz + │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ ├── key: (1,2) + │ ├── fd: (1,2)-->(3) + │ ├── prune: (1-3) + │ ├── interesting orderings: (+1,+2) + │ └── unfiltered-cols: (1-5) + ├── scan xyz [as=foo] + │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ ├── key: (6,7) + │ ├── fd: (6,7)-->(8) + │ ├── prune: (6-8) + │ ├── interesting orderings: (+6,+7) + │ └── unfiltered-cols: (6-10) + └── filters + └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + ├── variable: xyz.x:1 [type=int] + └── variable: foo.x:6 [type=int] + +# Self-join filters can be inferred even if the join key wasn't a key in the +# base table. +norm +SELECT * FROM xyz INNER JOIN xyz foo ON xyz.x = foo.x AND xyz.y = 1 AND foo.y = 1 +---- +inner-join (hash) + ├── columns: x:1(int!null) y:2(int!null) z:3(int) x:6(int!null) y:7(int!null) z:8(int) + ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) + ├── key: (6) + ├── fd: ()-->(2,7), (1)-->(3), (6)-->(8), (1)==(6), (6)==(1), (2)==(7), (7)==(2), (3)==(8), (8)==(3) + ├── prune: (3,8) + ├── interesting orderings: (+1 opt(2)) (+6 opt(7)) + ├── select + │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ ├── key: (1) + │ ├── fd: ()-->(2), (1)-->(3) + │ ├── prune: (1,3) + │ ├── interesting orderings: (+1 opt(2)) + │ ├── scan xyz + │ │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ │ ├── key: (1,2) + │ │ ├── fd: (1,2)-->(3) + │ │ ├── prune: (1-3) + │ │ ├── interesting orderings: (+1,+2) + │ │ └── unfiltered-cols: (1-5) + │ └── filters + │ └── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)] + │ ├── variable: xyz.y:2 [type=int] + │ └── const: 1 [type=int] + ├── select + │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ ├── key: (6) + │ ├── fd: ()-->(7), (6)-->(8) + │ ├── prune: (6,8) + │ ├── interesting orderings: (+6 opt(7)) + │ ├── scan xyz [as=foo] + │ │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ │ ├── key: (6,7) + │ │ ├── fd: (6,7)-->(8) + │ │ ├── prune: (6-8) + │ │ ├── interesting orderings: (+6,+7) + │ │ └── unfiltered-cols: (6-10) + │ └── filters + │ └── eq [type=bool, outer=(7), constraints=(/7: [/1 - /1]; tight), fd=()-->(7)] + │ ├── variable: foo.y:7 [type=int] + │ └── const: 1 [type=int] + └── filters + └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + ├── variable: xyz.x:1 [type=int] + └── variable: foo.x:6 [type=int] + +norm +SELECT * FROM xyz INNER JOIN xyz foo ON xyz.x = foo.x AND xyz.y = 1 AND foo.y = 2 +---- +inner-join (hash) + ├── columns: x:1(int!null) y:2(int!null) z:3(int) x:6(int!null) y:7(int!null) z:8(int) + ├── multiplicity: left-rows(zero-or-one), right-rows(zero-or-one) + ├── key: (6) + ├── fd: ()-->(2,7), (1)-->(3), (6)-->(8), (1)==(6), (6)==(1), (2)==(7), (7)==(2), (3)==(8), (8)==(3) + ├── prune: (3,8) + ├── interesting orderings: (+1 opt(2)) (+6 opt(7)) + ├── select + │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ ├── key: (1) + │ ├── fd: ()-->(2), (1)-->(3) + │ ├── prune: (1,3) + │ ├── interesting orderings: (+1 opt(2)) + │ ├── scan xyz + │ │ ├── columns: xyz.x:1(int!null) xyz.y:2(int!null) xyz.z:3(int) + │ │ ├── key: (1,2) + │ │ ├── fd: (1,2)-->(3) + │ │ ├── prune: (1-3) + │ │ ├── interesting orderings: (+1,+2) + │ │ └── unfiltered-cols: (1-5) + │ └── filters + │ └── eq [type=bool, outer=(2), constraints=(/2: [/1 - /1]; tight), fd=()-->(2)] + │ ├── variable: xyz.y:2 [type=int] + │ └── const: 1 [type=int] + ├── select + │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ ├── key: (6) + │ ├── fd: ()-->(7), (6)-->(8) + │ ├── prune: (6,8) + │ ├── interesting orderings: (+6 opt(7)) + │ ├── scan xyz [as=foo] + │ │ ├── columns: foo.x:6(int!null) foo.y:7(int!null) foo.z:8(int) + │ │ ├── key: (6,7) + │ │ ├── fd: (6,7)-->(8) + │ │ ├── prune: (6-8) + │ │ ├── interesting orderings: (+6,+7) + │ │ └── unfiltered-cols: (6-10) + │ └── filters + │ └── eq [type=bool, outer=(7), constraints=(/7: [/2 - /2]; tight), fd=()-->(7)] + │ ├── variable: foo.y:7 [type=int] + │ └── const: 2 [type=int] + └── filters + └── eq [type=bool, outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + ├── variable: xyz.x:1 [type=int] + └── variable: foo.x:6 [type=int] diff --git a/pkg/sql/opt/norm/testdata/rules/combo b/pkg/sql/opt/norm/testdata/rules/combo index 1436f2299dc4..e4ab57e43110 100644 --- a/pkg/sql/opt/norm/testdata/rules/combo +++ b/pkg/sql/opt/norm/testdata/rules/combo @@ -489,7 +489,7 @@ INNER JOIN a a3 ON a2.k = a3.k INNER JOIN a a4 ON a3.k = a4.k INNER JOIN a a5 ON a4.k = a5.k ---- -https://raduberinde.github.io/optsteps.html#eJzsvd9y3MiV53__ewr8dCUu_7iBYklFOhRhr90z0Q5Z7e327o1a4SirSzZbFNlLUrPTu70TE_MMfTlP5yfZqGKRLKDyzzmJTCABfHwjN6uAAjLPyczzOd88-X-effvfXj87f_btl6-__N2fi_9S_NM3X_-xWBbL8rurr968-fKb4g9ff_Vm_Yeq-PpNsSxPPhavNv80P59tPq_uP5_tf366-Xx2__np_ufzzeen95_PTz4-O3r25vrm0-8vPnx4dv7s-Pi4WP7qq6uLu4vl5XdXh4eHxV-f_vM3vymOy-KwPJpXxW9-893V4Y831z-s3t99d3VY_OOX__zHL__-j1_-vXh_ffn509XtefHxvPz_rz5fXhYX51Xx4XxW3J6fFj-cz4uP54uHD86KD-flF8XteVkWP5yX1fqi-cOH5Yv1py_Xny7Wn54VH8-r6vGes-LDeXVa3J5X8-KH8-rF-tOzh09nX6x_sixuz2dV8cP5bFZ_yI-rn86L5-VRdXZQ_-DD9-u_Hxwff_f5iy9mq-fV8fzgqHg-Ozo9-Ld_2_6tPKqONn9dPH3v7Lis1n8qvzgqy6evLo7OjsrqqJwfl2dHVXVcvTiqzo5ns81X50-Xly-Oy7PNH18elYud6483Vx-VL472b7A4ePXqeTk_qqr1e9zf8dWr54unP1TVzpvMjqsXmz-eHlXzvZ-4f8Cjana0-xNVdX_H9a_c3_Hs6Y6zL7bfmpVHs8pyx_snPpp9cbS949nTHatt4_-ybfyLq6vVzfEP1xdXxfO_L2__vvl48z-Dea2948HCluXJ2siW5cnazpblydrUluXJ2tqW5cn7m-__-peLq7vVzdXy8i-f_uX9-7_cXXxa3d4tP_14_mL9jbvlXy9X1xffn7_cONeDgS6rk7WNLquTjZkuq5ONpS6rk42xLiv3rcvZ-iuP9y5PN575aOHL2cnGyJezk42dL2cnG1Nfzk421r6cuW9ffbH-yuPtq3Lj2I8usjw92XjJ8vRk4yjL05ONryxPTzbusjz13P7l-itPt19sBoxHH1vOTzZutpyfbDxtOT_ZONtyfrL2t_W_ztvPTtdfebz9bG7o7E-fL-8ufry8eH9x99N5cbn6cHd8c_2_bp-v_nX5_u7yp-Prq9XBUXFz8be_bz_436ub6-Prm-NP1zcrk_U0_L7x6b7zvzQ7__3f6-5_anX_h8_q_l6VBn9_cPXtp3oPXxg8_MGtHz71-fTc4NMPLrz9dN-LG03pcuX_wKMz8Wh7p0TzvP-wOGDl-FLGflh3wtYe6GgEgQfhSGJH8jZebIO32L2xywdi_ju273-JXfN9f3N9e2u_ZExmLH3JB3twtGR3ViF9hNv3y6tiWbxd3r5alu98V3XcrcrH2XaA9923__wS0AshzVqFN2uPVm1sW79pm9o2pt0-3vji8m51c-v-elG_5t5KtxTmvCzeXn--W928er4eEN9fX93e3Swvru5uXz3_VXlePP_Vm__--nVxXLw7sPefqbtn3u7e7-d-5kmz-zhmM6PjKOce681lPdvs0o2HbMHZeTl_7NPNSqzZrYvdbv118atyXuvno-LD969MM6OhR009f-o3lKcu7zgIMPa1ccVu6mTtgnj_rp7ebXbrtjdfPbTTk69uV-wNf503eraqDD17352PS_z7bn5niHR3O3Xu6Pyn3uyKopi78czV4N-f63HAzu1sHdfssW0_vXpoi8cee-Aa9R6rd9C6x84MPXbfS49I47733n13dQ-1v7349OPlxYefvl1-Wv2P5c2X__Pz8vLi7mJ1-4C5HV_YgO-jebXDvotH9m3s49zYtzEEX1uCe4UJ-07Kvgs3-y6aQ82gI6Nhk7Lko_ZeZ7cFEns3bPh949NcoUP-7HuvKV2uPCbWMWyPtndKNM9zsW_blzL2w_js29YIAg_CkRQxvafxYhu8i317vpur-dfZt-clTOzbcsmYzFj6krvsW3ZFUvYte4Qm-3Zf1Tn7Vj3OE_sWXRbGvvXNWoU3a49WbWXf-raNabd7KObY8fWifk189m36lefbu3_1bfH7r77981dvfvfne13k73777Z-fb27622-Lr978eXFwUHz9Tf3P__Xrr18fSB_s18Xdeu46sNuXjc1rR-1-5nE7mxfaSBibt9x8z_KM32uaXFdsvvkwNjbvMZTO2LznOXbYvK87wth8866e3m12a6ds_vHnbWze1vmdsXnbA-yweWuDh7H5vS5pdlyzx3pk83-4vrj6p8fUQY3K1z6Cx8Pj4fF5Qwd4PDweHo9Hw-Ph8TgSPB4eD4-Hx8Pj4fF6Ht8LKddr5QWPB7F3DQAQe4g9xB5iX7g7rtljPRD7P32-_fs9jv_q6u76D9cXV69XH-4eoL3t0xq3fwm3h9vD7XODE3B7uD3cHo-G28PtcSS4Pdwebi_jny6gGVpD5vGq1eU2SHR9eyiU391SrSi_qk5LwlsHlQrqvP9CnkpZOCh9b6qq3OQD8Ek82Q0k5sAbYSNIGrtQ1W4qnt_dfBaskUgHkQ4iHUQ6iHRQl-mgb1afrv9l9eb67s3ny8vfXV99f3F3cX31kA6yfbpNB73cpINekA4iHUQ6KDfmRTqIdBDpIDyadBDpIByJdBDpINJBUbdxjCqxo7os5vYNT_ZFdHGejdyurVtlX3RpOglez46yh8F2Ujd260q4Z4gciN-3yYGQAyEHQg6k-xzIl5cXny6ulnerb7dL2vvkx96ft1mPF5usR0nWg6wHWY_c0A5ZD7IeZD3waLIeZD1wJLIeZD3IekTdBPMQIsq-PcpSVyK6HpYrUd262bqiiztv5JCnUrZ157mSR56d8ARu2ZBABoMMBhkMMhhkMMhgTDeD8Xr14e63t7fX7y-Wd6s_XF9c3X6zjtIeMhnWj7cZjZKMBhkNMhp5YhsyGmQ0yGjg0WQ0BpTRaEIVhwc1EUoAPcbpJLG_BXSR_QjMfnja02S_MsY-aDOWvuRu9kN2RQ7ZjxDinhlqz6GoV7uSan1X19LW00rSdIKZtEfon3wyk72rek7b_UBpBnPxBepcRfqUPVklu8cmLGn2mFXyrCWaWSXtOqKflaXdT4SNFJZVstxctt2tqH-5s6ySXk8xk26R69wcVE8lHj3jGIeqdJ_uKKmeTUWasCZfSb6SfCX5SvKV5CvJVw4uu0G-knylJV953GxKFyPSpFlw-97d3p5SI6mZU1LTk-nBkyJkKmupRM93czVQVSpRl3bKJd-kTTTFSZMoU0u955TEyaS4zSNPH40pb5R8kva0YZp8lAGNeb7ZbQaqkz3Tfvu2XDFCM5e-a0tzlP2IeDMR6dEJpEf1DezfRddXniugiQVuECfP5f4N2Q67nvNcsjyuNYVuy3OJI7OO43n7ZO5riLA8V_Ounvx40wo6zXMpZGSnATKyfhaF6tVbnH6XpY8t38vKCrwZb7KiZEWN_UtWlKyoPyt67M6KHlo-3suiAH17JzPJR_S93BtZ0zFkTW3pHFy6d5e2d0otc2n7UsYO013mUphY6j2jJE4ltUySSJNH_WWN_OmiSE2g2Kk7BmQ-9PWHrW8iZ5wMgartK53nmDJfZ9hi_jZFfvG30G0EaVJf8k0KZGIVfHa4Zk4mlkys6pnIxJKJtX69qF_TW8lTT_pNO7z3sxJQT9lx0m-Wm4fVvO00_eZJGO8n4m3pNz9M6wrW2qMa68uGpd-Om13XzLI3e7bT9JtEQzvXaGg7DnblwWmc3vQkoptfyKNvre66yb3-6ebz1Sap-nr14e5315e3D1lXwwfkW8m3drkLdS8JQ_ald8DS3Rgf3tl0XjZ7iGuD7J4fZ5yLyxwq73lJi0lr0g25Z7G-1Gj_KUDtL3u3-vrCnuBNZ_a72IwzVvFtpoDep4C0pFugnCUhSUKyd38jIUlCMgczJyFJQlL1TCQkSUhav17UryEhSUKyj-qnnrSVx1p62_UnD2HipK2ad1VWtu00bbX385601WYroDFvtfNJLXE1J3FF4mowiavhRhxDB1ndJqJUXaFqWEUzRUjgkHXoOn3D0B02dBf2xWMpqhURL_tiXaAp8iaSIsD-XBbTTe8byeMCub2R5nHdG3VXX27Tl1zgSZbIsz3eT9HFu4cZXUKrgrUcBVrXHBtRlkhxfKvbvkdo5tJ3TWOOdjyuOCg2r0renixR4DmyPR7ja80S6Q-TTX1Qr__AxL6yRCHn9frdIE6WSHWOpfvrRf2avguI2rJE2uG9n5WAesqOkyWKeqhmr9vWpLvS-soSydZophAmTpaoZV3YLDauGeYV8FgIHrMpeb3VgQqb9wx3UTx0suTsoKh1ggrrkOX8HujF5XyNxpJ64Aij0-RLL0lDpnAY-9pO8vURMBnJayoNf4T2r3rhhHZqD1AVV_UJaxSP2SQ2gkv7wTb6B3tiN_JrWwCcwEaftWz0_j2ylbvEgTqCH5LVssgB70gmC_fJIE7j6-l4EOczCVcEcZCP6xcUVtI0jx6PDCnsxrFf3sZsUD3VuDE_jC-wigOEnMVsXAbQ7PmsBcTyujfzjXz4bFTy4WM0aH1Ctg4bv0WzJ23wPJTae3MEYt-YYl_Gh1YQnkECTXCUWio7qxbfmwx4OJNnTskbJs4btm5gRiTTIOAVKVpXM8Ol36NOww533G2_IbrRFFErkBgXuIapwLTQIiNt2QzQaCzpYDTCjFzHYkBzQ6aUcExz3o6t2bDsuRrAEtwyntrTExKDHYFgQfKaynFxhMOj6oWj7ilx_pJVUe-8Kp_dJc7HdG8xMV7aj2BB_2C2zSaua6PtOBE3-v62E12j9--RrdwljmBB8EMyfXoOggXJZOHelOI0vp52pjifSbgiiCNYcP2Cwkqa5tG_YCE7iOZfSAUtw4U6BpUCtLA5yXCXUz0qkhqtmUxcWliHEfE1I4guxO8a4hGjjDO0b53agO1LKO2leeqkzc8qF0vXr-8nAAl8Ools2niDJNppf0e4BdTCjsjEfdu7VZzwRPprerlsr4GKeN6Ry6vN9pmBxtr8YJoFR5zgRSygltpQ03h6LM3ssJr9-sw-S-kjJ2m2jv1KzS6LaFmu2WYFgp4fie76rDgsj17M0F2j6stYZR3UyCNoXhugQVntcP7Gp4wAo9FRT3cYcCz20E4PZsCyr3ija6cZlCI15-THHH-eyBhTDd9xpyK-ncCAEltzOJ1xpPURuLKJfgCTfldqzUmoDvp1pHTv7DIR7f4ilBaplRZZjS8hb2o3t8abplh6eDQlYpsegaZE_K4hvj9KTYn2rbsSReU6ISVTOQm2z0S0Q4VVCUYdu2JBa1557j8wP6t8E0L9-n6EQIFPJ9mOYLxBkj0J_o5wb0wQdkQmY257t4ojBJL-ml6GnrcQqP8Y6SBIqdhGSj7WYCfw1TuR0DZ-Uy34K7x2mpmYtvHAYYraus1nKas1P6JWW1u_S3KBraNz5CpbX-fk5Ov2abZ1_6QS3RaFecJVXN24RWezrvelbPLbcGPrG_4YDcyO6H1GpZff6g1JaTzIb5HfDll--2KG_BYxWGaS26k2rC2ERWzrcPXGp_j7QAW2U21d50IOae1ghif7ajaitHaqThJFTjvVxrM5NULaqQlpB-oBvYpnp9NmTqtENpuB8x0MRyqb9du6DEI7Ow5gpkTWmZWPJlPS5eeg2lcV24NgOjIPYmMXDeYpzOnFQuuPKu2WNpsk0BB1PA3qX0RmBo0XST3LecRQQXY3ODFU27Eh-GmDuidMA737_wcl3Qp4RGXDGmdFufy6fpeutGL-n3OYcATLm4nlQb5uzWnYtS-QQm7TUpem-kmtnMglMEGXhi4NXRq6tDHp0r5ZL5GNwrSdT1CmoaIYrCZtRG1qoyHI0Rz-3fgUJx-tEG1EDetctKFBG8yYZF-5RtGgTX7c6aLxRtRuNk9GeDYd4dlAx4xBSM4G3VxOU0Rt1qvHHYxPZ4bEDImZ-3eSS8w6ccxhicvQlQ1fV9bWrA_yU5SNS0w2RWnYtIVe3eimelF5pfTXxqM2JV6qm7SfHmLpp0Lu0qugKY6ULqdRL8ZDRtNQtdbTJa7tJfhJ9yCAZgvNll-hg2ZLZjVotqap2fry8uLTxdXybvWnx_rsG8XW_t939Vrzs1HptfZWrogNkBV1Lyva-xRbRPiC8EUifLF9ZeQJWbQbk9VuOL83crNHhxCsQ5B8PascIJn5CWbmxddkZarJTvOTn532ny0su-NxTp8E7iRt12JDct_pL-05VkNJVwc-VktzCfrNvrO8KQ2n8ag9l8Zo94PJ6k4486at27mXvGmMlk6SkvTlB4OO5ws53K9xi2xP-As7iCyOtwb-lvYwsdY-2qZN5eeHxfLMVq2qOAMsij9qz3cMOfaLLD1ZeqMNea5oGg9ZerL0-WTpQyqrzM9GmKnPprJKPjCcoir2NkXykE_ydgCVVEbl1ONuWAQklpAMAUn-lVPGOM4Ms91Q26C2sd6710op-YwREy2SgiJp5JVRKIoS8KKx9VZiDUxWRh4C1cXanazeNG-dWL8VXCje0sHo0XjFQQjbtE-EsC1OHZZ2JR3i6BwCf0tbyKG1zqFNm8rPo4mlc2jVqipLj6BzkP5Um5IEQ9I5aOtSdaZz0Gp9gh6slZxCK-kNlFOolTmmn3FO-Eg2xLaFZGMMkg1tYYWtXGM-KrnG3tKRBCTSAqopDNIAHXMcGfDBuIp0oh93uoZM7mQzuc7vjdHWA1KTzmy3ezNyEKfN1B9kKbtk8Ha89RusaQBXLnh0SUTFc6feyjya1Foka_MPZuFMs9898h1vj2-xD7jvpJP2KcP2w8dJOgX-lnYXfOukU5s2le99j5V0atWqKkuPkHRSZwoC9rn3kHQSj9G9JJ1Ch8F4uR1P0sX7G1pzaNpBp0kXyXLcMFe7Lut8mlY8TMsZWvFLonoAmczLigeU17uIOhvrf0ZS5SLWHBzYfu7aFpFn3tAWlFpwhPlW8CuKOhY9z7KSgbU5wQaNqtHoi31a1XRMwIzqur2-cAniBcQLGYoXgupNzEcoYBhxvQmKTOTSkAg_qCmRsePSmi2817G4QjUzmHHGvsIceN0IikVkN1Q0vFOaexy-v4XEg04NynCbIg-p1BiKXlDpIqtxrdFA41CJSR4ZlVg3pSYCLavzNKDiYRKajj2Jorgqtz3h3jSg4NIYaUD9z0g2gcdKAwa2n3vrd-Q0YGgLSi04QhpQ8CuKfa09pwFdD2ZLAyrsKPr26dApMEIa0HV7_b7-AaQBJWW6OksDSvLVQQ-jzjBKFOuBGUZRptl0a-cSfeSZS_W26_usZTmqrOXe6gxcT7KNXdYZ2ptj0h1iemgv9bq_WVAcoGeKe5TeY0t5RA3q2c-tdLxm1m56yRbPi6XYHEaGoa2h-odQXZSWgdHKHjnZXkXlxpwx0HPJ2yktq3N6rniYhKZjZ4-Kq3Lb3Oql54JLY9Bz_c9IdrPGoueB7efewxqZnoe2oNSCI9Bzwa8oNij2TM8lA6t8l2oseq4ewLxTYAR6LqKm4g3K_dBz59KsM3quWty0AtYeqmy8r6Qvm53YKVW2hefeXcS5r6o9Txt1a7FrIPF8c7jLaM-LyQ2o88Wz7DnSWIh9wSG7IIct59KFsvuqGGtk1S_Y9phHXhnr22p_Z3ma9XBAawksM8Iq2P0Dnk3Feax9PaOhe-945BWvZuhxzVER1rmWO8u6tNmXPZZasS9sxauiVlkT-3LW18IBK9nmLWW7-seliiiLw_Lo9OVGFXH8lMQzJ8WRSkwxMeGWSuxlgqcgldh7afYlR0-E2aQSto2YeqmE7U5PUglb-riVVMKbc8mUNbTCVYnyNGzVbSmIUBhlzrbpfdrIVicF6iPiYJ4XkxtQ5xxM9hxpLMROG2QX5JA1lnIw91UxOJjqF2xp4sgcTN9W-8nhNBwsoLUElhmBg7l_wJNLyoODeUZDd_o3MgfTDD2uOSoCB7PcWdalzb7sMcsrTfDG4mCy9YXDd-JldKWJ-X442HHz55sc7NAROh7GiBoNN3kKGA_bxYqHvsRi3qtwNfyNvQiz9s0WIRMSWio2-hfzh0MJBO0PGs-4XCmmvlfyKRQQiqBv_8udx3veR4huCPa1tPe7fQkdlLGd9YIYYZ305rvKhjTBnKpdZkHtEseUAywuQuBmvbchdZpVuGYfwvYVC2mCNOGgYZlEIoRm-zf1dlqvAZlhWbKvSYgchnmneLMTtBMhCPqj15Dr0BFtPTt69uW__nh5fbP6_cWHD8_On93LEL5ZXd98v7r5w_XF1WNB-frf7uUHpy935AeWnfiIDqYXktic_n6rfIQg3j6FHtQ-DCud7Nn8nncEpa4aEXt57R59-42ssvYYf3DWX1SmLgWWKHYTrb_6j9ZS1NJUBPEpwjx1ScC0QbwmpPJ-dwBB_PSCVe9z7JcLTcg4pDffLQ2ahnEca83GekEcs5Hevpah7sFsDJXzsmIcsg49dRl6S8Yh7ELLHBuBcQR0Wq-Mw9NXc2evBte9FDAOX7doC1EK-qNXxmF4tMbOin9eXa1ulnerP65u_raqgQ3TJzW8cQbeAG-AN_KILcEbQ_UY8AZ4w6FOV-ANmaDh03o-zwOGeEO21IqG9R2LTf7m4upv58XhQnDN5td2Lyrno8Au3sfKnCuogucY2olWvSZnRHXzVPXSLKiX4vDMfiiH9d4G8W9WlENlZbHkLXZLK-dyVcxjRCkzSkgNpAZSE05qXl9ff_z8oxHV1D6qsZoZrAZWA6vJI1CG1QzVY2A1sJouWc3lZkJ_CufANg-G9PSabxfvilfF29KsnG4Cn_sG3V5cLG9W65vBbyIbFLqQwehCxkdMwBPgCfAEQhLgxJBDLeAEcAI4AZwATuQKJxCS2IUkzvypVUkiye-PDERMT6KQYaZf1WMZoqNU0p_xsZFWA0SoZglSA6mB1GRHahCSwGpgNbAaWA2sBlYzblZTF5JUCElMQpJyvlGSLBCSDJ3f-C5JIpiwwRK9nkR1dAiwBCEJeAI8MRY8IS6yekaRVYAEQCKnaBAgMVSPAUgAJFoCiQc9iDdAygAsGAp_eK8xFv7wXiVRFfQb7489oA1NdksDZV-y29aMKY4UEh_I4Sy0G69UigpcxsBFsY8v6eZMJffxKOGKoICB3xhBjwIKqsaJWFDQPlY4Zw9riCxGT4pZWHkqq9yi_a8X5cAfx71j6edsrawvPR5neAoYRVI2feg5WX0xQNtjNw221-f1HVvVQwFu2GmjD2CnsNORkSDYKewUdgo7TWZc9rWrJyaDncJOe7cQ2Gkf7BR-2CLyhx_CD-GH8MN2o0jKph8EP5xyZk_QZbBT2CnsFHY6ZRIEO4Wdwk5hp8mMy75u98SjsNNxsVNZMOI8HT8VQRkOvZWFSa0ZQByIqbDZkSNMjZ1ox59FM54cQ2n9vCj_BAryg4LHh4JjlMkDAyfBwI80r0EWFyaw2KR5Rq5omAZQkEJBoaBQUCgoFDRPBAUFHarH-EFB1BgSCgoFHQUFHYGCFFwILrRe0L53wYWC1gQXWiNXcCG4sM1InLLZc8eF_edohZPpZpBKkgwVdBrEFGI6IWLKYbcQU4hpJrgKYjpUj_GzEsn8mIEJQkzjE1PZwtsZSaUKoTSxU9wC_S5orIgkU0Fj7yMYoLH3GiM0Fsa1ozmiJUPCKL2i_TZFLwyTmFH-e9NVDdo6hvc2qtPNIIwcqwKm6OvU1z-up3zjoa-7n-wii3kJsgBZgCzyiBdBFkP0mGMRsjgMjdaytFTXii66SMQWJbr3Su7Fh5UxnJkINrG_E8xg-MzAi6R2Xvn54udyflC8Xb6_-7y8XDec5DBLoAPQYWjQQdb-uZzlmgQ67N90WBoNs73FFhk1Fgn73xdYGIxkGIzk9eZwdyMkqX1UE3bMoSRQEihJHrEnlGSIHhOFklxuRuinlRvAxAlMPq5-emqFt-X8XfGqeFuZq9g-htX3Tby9rFjerNa3gZwY3glyMnxy4n0swAfgY2jgA7XFCMAHzGE0zEGly3jJVhKIA8Qhp-AN4jBUj_ETh_4iNbaSZBvZi4O-NNGeIswLrUsgKzxkLU0TCYF0SdviOo03USkTK_koi1mzpHHT1mEr5wQqqr0kOWAuAnmQ1vDQAj6xkCIOVtD1mJsuJIEMUtaQJ3Jw2Fuicjpe5YUchnhhgaXNKfsBt4mjFbknNzPIDeQGcpNH2Ay5GarHQG4gN5CbbsiNSFY1RYijEVA1-Y9YRwXNiWBZSbQebWiOqrSxU4oRB53oShuDTsLQCZwCToG-BEoxzJgLSgGlgFJAKaAUUIr8KAX6ksKhL3GmT2Ps4hkZkZieWiHDfL-qx_JkSCkVQSOFJK2HjGA9E9wGbpMRt0FfArmB3EBuIDeQG8gN5Gas5KauL5mhLzHrS6pqoy8p5-hLxkFzfJckEVDY0Mnwjs4GncAp4BQZcAr0JVAKKAWUAkoBpYBSDIxSONOmqfKlGrF93GV-HzIThX8Ni08YKsXmUsNE47FaL1s0o7IxVIpVRdsKXqhK80e8b4QKtHlSo0Ftu3mkBg0qsjBBkSY1MDIRg_NRrARIA6RBTAKmAdOAacA02XkMmAZMA6bpFNO0q1kyWmJTV5QssixYArpRohvfFfGkXh5YQo2SYcMSyARkAvkIXGKYURZcAi4Bl4BLwCXgEtlyCeQjdvlIWI0SSVZ_ZAxiesKEDPP7qh7LkBqlEvyMD4u0GiBClUpAGiBNRpAG-QiYBkwDpgHTgGnANGCakWOaunykQj5iko9sC5IskI8MHd34LumsEkkVpCJRcBKdhgROgnwEMpE3mVDJR86Kw_JoXsIl4BJwiTyCQrjEED3mWBts6ZLhWVqqK9KMHnnZkuTG1JA1OW4-wAM0kluZVn8wECvpGD2LKS20EEkN0yWb9Lii6HScjuqd9FSF1WtmOy_-vJz_XFUHxdvl-7vPy8tNA0p8L_9DXqSXhMBN370tR5moWnWYUKqN5iegVZ1-aw1V9XSnL_Sx78oCngMkAZIASaYR8tn8HUgCJAGSAEkygiTmw_x0WwgmD0kI1AnUCdQJ1CcRqMvaf5yseNCQotVErMbaQJIRQxLlHpcNJjmdg0nAJGCSPIJPMMkQPSYKJhEp9rM02l6ISV3JX84dSn69hB90AjoZDzrxPhzcA-4xVO5hn3rE23LS7MeBeyDOmAh3QJwBdYA6QB2gDoOjDogzEGf0voMll-IesgW6MryUZfxi3VQcrEYiPakqhXgfwbCtzHvNWI_uVWljFg1pzEKrjMmwFKj0Cm2A7bvvaM_oVTXoYMgQdAQ6Ah2Bjgwv1rP5O3QEOgIdgY5kREeo79G__oKonKicqJyoPMOoXNb-oybCgwYTJlNLDK-hIyOmI-xZgY_AR-Aj8JEB8pH6npUZe1Y0e1YWmy0rm8oE7FmBmcBMtFtVQB4gjyEgD9kWlZlui4oKeQg3qYA80GKMmzagxYA1wBpgDbCGwbEGtBh6LcZ6uYoYI-ZWFWeUlCo8MvSAbJWuDIhk2b5YNxWHV90Dnrj55CiAR1RyQlIxo2tP3Q2v1KKYco4oJhtCRGkYQaM6_TThVpUtFdk_PfdhrqwH3HUgYjs_11hQCEYyTkbyzWpjxjU6Uv_bPReZlxsNxhlcBC4CF8kj2oSLDNFjPFzkIcaxg4SsbND1oKYSGvZvG7Pb9q87w-LhgwkDF2vGOHbGExTVOBUs2zjGsQQMr2Avo4MxQo2Uo6v9UVMqix7GWvuXOqlBqh4Tqwe_2VmyNtzofsTWoqEU9R5j590NaMh7jRENRcnw94uGvI_lQ0PeG4grgsYYRKU3F9QHbYmGfPeVnhTS-bgreAhZbS7J-sJ71c4yI8G2GMo95830vF3ezBJZL7C2ahzPCLBaU6vGyvLYWzWhlu5Up6VrGeRowpEITe1N_XkDJ0Hj2mCtJBZVLZa9YZ1HPhgAa-0r5ObbKubAlhGCZj2fpAn2sHXzK0X9e93Ta9uDHmfxhIe2p-hKt-p0bAA_gB_AD-AH8AP4Afz5A34AOAAcAC4dPQDgAHAAOAAcAA4AD7JaU6sCwAHgAHC5DQHAO8HLIxWBuToCwA_gB_AD-AH8AP7MPAbArwAMAH47gohM9_3Lt-aDGUfe4LreLfMYaWoxjimLkbIyY1gGY1FLYDz4zWLQ6QtF-JtT-VAyKLbH8mVQciHnFF4V3TzXshqqRJyz8GecxYAuFef2cFODtq4G6m1UMhBkIMhAkIGI9tJkIJDgQ-gh9MPijbaREkIPoe8vCM_ZYyD0CpABoQddg65B16Br0DXoGnTta03QtaBRQdega9A16DraS4Ou44rnx6K_cvUHhB5CD6GH0EPoIfQZekyjWSD0EPoGobe_U6YAtrvSJ5HTEpGWuYbXUx5FGbiE7pfjysJD837d9r0nDg5FZ_G1OMnByxac5uCPzuIEaVaANKG8AUWDYhYNAp-nwOdeaNmM5RPUJ9INb-b5S_C4LeUHE9UUdKMTaAAtlypgUGdGk0_IKp9gvGnbhBpJisyTFK4HzZzed3r2sHPIAOGD8EH4IHwQPiJ7ED4IfygIHwEzIBIQOW4QCdmD7I2Z7AGyAFkWta1pfYdqIaPFG6oF8cKqf9WCsLEshhZh8aRNhMqXSxPJ-bjGclg_rB_WD-uH9cP6c_QYWL8wGGjExDZ-EJn9uNDFyPMLnt2U4eShJblLZebjgXwpzU073j0a5Fl99KuZrr4k0P63O6ce3kcwwA_vNUYG4r1KctTlwNNZ3veiLI2CuqhakzNdg8GMnM94ljUUUEHwTAGVdCmdx4oZDRi4MLHAZsUMIwpcNEngAlkyqBJUmQt4sY2UoEpQZa8YJ1uPAVUKY_ZJy5IBfYA-QJ_3GkAfoA_Q18rQTQ0K6AP0AfrkNgToSwT6-pAqeJeUZn1um7S-qyvgmfBMeCY8E54Jz8zVYwQEYqQ8s7_T95QbxuzLTSN4DYxwhg9eqQdh5WrUg6AeBPUgpIZuatD860G09dIohyey8VfsQTLXyaUkhMxLwuGJt8XMS5uckGi8xO8As7leI-qSB-tH7OB87y5v85RiGNR-_jYHxwHqY4D6JzzbgMQP8b8Pz5ogsdFohy3L5aw_CPZ4eBwEG4INwZZ6zKSX3DEElCySWSSzSNYvksmXkS8jX9apcZMvI1_mvzn5MvJl5MvIlwmbinzZpLYQuEINMDeYG8wN5gZzg7kz9JgxgafoQD5SQVsT9rJ_G5oETRLRpNg5GGU3Gbfk5ZCDiQIfoGwRcQCULa6FQ9niPXz7U4LgRYPmRb0U1spD6uE1oc7V1SpSrgx_DAUlxkHKf4lGytGK5Fv5xPWMQGYgM5AZyAxkBjJn5jETXl53XYqWBTEL4mkuiElfkb4ifUX6ivRVT55D-or0Fekr44KJ9BXpK-TOkOhWJHpeQqIh0ZDoPGJ1SPQQPcZzdIQZ60XneSKQF2l2drGGqDHSuMFU_-F1fDBlfydOi1PEpbIApXMA430sTosLAzC--3JaXManxcnaH_YB-whjH0UW7MPZSRv08M-rq9XN8m71x_XsXgMQpk9qGGIGhgBDjAZDHPtFScJVcZaxU_hoGl8AZVulV5Xxa_vB2hnUI-Um76grdnDA0HCAnVM1I8Wfq8ofLMISYAmwBFgCLAGWEMASPK06d845gSzB247xWcKxgyVkpqOwGEwsbXljoX8oMxEZ5Xh9ff3x849GzFH7qLbx7yWcA84xdc5xuXGPp1EX5BGEPD6ufnpqpLdV9a54Vbytzt6Z-ch9m2-_XyxvVuvrYR-wD9gHUgjwBfgCfAG-AF_kjC8mIoUYFL5ozxE0aonTl5QPgiKMjCLYVsZs2mDTRpchvH-9HXmFLVlTh5acMfyM27diLHs78ptk7iPyotar8RDz11p79eCBtVNBa4zj3helS3ZnyJQqVtIESW0Ot5VVU_GWFGuJdTrB0UlcyFuBwKPOE5KjSm4rFstuHd4F-2IjiFACC2fAnDhutofP0gIXIVG01ToiROi-e4sDdU8wru-xljNiQKCeJGx34gzrN3MJ6QX21laf4Lc315nvcvhm30piafOnA8q3rb97QHklOKC8sh9QXi-J5ak8bX3CfCpthAlBtgiHDS8gHBAOCAeEA8IB4YBwQDjZIRy3THHCNEckPLSAIL8MEawTz7KCsI7vkjZYR1Uy1ixoiMpQdCVjYShhDAVgkRWwQHECrgBXgCvAFeAKz_oFXAGuAFcMAlegOClcihNXQlW5f20KaGJ6-oUMFQCqHssTJqXUCI2UlrQeMoIVTgAc0xPmB3BQnIBwQDggHBAOCAeEA8IB4dinmiEhnLri5BTFiUVxcnavOKlQnIwD6_guSSKpsDEUveJExVCUihMYCoqT4QMLFCfgCnAFuAJcAa7wrF_AFeCKnnBF9mUJ4y70-1CcqBxtWIzCVCo5lxonat9V-uLW22qOqEUTeRZlVcXeSoKoUgBEvre24OtwYNKg9uc8wYQGLXmY13wwwURLjN5IjRMADooTEE7fASkIB4QDwgHhgHBAOCCcvhFOuxonI6Y5dcVJOc-yxglYJ9VZO3GFYR6GQo2TETAUgEVWwALFCbgCXAGuAFeAKzzrF3AFuAJcMQhcgeKkcChOwmqcKM5jHhmamJ5-IUMFgKrH8oRJKTVCI6UlrYeMYIUTAMf0hPkBHBQnIBwQDggHhAPCAeGAcEA49o4YEsKpK05mKE7MipNtjZNyjuJkHFjHd0lnNU5mQYoTFUPRKU5gKChORgAsVIqTs-KwPJqX4ApwBbgCXAGuAFeYccWxNvTSpc2zpRr2yDN6HGZLp5uzRdY8uvmsEJCJMoubKn2riTmSFHVIkusUl3GIpKbpEmN63FN0Nk9H9VV6Kv3qtbWdF39eVT9XZwfF2-X7u8_Ly00DSvww_yNmpJeEAFDfvS0HqahadZi8qo1UKKRVXX5rDdD1xKdXErLvzwLGAzOBmUw9AoSZwExgJjATmEnPzMR8iqBy7wHMhFidWJ1YnVh9QrG6rP1HjY6Hjytaz8161D05ZqLcGLOhJqdzqAnUBGoCNYGaQE0SUhORuh-AYlX9Vy7Vv17uD0mBpIyOpHgfDgwCBhkqBrFPQeI9PKk274BBUG2g2oA_wB88MZLz-eEP8Af4A6oNVBsjZQ05FgeRrdOVgaYs_RfrpuKwNRLzSVdpxPsQpo1o3ovGfZKwSj1TzhvqmdLJK9Q6gzhxtvT2ilNr2uTdffce74HBqlYdDDaCmcBMYCajiwBhJjATmAnMBGbSMzOhOkg--gxidWJ1YnVi9WHE6rL2HzU6Hj6uMNpbWtQ9OWbCTheoCdQEagI1gZpkR03qO11O2emi2-lSzjc7XTZ1DdjpAkmBpLQ99gQMAgYZEgaR7XQ51e10UWEQ4U4XMAiqDVQb8Af4A_wB_gB_yIM_oNroYKeLeWELa-ieNcTNb0ZiDZKyCZKqD53ainFxH6DaqFBt5IcrKHciwRUuv22n2hC5QWxOZJiPhLii7THDalyxhRT7J-c-rD7rQX6dTthOzjXWCZKrNnSNoFJtnE2QmaDagJpATaAmUBOoSXbUpK7amKPaCKpPWp2h2oCkQFLMJMX7cGAQMMhQMYhMtTHXqTZaToJgECUGGTmBQLUBf4A_wB_gD_AHz9SPaiNH6LCn2jDmUZTRJKxhYunKLNJ_9l-H-YyI-aCeARuBjZLWJ4WZZMNMXm40G2cwE5gJzARmAjNJxEzMq7zo6zrRSq7NiW6e6GffxWJ4WpeHdsb3oWBoofGoboDFosYrFltcsRjuaSr-1W-UveAyyhiXKij8K27SNjpL9DCLheCajoqLqDxW6YGLZnhjZuIOpzNz-2h2pI2IFy5aaHJDBeG3GkSrXvPf1xBkL5S9lGeZElVHtVZhSMGCLyZv4IWFiS40Y3IjXDA4n2tXihHnRKLTDpxjzK3IoYeXfeSAc0QPajWIbCCNcmvNPaaZgWnANGAaMA2YBkwDpgHTgGnywjSiPWnTIzb1nWcL18azJusR7z8D3XSMbnxXRBBnymCJD02k2qQCLIkDSyATWZEJ5CNwCbgEXAIuAZeAS8Al4BLD5xLIR7Q70hoX7elHJFn9kTGI6QkTMszvq3osQ2qUSvAzPizSaoAIVSoBaZy2kA2kQT4CpgHTgGnANGAaMA2YBkwzBkxTl49UyEdM8pHqbKMfWSAfGTq68V2SRCZh4yR6FYmCk-g0JHAS5CODJRNUbIVLwCXgEnAJuER0LtF54dYs8EUGZVuNqSFrcjxW1dYRoxFnajZNTlYRZEQvuxA9iykttBBJDdMlm_S4oqjYakf1TrpxukaspCzRWlU_V2e7JVorke9RTFSCH7R2OXwo1UbzE9KqLr9NWKL1ESxsGcgO1qn8WKeyYp2dassWrEN1VvgIfAQ-Ah-Bj8BH4COD4CPrxa8CkDhD1cnyEWJ0YnRidGL0ScTosvYfJyYeNJ9oNRGrifbk-IhyZ8uGkJzOISQQEggJhARCAiFJT0hEOn1giUW_Xzn0-3rhPtQEajIeauJ9OJAHyGOoyMM-9Yg346TZhQPyQJKBJAPgAHAAOAAcAA7ZAQckGcklGeYV7eTpQo7lPGSLc2VoKUv0xbqpOFCNRHlS1QbxPoJhI5n3mrEe1quSxCwaipiFVhCTYfFP6RXa4Np339Geyqtq0MFQITUZ6bROB3gEPAIeAY-AR8Aj4JF9nwKPDAOPGI-9sIdglPQgLCcsJywnLJ9GWC5r_1Ej4eGTCaO9pUXYk8MjbFgBkABIACQAEgBJroCkvmHllA0rqg0r9wdObEoSsGMFaAI00W5UgXnAPIbAPGQbVE51G1RUzEO4RQXmgRoDNQawAdgAbAA2ABuygg2oMdisMnawEDeRGQcsyIv6yUpNJLcQw_ZzvRqjQo2RDZmgIImETLisop0aw0mGUiEhw6wjJBNtT_QdGpkw2puuEVRqDKPCc-R4BDUGgARAAiABkABIcgUkdTXGHDVGQPnQ6gw1BtAEaIIaA-YxUuYhU2PMdWqMlpMdzAM1BmoMYAOwAdgAbAA25A8bUGNQG6P_0qGjzkxmkemz_zqAZ_iAB1UMhAhCROlQ8Ah4ZILBHngEPAIeiYRHzOuR6CsQ0Zoj0lbV7PCIyiGHhkeMx9JbI7By3j0d0Tq11uMeff6s7n-1AUJOR5zxVppAaz_Cin7koQyLxLihNEiLRETSObf3IZquaHYuLxRJUoOkG6fbdbkNc1ehkXLeqN9aisikP7sQ0Wa04bwl8xCrp4LCeZFdUtREC0l6Ol-lK7CQmI9sn37_BN37hXPzReoPbjtDdzsq7fDacg4kAZIASYAkQJK8PQZIAiRJBUnWK2AFJXHGq5OFJATqBOoE6gTqkwjUZe0_TlY8aEjRaiJWY-1pQhLqeoBJwCRgEjAJmCRXTFKv61ElqOsxKmJSr-tRzjd1PRaDKOsBOgGddIlOvA8H94B7DJV7yOp8VLo6HwruIazyAfeYIndAnAF1gDpAHaAOUIc8qQPiDMQZve9gccacqYJNQ5QpW6Arw0tZxi_WTcXBaiTSw3G67Tw1ynG62voeZ9T3kAXYvvtyHu-gyBB0BDoCHRldrAcdgY5AR6Aj0JEc6Aj1PfrXXxCVE5UTlROVZxiVy9p_1ER40GDCZGqJ4fU06Qh7VuAj8BH4CHwEPpIrH6nvWZmxZ0WzZ2Wx2bKyqUzAnhWYCcxEu1UF5AHyGALykG1Rmem2qKiQh3CTCsgDLQZaDFgDrAHWAGuANWTFGtBipNZiuA8ChSsk5wpx05hRuIKo0oGkUEPXw_7uql6txSjnaDGyARNUJBE0qtNP22kxRn36dkww0TgDdmE6ArYJJownwBrQqFiLkfig8GnSEbQY8BH4CHwEPgIfyZWP1LUYc7QYei1GdYYWA2YCM0GLAfIYKfKQaTHmOi1Gy7kO5CFHHlOgDWgxYA2wBlgDrAHWkCdrQIuh12KYsyfWIJKqoZJFuiI6GlZeMotkn_3X4TvD5ztoYgBEAKKUVUOhIz3TkXm5oSMz6Ah0ZDR05NgfugmXaWOgJ4Z4Nl5IJ9PVSxeKsJhILMa8loy8etxfLzqWBq0PbQ9da9rXANbVZVugk4njy4M8d43R_W0wAnOQoBc1TtF6ndbJqifGsnNA6P1a7nFhdr-wsBvoblBYzn-uqkbhytKMZdRBTKroJTg-jxWvdH1CS5ce6HFEs2d5_TFJaZNuvG3X1Sww3-Ffe85VOSmKPG0R0Wa0oMDdCq17KggUiOySWinaVo1THlYAauyExopmuisi4npTiAfEA-IB8cifeDTqMAvT1hCPFsSD-Jz4nPic-Jz4nPic-FzVqsTn_cfnni4afA5nKPyh1WJbnmCKSjx09TbumcfpS5gHzGPqzKNeBaB6B_4Iwh_16gDlfFMeYPHOjEr8ZQHgIHAQOEgCDiILBwAZgAxABiAjZk8BMqYCMgwLzibIsG8vDQIZ9ttNGWQgpAAq5BGgTA0qIKRASDEk_1SHgtFjQEPw55mwW5-h3iaGFEF-y26mDuqC5ABnnPU4krEZ1QGcnQSazfId91RmoRenLPa1KeaiAvLsYYy1tzAOEhXIjFY4QFoxAJIDyYHkQHIgOT6SA-mAdEA6IB2QDopkDNg_icWJxYnFicWJxYnFicWJxYcTi3u6aCx5mqEgB5ONpUoisVUE1pFhLDU11lHfKjJjq0iMrSL3B0luNtmzVQT-Af_omX_IAgIQBggDhAHCAGGAMOTf9GwMmQk3hqgQhm9ryIQRBqoJSEIeUcnUSAKqie73hzRWDGCDvLFBpDxFOxLg3v7vLFTQtQUdeI4qNKsYJGcUEv-r439qaYhraSjSnaGjTg4E0s0UhQBFjyB7Qiay4UfKStSQJBUd0WCRJDxEDELUBCQV-giHfZFgh5hyRN0D0elRoIT0hPTZRQyE9IT0oSH9ek5jJ8QwQnoW1yyuWVyzuA5bXEcciGCLsEXYorOpYIuE9IT0hPSE9IT0hPTjc1B1SBc9ljMEcR5tnTJ68-zAa303fxCYZ5ySydYJSVATe-uE-jgOpac9-vpZ3e9qA4OmdEQ5p3RElvsuuvQ3j9uZHcnnfmn2XXTjYPXDa7T7LvZ8ynJsb2gwnTimDgitU0bY2kDb0arsu5AsMTIs4wjvgHdMO5yCd8A72JWQsYMSkRORE5ETkRORE5ETkRORDyci93TRWLI1gwIPRkNLlE-ioiPEI8OAamrEo17R8ZSKjjEqOpbzTUnHjd6cko5QEChIzxREFhYAMgAZgAxABiADkCH_pqek46mwpKMKZPhKOk4dZKCggCfkEZpMjSegoOA0zCH5Z_foIFLGooMDHszRY7AFaft70ZzmNVuvqwoxQ-Kt19TIcDpTBwUfcqCQbq6YrPJMT9hENvxIeYkalKQiJBo0koSJiGGImoKkwh_BwC-byjMc0kAwTzBPME8wn9N2iAXBfN_BPMtqltUsq1lWD6mgI1QRquhodajiyKkiwTzBPME8wTzBPMH8OPyzj2A-0SpaG8xHWkarg3lltxuFeJpgviKY7yqYZ1ndd7JeMbVmsWEoFlJUBfPKeWPRpImqYJ56J6mDeRAimXmCeYJ5gnmC-WyDeWT2gwnmWUOzhmYNzRpasIaOOOqAEEGIIEQQIpl5gnmCeYJ5gvl8g_n1IpFTB_o7ZdG9AosWphj6y1fuRheY-Kritr2bP77pkJVMOMDpxpvrrtzOj5WHOigUyy0L_KUxOMEjiBidz6BEtdfUgVsH5rW7ohc8VtNC_ObhGO2dGK-9ochvLoB6rbrHf18p4gtlEonRRIChpwQVWl4hiGEBHDkBjm09wFcPK8FHwvGw0q0jjnohwF8Xv6rODIzjfpLczWVUVTTK8c1q00I1vlH_25ZszCAbkI39wOnYQTYMobKebBhu8kQ2TE6qIBuKZWKWkUh4wGkFDWaA4EsBAxrylg0QCg8rFJYHOoTChMKEwoTChMKEwqlCYU-r9pcc8TVgW9WWP51ha7JDR1hkSOTpwyLzaV3bsMhEBpRhke3Fnv3f_-__BQAA__8_L8GV +https://raduberinde.github.io/optsteps.html#eJzsXV1z20Z3vu-v2OpKKgFaAEiJ4juaceo4U3lU2bX89ib2ZBCJrGFTpEtSadxOM5n8Bl_m1-WXvINvENiPs4sFsIBObmIB2OXifOyefc5zFv93dPsf10fzo9uX1y9fvCP_Qn54-_rfiU985_366ubm5Vvy6vXVTXjBJa9viO-MP5PL6H_l-150343ve9X7k-i-F9-fVO9Po_uT-P50_PnIOrrZbB--D5bLo_mRbdvEf3a1DvaBv3q_Ho1G5Of8z-fPie2QkWNNXfL8-fv16Mt282lxt3-_HpG_vv3517ff__r2O7nbrB4f1rs5-Tx3_nn9uFqRYO6S5dwju_mEfJpPyef5LL1xQZZz55Ts5o5DPs0dN2w0TW86Z-Hd8_DuLLx7QT7PXTfr0yPLuTshu7k7JZ_m7ll49yK9652GP-mQ3dxzyae55x0O8vPi65wcO5Z7cXJ4Y3kfXj-x7fePp6fe4ti1pycWOfasyclvvyXXHMu1oquz_LkL23HDS86p5Tj5ozPrwnJcy5nazoXlurZ7ZrkXtudFj07z5s6Z7VxEF88tZ1Zob0etLefMqnYwO7m8PHamluuG7xH3eHl5PMsvuG7hTTzbPYsuTix3WvmJeICW61nFn3DduMfwV-IeL_IevdPkKc-xPJfRYzxiyzu1kh4v8h7dRPjfEuEH6_Via3_aBGty_NHffYxuR_9RzCv0jtTCfGccGpnvjEM7851xaGq-Mw6tzXfGd9v7n38K1vvFdu2vfnr45e7up33wsNjt_Ycv87Pwib3_82qxCe7n55FzpQbqu-PQRn13HJmp744jS_XdcWSsvsvv2vHCR7K-nUnkmZmF-944MnLfG0d27nvjyNR9bxxZu-_xu3dPw0ey7l0ncuzMRfzJOPISfzKOHMWfjCNf8SfjyF38iaD78_CRvPtZNGFkPuZPx5Gb-dNx5Gn-dBw5mz8dh_4W_p_bvTcJH8m696YUZT88rvbBl1VwF-y_zslqsdzb283_7I4Xv_p3-9VXe7NenFhkG_zXx-TG_y62G3uztR822wXNekp-X7pbdf5zuvPH1w_df8J0__Teob-7DsXfU1dP7sp7-Izi4albp3dFPj2l-HTqwsndqheXRMlz5T_Qow3xaLZStHneHwwHdDkPGeyHh05Y2wM5QgB4EDoS2JGEwtNt8Ay7p6q8J-ZfsH3xSxTN92672e3YTYZkxtCXTO2BI8n2rAI6hN2dvyY--dHfXfrOB1GrltUqOZxEAcJ3T_73TUELKmJ11cXaoVVTZSs2bZpsddpt1nGw2i-2O_7j5LBNbKUJCjN3yI-bx_1ie3kcToh3m_Vuv_WD9X53efzMmZPjZzd_v74mNvlwwtYfTd2eUN1VPXezTtLdh7OaUR1Hcu1hdg7TbFmlkYckwNncmWY6jSKxslpnRbX-jTxzpgd6tsjy_pK2MlI0StP8RGwoucpb3gRQdU2N2GlKlg2Iq70KtFtWa6LNy1ROua8mEXvJX6clzbouRbOxOrMQP1bzB8pOt6jUKUf5uTbbQlHoarzgCfx-Lg8HFLpjKa6ssURPl6ksMo2luMahxg4VFGrsgqKxWEsZpBFr78P7dQxq3wYPX1bB8uut_7D4T3_78r8f_VWwDxa7FObmPBAB39bULWDfJMO-qTo2DfumbsFDS-BHmIh9N4p9Ez72TcpTTa93Rv1GyhqftSvKrgtIVDos-X3prqmgg_nYd0WUPFceEtbRb49mK0Wb5_Gwb9ZDBvuhfuybJQSAB6EjSezpBcLTbfA87FvwrKnmf4h9C16Chn0zmgzJjKEvWcS-YS0axb5hQyhj3_xWrWPfUsPJsW9QMzXsW16srrpYO7RqJvYtL1uddluBYmzO4-SwjX7sm_Yrx0nvV7fk-6vbd1c3L97FvMgX392-O446_e6WXN28m52ckNdvDy__6-vX1yfQgf2N7MO164RtXyxsXnbW7mYdZ2PzQBtRw-YZnVcsj_pc2eTawubLg2Fh8wJDaQ2bF4yjgM2L1KGGzZd7FWi3rNZWsfns51nYPEv5rWHzrAEUsHmmwNWw-YpKyoora6xDbP7VJlj_kKUODlD5g1uIxyMej3i82aAD4vGIxyMejx6NeDzi8ehIiMcjHo94POLxiMcjHi-Px3eClMtz5QHDQ8SeNwEgYo-IPSL2iNgTvuLKGusAsX_zuPsYw_FX6_3m1SZYXy-W-xS0Z909wO3PEbdH3B5xe9PACcTtEbdH3B49GnF7xO3RkRC3R9wecXsY_skDNFXPkMlaLVbJJpH3dF9Qfr6kaqH8Uue0NNi10lFBretPZVSSBwc1r02pU27MAfAx8cQ2EJ0Tr4ZCkGbsQursJnK83z4CYiRMB2E6CNNBmA7CdFCb6aC3i4fNL4ubzf7mcbV6sVnfB_tgs07TQay7STroPEoHnWE6CNNBmA4yDfPCdBCmgzAdhB6N6SBMB6EjYToI00GYDtJaxjGoxI5UM53lG4LsC6ixmUKuJ-ta2Re5NB0EXjcOZVcD2zF1w7auBmuGMAci9m3MgWAOBHMgmANpPwfychU8BGt_v7hNQto4-VG5nGQ9zqKsh4NZD8x6YNbDNGgHsx6Y9cCsB3o0Zj0w64GOhFkPzHpg1kNrEUy6RYQ9PcijrkDoulquRKrrsnRBjVsXssqoJGXdeq4kw7Mb_AI3bErADAZmMDCDgRkMzGBgBuPpZjCuF8v9d7vd5i7w94tXm2C9exvu0tJMBvN2ktFwBpjRsDGj0V1GY6RJ-FYuQjUVlDrRqofonZ1I-s5Z-O9koNHl8_j3on-fRv92on9H64UTdx4HCPH0G7eNu6RiagZqmWDeqi8o99DyVnb5rqmAofl5q9EARNnsVOx4UbwVtXXjPuOFZhJdj9q6cZ8TgxRb8SBMSPZhqu44IVmGA3swK-hPSLKEQPGgMgLaS3kNaP7kqEQiH4fzIARNZaQOusgnC4Ziqjce5pMFL0GzX1jWstdmDH3JYj4Z1sKEfLJKDtOw5KUJxyT2ydHNWnCFIlU797PrIyBlD31sxBoB8WKHmenG4wPYu0qHCcUbkmZACw85EwlOD0jEaJ6IIXUKaEbEEASLZSKGbKDYzdaB7bVAIakRMRidwyrEyeHDrREx5CmIHrSqvHVzkBoVeC7XYxxSp93KfX2xY1OBcryQ4oMUH6T4IMVnoBQfZJmYzDLRyyWysresretCV01o3HK9gtLTvxK9W-6kqPr0z0T7ofoKBpD-mdiAlQw6MYPkz6Il0N6xnlF48Us4SVfJqGP7dafJMGOzdc-ScTnIWjId4kHW0pNjLRkETZlDtakolgf89l_5LS9NqVFY7nnRLpI_U9OwEvUm1mFl2q5vIDVWr_N0rLFJz9LBOZosr2JVEJIDLm2dL21sjhGyvJ4mawnCOROwPtCvNbCWhkYrMstRpPJWYkKMKUwYWQqMHraB6WbIm7WfSgzZXADJtgcgY6dzqg6Yo6PXXeCsnCHRcRoPaAUybIbmQ8nxQRiCOAUOZAos73bErs5oMUCPh75rTc-E_Qj4TBUk4CEBr2UCnryAxUcbdcWkUhAxwCn1MKn4vwE79qhjJhWMKcgkabKYVGBAp2VQkh1liQShxqQq9ypgYJatoFUmlUTZxEShbKKbaF06rNajdxhBkfGcUVYg5FQi7w55d1T9PlHeHTKynjojy-YzspogAFrZwDUZXaHDhkzP8k4PrS-9kBug5TklG0yv5GYYqunQEtMruTEmeirYo5WZEUACmgwzeb-CbSZvUzDPZOwFC81tDDBSdasNG59mI3WT3vKRxu7vuflIY5f3vHyk7lli3TTjr3A3MLnbOcbdeAhW4ZEhH9HMtRkpaYpkSBaDBWe3zmc3tlIGSx17KrNGc1MGDOLkEHk6Z_CAqTs1SQg98JN-bEIKXmR5k5IjJVcKvmQlq1TuTukVretwvd1K7mDpK-U-ZmXLbL2VWdOWJbwyyYcbz13eNB_uIaAL5SR1R0YSs5A0eb7E6ZFDoB_0fTPG0o1mIhMFZufyPHFBwAWhhwsCK7tU5xufODeqHonSDOULfuAKkjGHuBUUKBvJmIB3RTImkjGRjCklYCRjPlEypmC9AR5X1xUDT2aq5sVSehh4jM7VvkXaKgNPwBmtcnFZDDxxTqqt9C8bGmC-rBoDzy6rrky0LWu2VQYepL5xKlPf2DJiBEd49GhTwEUtP2CGbpnuGtEv32wf1xGv8nqx3L_YrHYp8ZJyY8CUy0osjhw45MANhgNHBBy4ysqNLJEnkHZRVzYqz5gDFQfMNzRhMcLUVw9SXwAKEDiCQ6NHo--F0VdWAhEfsHsCkOwvC89aFOE1yicZsXthTSS6PgeNoVXnoVWzCUNA1S_SkXB5GszyhHQkpCMhHQnpSEhHatEzkY6EdCSkIyEdCelIEsaBdKRe0JHKI4Ke99UVaUUwDg4OoIe0Uu5V8uOprZJWKj8vIK1EZ4FRWSuFOwe0lSnSVqpJD6StIG3ladJW-rvB7Dvc3i4NRUoVUoKVEJMG-gbmsTFRMKxEgU7yBsYxGMcYGccQ9lbVAR1NrY8wwdwOSlAdIJ_jFdNPMPbq_BhMvcmIylSe7bK1HsNjWiwHLyZDYgfGa32P1yCf0WUsAbgSaP9gTM0Zu_bnaJDYMURih0DZcFcfoMdD37UZz2TnkGENkNhxyf9MtoDYwW_VDbFDakw5sQPUrAaxQ17AnrqAO3V5VafUQ-wAfgid_oE3M4gdEp-s53zMrytih8xUzYul9BA7YB9QZDxXNoBOz5mBHiPTFbEDFjzTcAA9xI6a33I04qQZyrqCID6C-AaC-KwSQeGh44Q1PfV3D9R3_JurIK3HjxPmmsB9DgFiBIj7ChBzDRs6Ww4QOGp8HwIRZBOTG3ujA3kckeMhIccQjUvOAQOcCqReuEGXZQNXEq0QUk4hZQmhlXFlQNNuwGX5geUIM7xtDZhZUeheTaF3Pz_Ucl490DPgh2BH5JoAQkOWrjISrbRudRgGqoVqeoBp3i9IWEnZPFqFqLl7G_6p2XSD6ujobPpgROiEHtiae0Y2zwDKmjexMvHlKngI1v4-qkH8-_p-sX0TcyCvF8t9WqQoeCipV5xG9YqnfaxXtNkVLsjzNwRLwxRBAykCitm7pRto9mj2ULPvoqi2EtY8hbrMSkFLY3WZ6MNPzIf7snSxHMDFGk10APMWMc7eu48VlZUXyjZ8WusoTVtu4ZQ5JIzhjNKvJZVFzxRWzzDj7_4mPPtOGOMqSGulDzUMo8yItIAMCWPlaQ0JYz0gjHENGzpbDpAl0nLhCl2QTUxu0MoYcASIhLHeEsYgGpecAwY4FUi9cIMuy-acSLRCwtgltQaZKzR-ITK1aTeEMfmBsUqSeW211SWDhV4tTpYTevfzQy3n1UMYA_wQrIrRBMIYZOnily5zja-j-mXumIChmh7CGO8XJKykbB4d1jQTtnFUC5vpBtVRdTN9MCJ0Qg9hjFvGzDOAsuaNqHhWVKABID_7nZg8UsTtEbdvtywbXGQ0yH1iy-Ry3VtPQe-caQYxogFjRAJlw119gB4PfddmPJO9pYQ1QCjoklo7CEWB-K26AYCkxsQqFtQP-8gLuFoiaArYoyBigFPqgXj4vyGo8zED2BGsN_wiwM7hHJmpmhdL6QFxGJ3D7KBsAB3W-uWjSJh5wWbNqlP889BgY3wn2uxbJDVZYTVhMjEnjYO5d5o1voA0jqbzpPVy7jlZa-cU0jxaBZLmu7nn5s0dZvOCo0aLR9L809zz8uYdVdOdkpFjTSaDqaZzsZoOcRijcJiqkSYxBxqpKUbaEMerEVPFSjXJSjUXK9XM8zhcFhjmergfRXM1w1z7tkAQ9s78qVWB9S-bLKgCo52Uj9lkXK66yCYruplUGNiMT4pIG1RcGN0Ml1nKMivOu-IX_xqnWAxBhlWb0putxs8aniCP5ERQa1RjPhukS7byWUMQw4uXq0bmCJA5okYZMYorIksS0c8OUaOFtM4HYaymbOfCj4Xix0KlHA8_FoofCzWEnYUfC2WZgzZ2lh5bY6xKPfgSKYiM1pa9l8VI5aEdPkQOrUnEQxM15vLQRI0FPDRRcwEPjf-JUyEPTcSCq9k8k3pss4rsv3gSUmX_xVOXMvsvnvGU2X_xRNk1-28yISPH8mZDYf8l4QLmzRHQVwH0aVy9mUlUvYr-KwKu6L8i8ar-qzqo6r-qlar-W9ITzeyrVl616aoFH9orEuvkiHWHezMj_ANn3N7NuAzjmhnHgsO5t-G5t6Rs5Kz1iUzD56xRYTwk0-BKkK8ELOt4mmQa4Uu2QQThge-YCgOmwtRyYEYlv2SzXvrTXWp5LsMSXNKZrWZSWmpphq6OGGgzNpKyDrA_iojGtHRTo6ERbmQa3ciwdAnkG5lBNAIzjGrM9aTUI-bvCpkkUXPoORIqCTiw2GgJOLjYqAk4uNioCTh42pOagIOnPZWaZ1KPVxrFtGe8MqmmPeP1TDntGS-DymnPePVUS3u-2T6uF7d3_vrFZrVL05yli3Fa05v1N63JAkONyWpi-NBw-KBHz0ytog4xj9hFHrHy0oezW-kmTnHDdQ-teT2c5zBn10LOTvwm_cOl-DKl4byIS_XXWWUwRpxUDZhU1RJIZuGHCqMQ1sAL01D6yx05PXJ8BSFOhDj1Q5xwsfFrDFQwSrDYaBglXGxUjBIuNipGSW2uHaPMqi7epIFjqRoju36AVMbHL9tZtDmiowY9hi9xu9f_yMQWwFoVEOcpwFpMerx2WAtNv0vTLymyjHSIOb1QpEPApKWEvSpIR-WFjGAnN4V08GUqi3SgIxrgiK3vjoGkWbV9KXcXyfL3g10k94O6ol2kqDF3FylqLNhFipqrFLrDd5FgsdF2kWCx0XaRcLFRd5FwscGJMvBdJFhstF0kWGy0XSRcbNRdJFxs1F0knF9E3UXa5SblXeSIE2KMdEQXlE7ywGJUL6YY0cIJ7qj7F0kw5Tc7od7E-MGs-IGioioxV2vUwO6Oajg1YwWKC5e-y6YWIXDaCb7GphoXcFoKv8GmGg1ApEM_skYtBgBJh3FQjerKD5IOg96qymxVJbWq81nVqayqa_uIs6wfWUcvf_2y2mwX3wfL5dH8KEaL3y422_vF9tUmWGeE1sNrCUo8KaDEDEYZYsO4rnaMDbOxMS2wGBcRq0n7oTJ-BhW4cuMPBL6MdzAA5qUZ7oIgXfpALhG-pQptqaJa6oCWOpalDmOpIliq4JU6bqUOWamjVapAlSpGpQ5PqSNT6qBUrzEnHpgWB9n_tgl2-4R28cN283AVhs9heJ1G3JwHKOG3TQu_R8OoMcNAvP9xQq8DcZuekB5UIM6U3-yEehMDcbMcrLhcUteC-k6mJyORn11LsyjtU7sQW3eA2LpCWAGJLmoGGUPee1Fm7RK3QJVWoMooUCcTqPMI1CkEquwBVeKAOmdAnS6gzhRQJQmo8gPUqQHqrAB1QgCfC1B2bMojjDSfpKvSGggSe9LOSWsiTOVJuyP31bnfm4Bn7aQ9j__q_G9JyCToJJ2M--rcc2ZkcnHS_jRScKQaCTXcvOPewoy9BRHsLTTEzXqSRrnJmbG36D1vR7DFcIBbDPhxCuK9k2B_IQHpCBRn61UcuzsqzqI_b1WO0pj7Km5QzK32hWexpKM0fvzLr-SVSVhJRmncV-dW6crkpqSjNP6r8ytw4WkoySiN_-r86lqZjJN0lHb0___0jwAAAP__VSsNvg== # Exploration patterns with varying costs. optsteps diff --git a/pkg/sql/opt/norm/testdata/rules/join b/pkg/sql/opt/norm/testdata/rules/join index 28f72b47c360..22b83f11727b 100644 --- a/pkg/sql/opt/norm/testdata/rules/join +++ b/pkg/sql/opt/norm/testdata/rules/join @@ -1844,21 +1844,20 @@ semi-join-apply norm expect=SimplifyLeftJoin SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k ---- -inner-join (hash) +project ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:8!null i:9 f:10!null s:11 j:12 - ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── key: (8) - ├── fd: (8)-->(9-12), (1)-->(2-5), (1)==(8), (8)==(1) + ├── fd: (8)-->(9-12), (1)==(8), (8)==(1), (2)==(9), (9)==(2), (3)==(10), (10)==(3), (4)==(11), (11)==(4), (5)==(12), (12)==(5) ├── scan a [as=a2] │ ├── columns: a2.k:8!null a2.i:9 a2.f:10!null a2.s:11 a2.j:12 │ ├── key: (8) │ └── fd: (8)-->(9-12) - ├── scan a - │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 - │ ├── key: (1) - │ └── fd: (1)-->(2-5) - └── filters - └── a.k:1 = a2.k:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)] + └── projections + ├── a2.k:8 [as=a.k:1, outer=(8)] + ├── a2.i:9 [as=a.i:2, outer=(9)] + ├── a2.f:10 [as=a.f:3, outer=(10)] + ├── a2.s:11 [as=a.s:4, outer=(11)] + └── a2.j:12 [as=a.j:5, outer=(12)] # Right side has partial rows, so only right-join can be simplified. norm expect=SimplifyRightJoin @@ -1890,22 +1889,20 @@ left-join (hash) norm expect=SimplifyLeftJoin SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.k=a2.k AND a2.f=a.f ---- -inner-join (hash) +project ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:8!null i:9 f:10!null s:11 j:12 - ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── key: (8) - ├── fd: (8)-->(9-12), (1)-->(2-5), (3)==(10), (10)==(3), (1)==(8), (8)==(1) + ├── fd: (8)-->(9-12), (1)==(8), (8)==(1), (2)==(9), (9)==(2), (3)==(10), (10)==(3), (4)==(11), (11)==(4), (5)==(12), (12)==(5) ├── scan a [as=a2] │ ├── columns: a2.k:8!null a2.i:9 a2.f:10!null a2.s:11 a2.j:12 │ ├── key: (8) │ └── fd: (8)-->(9-12) - ├── scan a - │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 - │ ├── key: (1) - │ └── fd: (1)-->(2-5) - └── filters - ├── a2.f:10 = a.f:3 [outer=(3,10), constraints=(/3: (/NULL - ]; /10: (/NULL - ]), fd=(3)==(10), (10)==(3)] - └── a2.k:8 = a.k:1 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)] + └── projections + ├── a2.k:8 [as=a.k:1, outer=(8)] + ├── a2.i:9 [as=a.i:2, outer=(9)] + ├── a2.f:10 [as=a.f:3, outer=(10)] + ├── a2.s:11 [as=a.s:4, outer=(11)] + └── a2.j:12 [as=a.j:5, outer=(12)] # Input contains Project operator. norm expect=SimplifyLeftJoin @@ -1937,23 +1934,22 @@ SELECT * FROM a FULL JOIN (SELECT * FROM a INNER JOIN a AS a2 ON a.k=a2.k) AS a2 inner-join (hash) ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:8!null i:9 f:10!null s:11 j:12 k:15!null i:16 f:17!null s:18 j:19 ├── multiplicity: left-rows(one-or-more), right-rows(one-or-more) - ├── key: (1,15) - ├── fd: (8)-->(9-12), (15)-->(16-19), (8)==(15), (15)==(8), (1)-->(2-5), (3)==(10), (10)==(3) - ├── inner-join (hash) - │ ├── columns: a.k:8!null a.i:9 a.f:10!null a.s:11 a.j:12 a2.k:15!null a2.i:16 a2.f:17!null a2.s:18 a2.j:19 - │ ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) - │ ├── key: (15) - │ ├── fd: (8)-->(9-12), (15)-->(16-19), (8)==(15), (15)==(8) + ├── key: (1,8) + ├── fd: (8)-->(9-12), (8)==(15), (15)==(8), (9)==(16), (16)==(9), (10)==(3,17), (17)==(3,10), (11)==(18), (18)==(11), (12)==(19), (19)==(12), (1)-->(2-5), (3)==(10,17) + ├── project + │ ├── columns: a2.k:15!null a2.i:16 a2.f:17!null a2.s:18 a2.j:19 a.k:8!null a.i:9 a.f:10!null a.s:11 a.j:12 + │ ├── key: (8) + │ ├── fd: (8)-->(9-12), (8)==(15), (15)==(8), (9)==(16), (16)==(9), (10)==(17), (17)==(10), (11)==(18), (18)==(11), (12)==(19), (19)==(12) │ ├── scan a │ │ ├── columns: a.k:8!null a.i:9 a.f:10!null a.s:11 a.j:12 │ │ ├── key: (8) │ │ └── fd: (8)-->(9-12) - │ ├── scan a [as=a2] - │ │ ├── columns: a2.k:15!null a2.i:16 a2.f:17!null a2.s:18 a2.j:19 - │ │ ├── key: (15) - │ │ └── fd: (15)-->(16-19) - │ └── filters - │ └── a.k:8 = a2.k:15 [outer=(8,15), constraints=(/8: (/NULL - ]; /15: (/NULL - ]), fd=(8)==(15), (15)==(8)] + │ └── projections + │ ├── a.k:8 [as=a2.k:15, outer=(8)] + │ ├── a.i:9 [as=a2.i:16, outer=(9)] + │ ├── a.f:10 [as=a2.f:17, outer=(10)] + │ ├── a.s:11 [as=a2.s:18, outer=(11)] + │ └── a.j:12 [as=a2.j:19, outer=(12)] ├── scan a │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 │ ├── key: (1) @@ -3845,41 +3841,39 @@ inner-join (cross) norm expect=RemoveJoinNotNullCondition SELECT * FROM a LEFT JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f ---- -inner-join (hash) +project ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:8!null i:9 f:10!null s:11 j:12 - ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) - ├── key: (8) - ├── fd: (1)-->(2-5), (8)-->(9-12), (1)==(8), (8)==(1) + ├── key: (1) + ├── fd: (1)-->(2-5), (1)==(8), (8)==(1), (2)==(9), (9)==(2), (3)==(10), (10)==(3), (4)==(11), (11)==(4), (5)==(12), (12)==(5) ├── scan a │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 │ ├── key: (1) │ └── fd: (1)-->(2-5) - ├── scan a [as=a2] - │ ├── columns: a2.k:8!null a2.i:9 a2.f:10!null a2.s:11 a2.j:12 - │ ├── key: (8) - │ └── fd: (8)-->(9-12) - └── filters - └── a.k:1 = a2.k:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)] + └── projections + ├── a.k:1 [as=a2.k:8, outer=(1)] + ├── a.i:2 [as=a2.i:9, outer=(2)] + ├── a.f:3 [as=a2.f:10, outer=(3)] + ├── a.s:4 [as=a2.s:11, outer=(4)] + └── a.j:5 [as=a2.j:12, outer=(5)] # Full join case. norm expect=RemoveJoinNotNullCondition SELECT * FROM a FULL JOIN a AS a2 ON a.k=a2.k AND a.f=a.f AND a2.f=a2.f ---- -inner-join (hash) +project ├── columns: k:1!null i:2 f:3!null s:4 j:5 k:8!null i:9 f:10!null s:11 j:12 - ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) ├── key: (8) - ├── fd: (8)-->(9-12), (1)-->(2-5), (1)==(8), (8)==(1) + ├── fd: (8)-->(9-12), (1)==(8), (8)==(1), (2)==(9), (9)==(2), (3)==(10), (10)==(3), (4)==(11), (11)==(4), (5)==(12), (12)==(5) ├── scan a [as=a2] │ ├── columns: a2.k:8!null a2.i:9 a2.f:10!null a2.s:11 a2.j:12 │ ├── key: (8) │ └── fd: (8)-->(9-12) - ├── scan a - │ ├── columns: a.k:1!null a.i:2 a.f:3!null a.s:4 a.j:5 - │ ├── key: (1) - │ └── fd: (1)-->(2-5) - └── filters - └── a.k:1 = a2.k:8 [outer=(1,8), constraints=(/1: (/NULL - ]; /8: (/NULL - ]), fd=(1)==(8), (8)==(1)] + └── projections + ├── a2.k:8 [as=a.k:1, outer=(8)] + ├── a2.i:9 [as=a.i:2, outer=(9)] + ├── a2.f:10 [as=a.f:3, outer=(10)] + ├── a2.s:11 [as=a.s:4, outer=(11)] + └── a2.j:12 [as=a.j:5, outer=(12)] # No-op case because i is nullable. norm expect-not=RemoveJoinNotNullCondition diff --git a/pkg/sql/opt/norm/testdata/rules/project b/pkg/sql/opt/norm/testdata/rules/project index 166298d40ac9..eec07141c019 100644 --- a/pkg/sql/opt/norm/testdata/rules/project +++ b/pkg/sql/opt/norm/testdata/rules/project @@ -270,15 +270,7 @@ project # EliminateJoinUnderProjectRight # -------------------------------------------------- -# InnerJoin case with self join. -norm expect=EliminateJoinUnderProjectRight -SELECT b1.x, b1.z FROM b INNER JOIN b AS b1 ON b.x = b1.x ----- -scan b [as=b1] - ├── columns: x:6!null z:7 - ├── key: (6) - └── fd: (6)-->(7) - +# No self-join case because EliminateJoinUnderProjectLeft can always match. # InnerJoin case with not-null foreign key. norm expect=EliminateJoinUnderProjectRight SELECT k, v FROM a INNER JOIN fks ON r1 = x @@ -290,41 +282,41 @@ scan fks # The left column can be remapped to a right column. norm expect=EliminateJoinUnderProjectRight -SELECT b.x, b1.j FROM b INNER JOIN b AS b1 ON b.x = b1.x +SELECT x, k, v FROM a INNER JOIN fks ON r1 = x ---- project - ├── columns: x:1!null j:8 - ├── key: (1) - ├── fd: (1)-->(8) - ├── scan b [as=b1] - │ ├── columns: b1.x:6!null b1.j:8 - │ ├── key: (6) - │ └── fd: (6)-->(8) + ├── columns: x:1!null k:7!null v:8 + ├── key: (7) + ├── fd: (7)-->(1,8) + ├── scan fks + │ ├── columns: k:7!null v:8 r1:10!null + │ ├── key: (7) + │ └── fd: (7)-->(8,10) └── projections - └── b1.x:6 [as=b.x:1, outer=(6)] + └── r1:10 [as=x:1, outer=(10)] -# No-op case because columns from the right side of a LeftJoin are being -# projected. +# No-op case because the left input of a LeftJoin cannot be removed. norm expect-not=EliminateJoinUnderProjectRight -SELECT b.j, b1.j FROM b LEFT JOIN b AS b1 ON b.x = b1.x +SELECT x, k, v FROM a LEFT JOIN fks ON r1 = x ---- project - ├── columns: j:3 j:8 - └── inner-join (hash) - ├── columns: b.x:1!null b.j:3 b1.x:6!null b1.j:8 - ├── multiplicity: left-rows(exactly-one), right-rows(exactly-one) - ├── key: (6) - ├── fd: (1)-->(3), (6)-->(8), (1)==(6), (6)==(1) - ├── scan b - │ ├── columns: b.x:1!null b.j:3 - │ ├── key: (1) - │ └── fd: (1)-->(3) - ├── scan b [as=b1] - │ ├── columns: b1.x:6!null b1.j:8 - │ ├── key: (6) - │ └── fd: (6)-->(8) + ├── columns: x:1!null k:7 v:8 + ├── key: (1,7) + ├── fd: (7)-->(8) + └── left-join (hash) + ├── columns: x:1!null k:7 v:8 r1:10 + ├── multiplicity: left-rows(one-or-more), right-rows(exactly-one) + ├── key: (1,7) + ├── fd: (7)-->(8,10) + ├── scan a + │ ├── columns: x:1!null + │ └── key: (1) + ├── scan fks + │ ├── columns: k:7!null v:8 r1:10!null + │ ├── key: (7) + │ └── fd: (7)-->(8,10) └── filters - └── b.x:1 = b1.x:6 [outer=(1,6), constraints=(/1: (/NULL - ]; /6: (/NULL - ]), fd=(1)==(6), (6)==(1)] + └── r1:10 = x:1 [outer=(1,10), constraints=(/1: (/NULL - ]; /10: (/NULL - ]), fd=(1)==(10), (10)==(1)] # -------------------------------------------------- # EliminateProject diff --git a/pkg/sql/opt/norm/testdata/rules/prune_cols b/pkg/sql/opt/norm/testdata/rules/prune_cols index d9a3ccd17487..bdd0da683698 100644 --- a/pkg/sql/opt/norm/testdata/rules/prune_cols +++ b/pkg/sql/opt/norm/testdata/rules/prune_cols @@ -5313,7 +5313,7 @@ CREATE TABLE c100478 ( # A join which doesn't prune columns which could potentially appear in derived # ON clause conditions should not result in infinite rule recursion. -norm expect=(EliminateGroupByProject,PruneJoinLeftCols,PruneJoinRightCols) disable=(EliminateJoinUnderProjectRight,EliminateJoinUnderGroupByRight) +norm expect=(EliminateGroupByProject,PruneJoinLeftCols,PruneJoinRightCols) disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight,EliminateJoinUnderGroupByLeft,EliminateJoinUnderGroupByRight) SELECT p.id FROM p100478 p JOIN c100478 ON p_id = p.id GROUP BY p.id ---- distinct-on diff --git a/pkg/sql/opt/optbuilder/testdata/update_from b/pkg/sql/opt/optbuilder/testdata/update_from index 47171435775a..d19d31c4d5f6 100644 --- a/pkg/sql/opt/optbuilder/testdata/update_from +++ b/pkg/sql/opt/optbuilder/testdata/update_from @@ -23,17 +23,16 @@ update abc │ └── c_new:17 => abc.c:3 └── project ├── columns: b_new:16 c_new:17 abc.a:6!null abc.b:7 abc.c:8 other.a:11!null other.b:12 other.c:13 other.crdb_internal_mvcc_timestamp:14 other.tableoid:15 - ├── inner-join (merge) - │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 other.a:11!null other.b:12 other.c:13 other.crdb_internal_mvcc_timestamp:14 other.tableoid:15 - │ ├── left ordering: +6 - │ ├── right ordering: +11 + ├── project + │ ├── columns: other.a:11!null other.b:12 other.c:13 other.crdb_internal_mvcc_timestamp:14 other.tableoid:15 abc.a:6!null abc.b:7 abc.c:8 │ ├── scan abc - │ │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 - │ │ └── ordering: +6 - │ ├── scan abc [as=other] - │ │ ├── columns: other.a:11!null other.b:12 other.c:13 other.crdb_internal_mvcc_timestamp:14 other.tableoid:15 - │ │ └── ordering: +11 - │ └── filters (true) + │ │ └── columns: abc.a:6!null abc.b:7 abc.c:8 abc.crdb_internal_mvcc_timestamp:9 abc.tableoid:10 + │ └── projections + │ ├── abc.a:6 [as=other.a:11] + │ ├── abc.b:7 [as=other.b:12] + │ ├── abc.c:8 [as=other.c:13] + │ ├── abc.crdb_internal_mvcc_timestamp:9 [as=other.crdb_internal_mvcc_timestamp:14] + │ └── abc.tableoid:10 [as=other.tableoid:15] └── projections ├── other.b:12 + 1 [as=b_new:16] └── other.c:13 + 1 [as=c_new:17] @@ -103,17 +102,13 @@ update abc │ └── c_new:17 => abc.c:3 └── project ├── columns: b_new:16 c_new:17 abc.a:6!null abc.b:7 abc.c:8 old.b:12 old.c:13 - ├── inner-join (merge) - │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 old.a:11!null old.b:12 old.c:13 - │ ├── left ordering: +6 - │ ├── right ordering: +11 + ├── project + │ ├── columns: old.b:12 old.c:13 abc.a:6!null abc.b:7 abc.c:8 │ ├── scan abc - │ │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 - │ │ └── ordering: +6 - │ ├── scan abc [as=old] - │ │ ├── columns: old.a:11!null old.b:12 old.c:13 - │ │ └── ordering: +11 - │ └── filters (true) + │ │ └── columns: abc.a:6!null abc.b:7 abc.c:8 + │ └── projections + │ ├── abc.b:7 [as=old.b:12] + │ └── abc.c:8 [as=old.c:13] └── projections ├── old.b:12 + 1 [as=b_new:16] └── old.c:13 + 2 [as=c_new:17] @@ -135,17 +130,14 @@ update abc │ └── c_new:17 => abc.c:3 └── project ├── columns: b_new:16 c_new:17 abc.a:6!null abc.b:7 abc.c:8 old.a:11!null old.b:12 old.c:13 - ├── inner-join (merge) - │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 old.a:11!null old.b:12 old.c:13 - │ ├── left ordering: +6 - │ ├── right ordering: +11 + ├── project + │ ├── columns: old.a:11!null old.b:12 old.c:13 abc.a:6!null abc.b:7 abc.c:8 │ ├── scan abc - │ │ ├── columns: abc.a:6!null abc.b:7 abc.c:8 - │ │ └── ordering: +6 - │ ├── scan abc [as=old] - │ │ ├── columns: old.a:11!null old.b:12 old.c:13 - │ │ └── ordering: +11 - │ └── filters (true) + │ │ └── columns: abc.a:6!null abc.b:7 abc.c:8 + │ └── projections + │ ├── abc.a:6 [as=old.a:11] + │ ├── abc.b:7 [as=old.b:12] + │ └── abc.c:8 [as=old.c:13] └── projections ├── old.b:12 + 1 [as=b_new:16] └── old.c:13 + 2 [as=c_new:17] diff --git a/pkg/sql/opt/ordering/lookup_join_test.go b/pkg/sql/opt/ordering/lookup_join_test.go index c4e6386aa225..15ad50c2d152 100644 --- a/pkg/sql/opt/ordering/lookup_join_test.go +++ b/pkg/sql/opt/ordering/lookup_join_test.go @@ -26,6 +26,7 @@ import ( "github.com/cockroachdb/cockroach/pkg/sql/opt/testutils/testexpr" "github.com/cockroachdb/cockroach/pkg/sql/sem/eval" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/cockroach/pkg/sql/types" ) func TestLookupJoinProvided(t *testing.T) { @@ -46,6 +47,9 @@ func TestLookupJoinProvided(t *testing.T) { md := f.Metadata() tn := tree.NewUnqualifiedTableName("t") tab := md.AddTable(tc.Table(tn), tn) + for i := 0; i < 4; i++ { + md.AddColumn(fmt.Sprintf("input_col%d", i), types.Int) + } if c1 := tab.ColumnID(0); c1 != 1 { t.Fatalf("unexpected ID for column c1: %d\n", c1) @@ -153,10 +157,10 @@ func TestLookupJoinProvided(t *testing.T) { for tcIdx, tc := range testCases { t.Run(fmt.Sprintf("case%d", tcIdx+1), func(t *testing.T) { inputFDs := props.FuncDepSet{} - inputFDs.AddStrictKey(tc.inputKey, c(5, 6)) + inputFDs.AddStrictKey(tc.inputKey, c(5, 6, 7, 8)) input := &testexpr.Instance{ Rel: &props.Relational{ - OutputCols: c(5, 6), + OutputCols: c(5, 6, 7, 8), FuncDeps: inputFDs, }, Provided: &physical.Provided{ diff --git a/pkg/sql/opt/xform/testdata/external/hibernate b/pkg/sql/opt/xform/testdata/external/hibernate index 133ed327f9e8..e8cd7980eff5 100644 --- a/pkg/sql/opt/xform/testdata/external/hibernate +++ b/pkg/sql/opt/xform/testdata/external/hibernate @@ -56,28 +56,21 @@ where phoneregis0_.phone_id=1; project ├── columns: phone_id1_2_0_:1!null person_i2_2_0_:2!null formula159_0_:17 id1_1_1_:5!null number2_1_1_:6 since3_1_1_:7 type4_1_1_:8 ├── key: (5) - ├── fd: ()-->(1), (5)-->(6-8), (2)==(5), (5)==(2), (2)-->(17) - ├── inner-join (lookup phone [as=a10]) - │ ├── columns: phone_id:1!null person_id:2!null unidirecti1_.id:5!null unidirecti1_.number:6 unidirecti1_.since:7 unidirecti1_.type:8 a10.id:11!null a10.since:13 - │ ├── key columns: [2] = [11] + ├── fd: ()-->(1), (5)-->(6-8), (2)==(5), (5)==(2), (7)==(17), (17)==(7) + ├── inner-join (lookup phone [as=unidirecti1_]) + │ ├── columns: phone_id:1!null person_id:2!null unidirecti1_.id:5!null unidirecti1_.number:6 unidirecti1_.since:7 unidirecti1_.type:8 + │ ├── key columns: [2] = [5] │ ├── lookup columns are key - │ ├── key: (11) - │ ├── fd: ()-->(1), (5)-->(6-8), (2)==(5,11), (5)==(2,11), (11)-->(13), (11)==(2,5) - │ ├── inner-join (lookup phone [as=unidirecti1_]) - │ │ ├── columns: phone_id:1!null person_id:2!null unidirecti1_.id:5!null unidirecti1_.number:6 unidirecti1_.since:7 unidirecti1_.type:8 - │ │ ├── key columns: [2] = [5] - │ │ ├── lookup columns are key - │ │ ├── key: (5) - │ │ ├── fd: ()-->(1), (5)-->(6-8), (2)==(5), (5)==(2) - │ │ ├── scan phone_register [as=phoneregis0_] - │ │ │ ├── columns: phone_id:1!null person_id:2!null - │ │ │ ├── constraint: /1/2: [/1 - /1] - │ │ │ ├── key: (2) - │ │ │ └── fd: ()-->(1) - │ │ └── filters (true) + │ ├── key: (5) + │ ├── fd: ()-->(1), (5)-->(6-8), (2)==(5), (5)==(2) + │ ├── scan phone_register [as=phoneregis0_] + │ │ ├── columns: phone_id:1!null person_id:2!null + │ │ ├── constraint: /1/2: [/1 - /1] + │ │ ├── key: (2) + │ │ └── fd: ()-->(1) │ └── filters (true) └── projections - └── a10.since:13 [as=formula159_0_:17, outer=(13)] + └── unidirecti1_.since:7 [as=formula159_0_:17, outer=(7)] exec-ddl drop table phone_register, Person, Phone; diff --git a/pkg/sql/opt/xform/testdata/physprops/ordering b/pkg/sql/opt/xform/testdata/physprops/ordering index 87eb2b50c845..fe58aed7d0c3 100644 --- a/pkg/sql/opt/xform/testdata/physprops/ordering +++ b/pkg/sql/opt/xform/testdata/physprops/ordering @@ -2989,7 +2989,7 @@ CREATE TABLE table87806 ( ); ---- -opt format=hide-all +opt format=hide-all disable=(EliminateJoinUnderProjectLeft,EliminateJoinUnderProjectRight) SELECT tab_171969.col1_3 FROM table87806 AS tab_171967 JOIN table87806 AS tab_171968 @@ -3016,7 +3016,8 @@ project │ │ │ └── filters │ │ │ └── tab_171969.col1_3 = tab_171969.tableoid │ │ └── filters - │ │ └── tab_171968.col1_3 = tab_171969.col1_3 + │ │ ├── tab_171968.col1_3 = tab_171969.col1_3 + │ │ └── tab_171968.tableoid = tab_171969.col1_3 │ └── projections │ └── true └── filters (true) diff --git a/pkg/sql/opt/xform/testdata/rules/join_order b/pkg/sql/opt/xform/testdata/rules/join_order index b10bbce9ba0b..45913f964797 100644 --- a/pkg/sql/opt/xform/testdata/rules/join_order +++ b/pkg/sql/opt/xform/testdata/rules/join_order @@ -1964,27 +1964,63 @@ full-join (hash) └── filters └── y = z +exec-ddl +CREATE TABLE abc2 ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT +) +---- + +exec-ddl +CREATE TABLE abc3 ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT +) +---- + +exec-ddl +CREATE TABLE abc4 ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT +) +---- + +exec-ddl +CREATE TABLE abc5 ( + a INT PRIMARY KEY, + b INT, + c INT, + d INT +) +---- + # Iteratively reorder subtrees of up to size 2. reorderjoins set=reorder_joins_limit=2 format=hide-all SELECT * FROM abc AS a1 -INNER JOIN abc AS a2 ON a1.a = a2.a -LEFT JOIN abc AS a3 ON a2.b = a3.b -INNER JOIN abc AS a4 ON a3.a = a4.a -WHERE EXISTS (SELECT * FROM abc AS a5 WHERE a2.c = a5.c) +INNER JOIN abc2 AS a2 ON a1.a = a2.a +LEFT JOIN abc3 AS a3 ON a2.b = a3.b +INNER JOIN abc4 AS a4 ON a3.a = a4.a +WHERE EXISTS (SELECT * FROM abc5 AS a5 WHERE a2.c = a5.c) ---- -------------------------------------------------------------------------------- Join Tree #1 -------------------------------------------------------------------------------- semi-join (hash) - ├── scan abc [as=a2] - ├── scan abc [as=a5] + ├── scan abc2 [as=a2] + ├── scan abc5 [as=a5] └── filters └── a2.c = a5.c Vertexes A: - scan abc [as=a2] + scan abc2 [as=a2] B: - scan abc [as=a5] + scan abc5 [as=a5] Edges a2.c = a5.c [semi, ses=AB, tes=AB, rules=()] Joining AB @@ -1994,17 +2030,17 @@ Joins Considered: 1 Join Tree #2 -------------------------------------------------------------------------------- inner-join (hash) - ├── scan abc [as=a2] + ├── scan abc2 [as=a2] ├── distinct-on - │ └── scan abc [as=a5] + │ └── scan abc5 [as=a5] └── filters └── a2.c = a5.c Vertexes A: - scan abc [as=a2] + scan abc2 [as=a2] C: distinct-on - └── scan abc [as=a5] + └── scan abc5 [as=a5] Edges a2.c = a5.c [inner, ses=AC, tes=AC, rules=()] Joining AC @@ -2017,8 +2053,8 @@ Join Tree #3 inner-join (hash) ├── scan abc [as=a1] ├── semi-join (hash) - │ ├── scan abc [as=a2] - │ ├── scan abc [as=a5] + │ ├── scan abc2 [as=a2] + │ ├── scan abc5 [as=a5] │ └── filters │ └── a2.c = a5.c └── filters @@ -2027,9 +2063,9 @@ Vertexes D: scan abc [as=a1] A: - scan abc [as=a2] + scan abc2 [as=a2] B: - scan abc [as=a5] + scan abc5 [as=a5] Edges a2.c = a5.c [semi, ses=AB, tes=AB, rules=()] a1.a = a2.a [inner, ses=DA, tes=DA, rules=()] @@ -2050,13 +2086,13 @@ Join Tree #4 ├── inner-join (hash) │ ├── scan abc [as=a1] │ ├── semi-join (hash) - │ │ ├── scan abc [as=a2] - │ │ ├── scan abc [as=a5] + │ │ ├── scan abc2 [as=a2] + │ │ ├── scan abc5 [as=a5] │ │ └── filters │ │ └── a2.c = a5.c │ └── filters │ └── a1.a = a2.a - ├── scan abc [as=a3] + ├── scan abc3 [as=a3] └── filters └── a2.b = a3.b Vertexes @@ -2064,12 +2100,12 @@ Vertexes scan abc [as=a1] E: semi-join (hash) - ├── scan abc [as=a2] - ├── scan abc [as=a5] + ├── scan abc2 [as=a2] + ├── scan abc5 [as=a5] └── filters └── a2.c = a5.c F: - scan abc [as=a3] + scan abc3 [as=a3] Edges a1.a = a2.a [inner, ses=DE, tes=DE, rules=()] a2.b = a3.b [inner, ses=EF, tes=EF, rules=()] @@ -2093,16 +2129,16 @@ Join Tree #5 │ ├── inner-join (hash) │ │ ├── scan abc [as=a1] │ │ ├── semi-join (hash) - │ │ │ ├── scan abc [as=a2] - │ │ │ ├── scan abc [as=a5] + │ │ │ ├── scan abc2 [as=a2] + │ │ │ ├── scan abc5 [as=a5] │ │ │ └── filters │ │ │ └── a2.c = a5.c │ │ └── filters │ │ └── a1.a = a2.a - │ ├── scan abc [as=a3] + │ ├── scan abc3 [as=a3] │ └── filters │ └── a2.b = a3.b - ├── scan abc [as=a4] + ├── scan abc4 [as=a4] └── filters └── a3.a = a4.a Vertexes @@ -2110,16 +2146,16 @@ Vertexes inner-join (hash) ├── scan abc [as=a1] ├── semi-join (hash) - │ ├── scan abc [as=a2] - │ ├── scan abc [as=a5] + │ ├── scan abc2 [as=a2] + │ ├── scan abc5 [as=a5] │ └── filters │ └── a2.c = a5.c └── filters └── a1.a = a2.a F: - scan abc [as=a3] + scan abc3 [as=a3] H: - scan abc [as=a4] + scan abc4 [as=a4] Edges a2.b = a3.b [inner, ses=GF, tes=GF, rules=()] a3.a = a4.a [inner, ses=FH, tes=FH, rules=()] @@ -2143,15 +2179,15 @@ inner-join (hash) │ └── inner-join (hash) │ ├── inner-join (merge) │ │ ├── scan abc [as=a1] - │ │ ├── scan abc [as=a2] + │ │ ├── scan abc2 [as=a2] │ │ └── filters (true) │ ├── distinct-on - │ │ └── scan abc [as=a5] + │ │ └── scan abc5 [as=a5] │ └── filters │ └── a2.c = a5.c ├── inner-join (merge) - │ ├── scan abc [as=a3] - │ ├── scan abc [as=a4] + │ ├── scan abc3 [as=a3] + │ ├── scan abc4 [as=a4] │ └── filters (true) └── filters └── a2.b = a3.b @@ -2923,7 +2959,7 @@ inner-join (lookup t88659) │ ├── lookup columns are key │ ├── immutable │ ├── key: (1) - │ ├── fd: (1)-->(2,3), (7)-->(9), (7)==(2,8,19), (8)==(2,7,19), (19)-->(20,21), (19)==(2,7,8), (2)==(7,8,19), (3)==(9), (9)==(3) + │ ├── fd: (1)-->(2,3), (7)-->(9), (7)==(2,8,19,20), (8)==(2,7,19,20), (19)-->(21), (19)==(2,7,8,20), (20)==(2,7,8,19), (9)==(3,21), (21)==(3,9), (2)==(7,8,19,20), (3)==(9,21) │ ├── inner-join (lookup t88659) │ │ ├── columns: a:1!null b:2!null c:3!null a:7!null b:8!null c:9!null │ │ ├── key columns: [1] = [1]