Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qr4sp-002Rzp-46 for pgsql-hackers@arkaria.postgresql.org; Thu, 12 Oct 2023 23:14:23 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1qr4sm-00Bi50-QI for pgsql-hackers@arkaria.postgresql.org; Thu, 12 Oct 2023 23:14:21 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qr4sm-00Bi4r-CB for pgsql-hackers@lists.postgresql.org; Thu, 12 Oct 2023 23:14:21 +0000 Received: from forward501a.mail.yandex.net ([178.154.239.81]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1qr4si-000FXF-Nd for pgsql-hackers@postgresql.org; Thu, 12 Oct 2023 23:14:20 +0000 Received: from mail-nwsmtp-smtp-production-main-49.vla.yp-c.yandex.net (mail-nwsmtp-smtp-production-main-49.vla.yp-c.yandex.net [IPv6:2a02:6b8:c18:4601:0:640:7543:0]) by forward501a.mail.yandex.net (Yandex) with ESMTP id EFCCF62232; Fri, 13 Oct 2023 02:14:15 +0300 (MSK) Received: by mail-nwsmtp-smtp-production-main-49.vla.yp-c.yandex.net (smtp/Yandex) with ESMTPSA id DEp8na1UriE0-05NisbOr; Fri, 13 Oct 2023 02:14:15 +0300 X-Yandex-Fwd: 1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex.ru; s=mail; t=1697152455; bh=vGJ8bAeKJgkEkBvYQ1VWG31vvIvJtPvcs149gvtSGiQ=; h=In-Reply-To:Cc:Date:References:To:Subject:Message-ID:From; b=Pvv31YUb4T3EMk9120x2gL70GeKDYqUFDF25B5WP1ve/Dm3NYrO8N94IgvJpDiEEp n+yRGdnXGGZt/I+aoc5U+xDgdYme1PNpPe/vJ5nSXSqgYbwK+JdC6oVCw+fVP4AO81 OiQnzCmPFRRIBVEFDa3rLNbo3bVE9lKFCqK84qEI= Authentication-Results: mail-nwsmtp-smtp-production-main-49.vla.yp-c.yandex.net; dkim=pass header.i=@yandex.ru Content-Type: multipart/mixed; boundary="------------t1XliTeHWhQSjGPnistK3PF2" Message-ID: <602d6b44-c734-4f7f-8999-99b6cac9bf17@yandex.ru> Date: Fri, 13 Oct 2023 02:14:13 +0300 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: A new strategy for pull-up correlated ANY_SUBLINK To: Andy Fan Cc: pgsql-hackers , Tom Lane , vignesh C , Richard Guo , Andrey Lepikhov References: <247851.1668293143@sss.pgh.pa.us> Content-Language: en-US From: Alena Rybakina In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --------------t1XliTeHWhQSjGPnistK3PF2 Content-Type: multipart/alternative; boundary="------------Jm71UTDZb4OpveIcIDljXk6Y" --------------Jm71UTDZb4OpveIcIDljXk6Y Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit On 12.10.2023 10:52, Andy Fan wrote: > > Unfortunately, I found a request when sublink did not pull-up, as > in the > > examples above. I couldn't quite figure out why. > > I'm not sure what you mean with the "above", I guess it should be the > "below"? Yes, you are right) > > explain (analyze, costs off, buffers) > select b.x, b.x, a.y > from b >     left join a >         on b.x=a.x and > *b.t in >             (select max(a0.t) * >              from a a0 >              where a0.x = b.x and >                    a0.t = b.t); > > ... > >    SubPlan 2 > > > Here the sublink can't be pulled up because of its reference to > the  LHS of left join, the original logic is that no matter the 'b.t > in ..' > returns the true or false,  the rows in LHS will be returned.  If we > pull it up to LHS, some rows in LHS will be filtered out, which > breaks its original semantics. Thanks for the explanation, it became more clear to me here. > I thought it would be: > > explain (analyze, costs off, buffers) > select b.x, b.x, a.y > from b >     left join a on >         b.x=a.x and > *b.t = >             (select max(a0.t) * >              from a a0 >              where a0.x = b.x and >                    a0.t <= b.t); > > QUERY PLAN > --------------------------------------------------------------------------------------------------------------------- >  Hash Right Join (actual time=1.181..67.927 rows=1000 loops=1) >    Hash Cond: (a.x = b.x) > *Join Filter: (b.t = (SubPlan 2))* >    Buffers: shared hit=3546 >    ->  Seq Scan on a (actual time=0.022..17.109 rows=100000 loops=1) >          Buffers: shared hit=541 >    ->  Hash (actual time=1.065..1.068 rows=1000 loops=1) >          Buckets: 4096  Batches: 1  Memory Usage: 72kB >          Buffers: shared hit=5 >          ->  Seq Scan on b (actual time=0.049..0.401 rows=1000 > loops=1) >                Buffers: shared hit=5 >    SubPlan 2 >      ->  Result (actual time=0.025..0.025 rows=1 loops=1000) >            Buffers: shared hit=3000 >            InitPlan 1 (returns $2) >              ->  Limit (actual time=0.024..0.024 rows=1 loops=1000) >                    Buffers: shared hit=3000 >                    ->  Index Only Scan Backward using a_t_x_idx on > a a0 (actual time=0.023..0.023 rows=1 loops=1000) >                          Index Cond: ((t IS NOT NULL) AND (t <= > b.t) AND (x = b.x)) >                          Heap Fetches: 1000 >                          Buffers: shared hit=3000 >  Planning Time: 0.689 ms >  Execution Time: 68.220 ms > (23 rows) > > If you noticed, it became possible after replacing the "in" > operator with "=". > > I didn't notice much difference between the 'in'  and '=',  maybe I > missed something? It seems to me that the expressions "=" and "IN" are equivalent here due to the fact that the aggregated subquery returns only one value, and the result with the "IN" operation can be considered as the intersection of elements on the left and right. In this query, we have some kind of set on the left, among which there will be found or not only one element on the right. In general, this expression can be considered as b=const, so push down will be applied to b and we can filter b during its scanning by the subquery's result. But I think your explanation is necessary here, that this is all possible, because we can pull up the sublink here, since filtering is allowed on the right side (the nullable side) and does not break the semantics of LHS. But in contrast, I also added two queries where pull-up is impossible and it is not done here. Otherwise if filtering was applied on the left it would be mistake. To be honest, I'm not sure if this explanation is needed in the test anymore, so I didn't add it. explain (costs off) SELECT * FROM tenk1 A LEFT JOIN tenk2 B ON A.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd);                            QUERY PLAN -----------------------------------------------------------------  Nested Loop Left Join    Join Filter: (SubPlan 2)    ->  Seq Scan on tenk1 a    ->  Materialize          ->  Seq Scan on tenk2 b    SubPlan 2      ->  Result            InitPlan 1 (returns $1)              ->  Limit                    ->  Index Scan using tenk2_hundred on tenk2 c                          Index Cond: (hundred IS NOT NULL)                          Filter: (odd = b.odd) (12 rows) explain (costs off) SELECT * FROM tenk1 A LEFT JOIN tenk2 B ON A.hundred in (SELECT count(c.hundred) FROM tenk2 C group by (c.odd));             QUERY PLAN -----------------------------------  Nested Loop Left Join    Join Filter: (hashed SubPlan 1)    ->  Seq Scan on tenk1 a    ->  Materialize          ->  Seq Scan on tenk2 b    SubPlan 1      ->  HashAggregate            Group Key: c.odd            ->  Seq Scan on tenk2 c (9 rows) > I took the liberty of adding this to your patch and added myself > as reviewer, if you don't mind. > > Sure, the patch after your modification looks better than the original. > I'm not sure how the test case around "because of got one row" is > relevant to the current changes.  After we reach to some agreement > on the above discussion, I think v4 is good for committer to review! Thank you!) I am ready to discuss it. -- Regards, Alena Rybakina --------------Jm71UTDZb4OpveIcIDljXk6Y Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: 8bit
On 12.10.2023 10:52, Andy Fan wrote:

Unfortunately, I found a request when sublink did not pull-up, as in the 

examples above. I couldn't quite figure out why.

I'm not sure what you mean with the "above", I guess it should be the "below"?

Yes, you are right)


 

explain (analyze, costs off, buffers)
select b.x, b.x, a.y
from b
    left join a
        on b.x=a.x and
           b.t in
            (select max(a0.t)

             from a a0
             where a0.x = b.x and
                   a0.t = b.t);

... 
   SubPlan 2

Here the sublink can't be pulled up because of its reference to 
the  LHS of left join, the original logic is that no matter the 'b.t in ..' 
returns the true or false,  the rows in LHS will be returned.  If we
pull it up to LHS, some rows in LHS will be filtered out, which 
breaks its original semantics.

Thanks for the explanation, it became more clear to me here.


I thought it would be:

explain (analyze, costs off, buffers)
select b.x, b.x, a.y
from b
    left join a on
        b.x=a.x and
        b.t =
            (select max(a0.t)

             from a a0
             where a0.x = b.x and
                   a0.t <= b.t);

                                                     QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------
 Hash Right Join (actual time=1.181..67.927 rows=1000 loops=1)
   Hash Cond: (a.x = b.x)
   Join Filter: (b.t = (SubPlan 2))
   Buffers: shared hit=3546
   ->  Seq Scan on a (actual time=0.022..17.109 rows=100000 loops=1)
         Buffers: shared hit=541
   ->  Hash (actual time=1.065..1.068 rows=1000 loops=1)
         Buckets: 4096  Batches: 1  Memory Usage: 72kB
         Buffers: shared hit=5
         ->  Seq Scan on b (actual time=0.049..0.401 rows=1000 loops=1)
               Buffers: shared hit=5
   SubPlan 2
     ->  Result (actual time=0.025..0.025 rows=1 loops=1000)
           Buffers: shared hit=3000
           InitPlan 1 (returns $2)
             ->  Limit (actual time=0.024..0.024 rows=1 loops=1000)
                   Buffers: shared hit=3000
                   ->  Index Only Scan Backward using a_t_x_idx on a a0 (actual time=0.023..0.023 rows=1 loops=1000)
                         Index Cond: ((t IS NOT NULL) AND (t <= b.t) AND (x = b.x))
                         Heap Fetches: 1000
                         Buffers: shared hit=3000
 Planning Time: 0.689 ms
 Execution Time: 68.220 ms
(23 rows)

If you noticed, it became possible after replacing the "in" operator with "=".

I didn't notice much difference between the 'in'  and '=',  maybe I 
missed something? 

It seems to me that the expressions "=" and "IN" are equivalent here due to the fact that the aggregated subquery returns only one value, and the result with the "IN" operation can be considered as the intersection of elements on the left and right. In this query, we have some kind of set on the left, among which there will be found or not only one element on the right. In general, this expression can be considered as b=const, so push down will be applied to b and we can filter b during its scanning by the subquery's result.
But I think your explanation is necessary here, that this is all possible, because we can pull up the sublink here, since filtering is allowed on the right side (the nullable side) and does not break the semantics of LHS. But in contrast, I also added two queries where pull-up is impossible and it is not done here. Otherwise if filtering was applied on the left it would be mistake.

To be honest, I'm not sure if this explanation is needed in the test anymore, so I didn't add it.

explain (costs off)
SELECT * FROM tenk1 A LEFT JOIN tenk2 B
ON A.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd);
                           QUERY PLAN                            
-----------------------------------------------------------------
 Nested Loop Left Join
   Join Filter: (SubPlan 2)
   ->  Seq Scan on tenk1 a
   ->  Materialize
         ->  Seq Scan on tenk2 b
   SubPlan 2
     ->  Result
           InitPlan 1 (returns $1)
             ->  Limit
                   ->  Index Scan using tenk2_hundred on tenk2 c
                         Index Cond: (hundred IS NOT NULL)
                         Filter: (odd = b.odd)
(12 rows)

explain (costs off)
SELECT * FROM tenk1 A LEFT JOIN tenk2 B
ON A.hundred in (SELECT count(c.hundred) FROM tenk2 C group by (c.odd));
            QUERY PLAN             
-----------------------------------
 Nested Loop Left Join
   Join Filter: (hashed SubPlan 1)
   ->  Seq Scan on tenk1 a
   ->  Materialize
         ->  Seq Scan on tenk2 b
   SubPlan 1
     ->  HashAggregate
           Group Key: c.odd
           ->  Seq Scan on tenk2 c
(9 rows)


I took the liberty of adding this to your patch and added myself as reviewer, if you don't mind.

Sure, the patch after your modification looks better than the original. 
I'm not sure how the test case around "because of got one row" is
relevant to the current changes.  After we reach to some agreement
on the above discussion, I think v4 is good for committer to review!

Thank you!) I am ready to discuss it.
-- 
Regards,
Alena Rybakina
--------------Jm71UTDZb4OpveIcIDljXk6Y-- --------------t1XliTeHWhQSjGPnistK3PF2 Content-Type: text/x-patch; charset=UTF-8; name="pull-up.diff" Content-Disposition: attachment; filename="pull-up.diff" Content-Transfer-Encoding: base64 ZGlmZiAtLWdpdCBhL3NyYy90ZXN0L3JlZ3Jlc3MvZXhwZWN0ZWQvc3Vic2VsZWN0Lm91dCBi L3NyYy90ZXN0L3JlZ3Jlc3MvZXhwZWN0ZWQvc3Vic2VsZWN0Lm91dAppbmRleCBiNzBiMzQ2 Njk2ZC4uMGI4ZjI4YjE1N2YgMTAwNjQ0Ci0tLSBhL3NyYy90ZXN0L3JlZ3Jlc3MvZXhwZWN0 ZWQvc3Vic2VsZWN0Lm91dAorKysgYi9zcmMvdGVzdC9yZWdyZXNzL2V4cGVjdGVkL3N1YnNl bGVjdC5vdXQKQEAgLTIwMjgsNyArMjAyOCw3IEBAIE9OIEEuaHVuZHJlZCBpbiAoU0VMRUNU IGMuaHVuZHJlZCBGUk9NIHRlbmsyIEMgV0hFUkUgYy5vZGQgPSBiLm9kZCk7CiAgICAgICAg ICAtPiAgU2VxIFNjYW4gb24gdGVuazIgYgogKDExIHJvd3MpCiAKLS0tIHdlIGNhbiBwdWxs IHVwIHRoZSBhZ2dyZWdhdGUgc3VibGluayBpbnRvIHRoZSBzdWJxdWVyeSBzY2FuIGJlY2F1 c2Ugb2YgZ290IG9uZSByb3cuCistLSB3ZSBjYW4gcHVsbCB1cCB0aGUgYWdncmVnYXRlIHN1 YmxpbmsgaW50byBSSFMgb2YgYSBsZWZ0IGpvaW4uCiBleHBsYWluIChjb3N0cyBvZmYpCiBT RUxFQ1QgKiBGUk9NIHRlbmsxIEEgTEVGVCBKT0lOIHRlbmsyIEIKIE9OIEIuaHVuZHJlZCBp biAoU0VMRUNUIG1pbihjLmh1bmRyZWQpIEZST00gdGVuazIgQyBXSEVSRSBjLm9kZCA9IGIu b2RkKTsKQEAgLTIwNTIsNyArMjA1Miw2IEBAIE9OIEIuaHVuZHJlZCBpbiAoU0VMRUNUIG1p bihjLmh1bmRyZWQpIEZST00gdGVuazIgQyBXSEVSRSBjLm9kZCA9IGIub2RkKTsKICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRmlsdGVyOiAob2Rk ID0gYi5vZGQpCiAoMTYgcm93cykKIAotLS0gd2UgY2FuIHB1bGwgdXAgdGhlIGFnZ3JlZ2F0 ZSBzdWJsaW5rIGludG8gdGhlIEpvaW5FeHByLgogZXhwbGFpbiAoY29zdHMgb2ZmKQogU0VM RUNUICogRlJPTSB0ZW5rMSBBIExFRlQgSk9JTiB0ZW5rMiBCCiBPTiBCLmh1bmRyZWQgaW4g KFNFTEVDVCBjb3VudChjLmh1bmRyZWQpIEZST00gdGVuazIgQyBncm91cCBieSAoYy5vZGQp KTsKQEAgLTIwNzEsMyArMjA3MCwzOSBAQCBPTiBCLmh1bmRyZWQgaW4gKFNFTEVDVCBjb3Vu dChjLmh1bmRyZWQpIEZST00gdGVuazIgQyBncm91cCBieSAoYy5vZGQpKTsKICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgIC0+ICBTZXEgU2NhbiBvbiB0ZW5rMiBjCiAoMTEg cm93cykKIAorLS0gd2UgY2FuJ3QgcHVsbCB1cCB0aGUgYWdncmVnYXRlIHN1YmxpbmsgaW50 byBMSFMgb2YgYSBsZWZ0IGpvaW4uCitleHBsYWluIChjb3N0cyBvZmYpCitTRUxFQ1QgKiBG Uk9NIHRlbmsxIEEgTEVGVCBKT0lOIHRlbmsyIEIKK09OIEEuaHVuZHJlZCBpbiAoU0VMRUNU IG1pbihjLmh1bmRyZWQpIEZST00gdGVuazIgQyBXSEVSRSBjLm9kZCA9IGIub2RkKTsKKyAg ICAgICAgICAgICAgICAgICAgICAgICAgIFFVRVJZIFBMQU4gICAgICAgICAgICAgICAgICAg ICAgICAgICAgCistLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0tLQorIE5lc3RlZCBMb29wIExlZnQgSm9pbgorICAgSm9p biBGaWx0ZXI6IChTdWJQbGFuIDIpCisgICAtPiAgU2VxIFNjYW4gb24gdGVuazEgYQorICAg LT4gIE1hdGVyaWFsaXplCisgICAgICAgICAtPiAgU2VxIFNjYW4gb24gdGVuazIgYgorICAg U3ViUGxhbiAyCisgICAgIC0+ICBSZXN1bHQKKyAgICAgICAgICAgSW5pdFBsYW4gMSAocmV0 dXJucyAkMSkKKyAgICAgICAgICAgICAtPiAgTGltaXQKKyAgICAgICAgICAgICAgICAgICAt PiAgSW5kZXggU2NhbiB1c2luZyB0ZW5rMl9odW5kcmVkIG9uIHRlbmsyIGMKKyAgICAgICAg ICAgICAgICAgICAgICAgICBJbmRleCBDb25kOiAoaHVuZHJlZCBJUyBOT1QgTlVMTCkKKyAg ICAgICAgICAgICAgICAgICAgICAgICBGaWx0ZXI6IChvZGQgPSBiLm9kZCkKKygxMiByb3dz KQorCitleHBsYWluIChjb3N0cyBvZmYpCitTRUxFQ1QgKiBGUk9NIHRlbmsxIEEgTEVGVCBK T0lOIHRlbmsyIEIKK09OIEEuaHVuZHJlZCBpbiAoU0VMRUNUIGNvdW50KGMuaHVuZHJlZCkg RlJPTSB0ZW5rMiBDIGdyb3VwIGJ5IChjLm9kZCkpOworICAgICAgICAgICAgUVVFUlkgUExB TiAgICAgICAgICAgICAKKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCisg TmVzdGVkIExvb3AgTGVmdCBKb2luCisgICBKb2luIEZpbHRlcjogKGhhc2hlZCBTdWJQbGFu IDEpCisgICAtPiAgU2VxIFNjYW4gb24gdGVuazEgYQorICAgLT4gIE1hdGVyaWFsaXplCisg ICAgICAgICAtPiAgU2VxIFNjYW4gb24gdGVuazIgYgorICAgU3ViUGxhbiAxCisgICAgIC0+ ICBIYXNoQWdncmVnYXRlCisgICAgICAgICAgIEdyb3VwIEtleTogYy5vZGQKKyAgICAgICAg ICAgLT4gIFNlcSBTY2FuIG9uIHRlbmsyIGMKKyg5IHJvd3MpCisKZGlmZiAtLWdpdCBhL3Ny Yy90ZXN0L3JlZ3Jlc3Mvc3FsL3N1YnNlbGVjdC5zcWwgYi9zcmMvdGVzdC9yZWdyZXNzL3Nx bC9zdWJzZWxlY3Quc3FsCmluZGV4IDVkMzNlYjM5YmFhLi5jMDI1MTNjNDk4OSAxMDA2NDQK LS0tIGEvc3JjL3Rlc3QvcmVncmVzcy9zcWwvc3Vic2VsZWN0LnNxbAorKysgYi9zcmMvdGVz dC9yZWdyZXNzL3NxbC9zdWJzZWxlY3Quc3FsCkBAIC0xMDAxLDEyICsxMDAxLDIwIEBAIGV4 cGxhaW4gKGNvc3RzIG9mZikKIFNFTEVDVCAqIEZST00gdGVuazEgQSBJTk5FUiBKT0lOIHRl bmsyIEIKIE9OIEEuaHVuZHJlZCBpbiAoU0VMRUNUIGMuaHVuZHJlZCBGUk9NIHRlbmsyIEMg V0hFUkUgYy5vZGQgPSBiLm9kZCk7CiAKLS0tIHdlIGNhbiBwdWxsIHVwIHRoZSBhZ2dyZWdh dGUgc3VibGluayBpbnRvIHRoZSBzdWJxdWVyeSBzY2FuIGJlY2F1c2Ugb2YgZ290IG9uZSBy b3cuCistLSB3ZSBjYW4gcHVsbCB1cCB0aGUgYWdncmVnYXRlIHN1YmxpbmsgaW50byBSSFMg b2YgYSBsZWZ0IGpvaW4uCiBleHBsYWluIChjb3N0cyBvZmYpCiBTRUxFQ1QgKiBGUk9NIHRl bmsxIEEgTEVGVCBKT0lOIHRlbmsyIEIKIE9OIEIuaHVuZHJlZCBpbiAoU0VMRUNUIG1pbihj Lmh1bmRyZWQpIEZST00gdGVuazIgQyBXSEVSRSBjLm9kZCA9IGIub2RkKTsKIAotLS0gd2Ug Y2FuIHB1bGwgdXAgdGhlIGFnZ3JlZ2F0ZSBzdWJsaW5rIGludG8gdGhlIEpvaW5FeHByLgog ZXhwbGFpbiAoY29zdHMgb2ZmKQogU0VMRUNUICogRlJPTSB0ZW5rMSBBIExFRlQgSk9JTiB0 ZW5rMiBCCi1PTiBCLmh1bmRyZWQgaW4gKFNFTEVDVCBjb3VudChjLmh1bmRyZWQpIEZST00g dGVuazIgQyBncm91cCBieSAoYy5vZGQpKTsKXCBObyBuZXdsaW5lIGF0IGVuZCBvZiBmaWxl CitPTiBCLmh1bmRyZWQgaW4gKFNFTEVDVCBjb3VudChjLmh1bmRyZWQpIEZST00gdGVuazIg QyBncm91cCBieSAoYy5vZGQpKTsKKworLS0gd2UgY2FuJ3QgcHVsbCB1cCB0aGUgYWdncmVn YXRlIHN1YmxpbmsgaW50byBMSFMgb2YgYSBsZWZ0IGpvaW4uCitleHBsYWluIChjb3N0cyBv ZmYpCitTRUxFQ1QgKiBGUk9NIHRlbmsxIEEgTEVGVCBKT0lOIHRlbmsyIEIKK09OIEEuaHVu ZHJlZCBpbiAoU0VMRUNUIG1pbihjLmh1bmRyZWQpIEZST00gdGVuazIgQyBXSEVSRSBjLm9k ZCA9IGIub2RkKTsKKworZXhwbGFpbiAoY29zdHMgb2ZmKQorU0VMRUNUICogRlJPTSB0ZW5r MSBBIExFRlQgSk9JTiB0ZW5rMiBCCitPTiBBLmh1bmRyZWQgaW4gKFNFTEVDVCBjb3VudChj Lmh1bmRyZWQpIEZST00gdGVuazIgQyBncm91cCBieSAoYy5vZGQpKTsKXCBObyBuZXdsaW5l IGF0IGVuZCBvZiBmaWxlCg== --------------t1XliTeHWhQSjGPnistK3PF2--