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 1uLxfH-003rri-Cv for pgsql-hackers@arkaria.postgresql.org; Mon, 02 Jun 2025 05:24:51 +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 1uLxfG-007XA3-56 for pgsql-hackers@arkaria.postgresql.org; Mon, 02 Jun 2025 05:24:50 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1uLxfF-007X9q-En for pgsql-hackers@lists.postgresql.org; Mon, 02 Jun 2025 05:24:49 +0000 Received: from mail-pf1-x444.google.com ([2607:f8b0:4864:20::444]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1uLxfC-0018g7-3C for pgsql-hackers@lists.postgresql.org; Mon, 02 Jun 2025 05:24:48 +0000 Received: by mail-pf1-x444.google.com with SMTP id d2e1a72fcca58-742c7a52e97so3150217b3a.3 for ; Sun, 01 Jun 2025 22:24:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=illuminatedcomputing-com.20230601.gappssmtp.com; s=20230601; t=1748841885; x=1749446685; darn=lists.postgresql.org; h=cc:to:subject:from:content-language:user-agent:mime-version:date :message-id:from:to:cc:subject:date:message-id:reply-to; bh=9xYMc+zHhJG1fjf21mxefcIJHcPLX16RhkDSqVbV+14=; b=Iy8kGaHZv50gqosOXF/H9wm3tlsTKIYLChxIG+Ee/vf+ieinQJk39AfXhYvVCZA739 Z2GvTDRiaFVDt9og5T1otnKEWVd42S1tGhbuZ32JqaC9x9kIoDFB3ofngCft1jjTslYk Yz5ZtYxXS3Rqe4tzftPDg7TM2TB6gKINhkNe3jq4C0DrjmDHnz+cNw5Ow9K+LxJNPvS7 FlWtit+Qd/XN7P85/KxsJJSHdxIH/lX5UEM285NPBrF5IZtg7mMcN04J23BrB2EBAlnP gC7c+sfEmY3gK8skDPFlXPBzuUN32VKzj5W2C12DnHCGwIiogvdZCqQfogkr24/JHFDH Cz/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1748841885; x=1749446685; h=cc:to:subject:from:content-language:user-agent:mime-version:date :message-id:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=9xYMc+zHhJG1fjf21mxefcIJHcPLX16RhkDSqVbV+14=; b=WpzHj/5+oYaYjJcMKFm283lvWJ0ggdxn7cYSWBreA+0qBYjBaSQaLLKbJyImtNFseu u+jvXRfFMlR/+z9l6WVQfDghlcawibRnMfze3G67QLHMetXkLOrxvMbB7IAEbZAAivlx MtR2/9i2bNMMCWNv88NQsgreQ7UlGZgmGbRB8F488tqI+b0ZNXL/k0kQOpIvFoCtMsjh E7u2+GRIg0mntMG1uXgfzThFVBo7jN0/zpVzWIyh4hRWbbFSYUi8BFY7rbTVV/Jzg3qv zZBq9wCdyIaFvygP8GD3tw2NbFmntsL8iRQB59MTbaZ+sQgR49d++QWtVuv6KJluM8+b 9RVA== X-Gm-Message-State: AOJu0Yx2oUVL0fewsBOyNGlOveLuArU51bKLQT5jG3GFEoMQZW/nbJWs PCmRPIIf4rvMpZPfc5eKqZEgyMxnWosIOFFefPDiP8gAfb08JVaU+aY6dCF6Frgz6du40XBohFQ 4/YGxbZ1v6A== X-Gm-Gg: ASbGncvsuj14y5KGudLltwV5AOYOYQsHtNi2cfzoxY3u62Tvb+vhXxPAwE7FZAuC81J lCP+2618pheyal8JjtDxotn0KLhSkMgcMi87/asGCX1r1TJEVKPzJe4CFwAx3rJBEgy4vt357vF Z/3x8h+aUSfV5Xf2vC6uI6IvzHUFflNmvGGesO3v9K9FETWTTeYlryv8aZ1sSBMuf4fQvl7lWwA HKxwODk8wn3Ro1oF70145fM8jXGtf0GEmYzcuDo1JZgGmw0Xi/hJbmn2XMtLfr2xSO0pCZZlYQB EE2DOYsw2Yb6BtOokmJYbmNAtEp5VgO6QYkv9XIl2Nb2iA8phrNo0i+N9dSQ+qTb X-Google-Smtp-Source: AGHT+IHFF9lLfvqWQBCHYEG+7Ku07xpoFoIcelTQNws0b98p9AhW3PvgWebc1NWUj+ZqHRLTLxpbMw== X-Received: by 2002:a05:6a00:244b:b0:736:5753:12fd with SMTP id d2e1a72fcca58-747c1a1f44emr16288054b3a.4.1748841885206; Sun, 01 Jun 2025 22:24:45 -0700 (PDT) Received: from [192.168.2.139] ([50.39.255.79]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-747afeabad9sm7003265b3a.51.2025.06.01.22.24.44 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Sun, 01 Jun 2025 22:24:44 -0700 (PDT) Content-Type: multipart/mixed; boundary="------------Wy9z306B1utELZsM00vyO2mg" Message-ID: Date: Sun, 1 Jun 2025 22:24:44 -0700 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Content-Language: en-US From: Paul Jungwirth Subject: SQL:2011 Application Time Update & Delete To: PostgreSQL Hackers Cc: Tom Lane , Robert Haas , Peter Eisentraut 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. --------------Wy9z306B1utELZsM00vyO2mg Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Hi Hackers, Here is a new thread for the next part of SQL:2011 Application Time: UPDATE and DELETE commands with FOR PORTION OF. This continues the long-running thread that ended with [1]. I don't have a new patch set yet, but I wanted to summarize the discussion at the PGConf.dev Advanced Patch Feedback session, especially to continue the conversation about triggers fired from inserting "temporal leftovers" as part of an UPDATE/DELETE FOR PORTION OF. In my last patch series, I fire all statement & row triggers when the inserts happen for temporal leftovers. So let's assume there is a row with valid_at of [2000-01-01,2020-01-01) and the user's query is UPDATE t FOR PORTION OF valid_at FROM '2010-01-01' TO '2011-01-01'. So it changes one row, targeting only 2010. There are two temporal leftovers: one for 2000-2009 and one for 2011-2019 (inclusive). Then these triggers fire in the order given: BEFORE UPDATE STATEMENT BEFORE UPDATE ROW BEFORE INSERT STATEMENT -- for the 2000-2009 leftovers BEFORE INSERT ROW AFTER INSERT ROW AFTER INSERT STATEMENT BEFORE INSERT STATEMENT -- for the 2011-2019 leftovers BEFORE INSERT ROW AFTER INSERT ROW AFTER INSERT STATEMENT AFTER UPDATE ROW AFTER UPDATE STATEMENT I think this is the correct behavior (as I'll get to below), but at the session none of us seemed completely sure. What we all agreed on is that we shouldn't implement it with SPI. Before I switched to SPI, I feared that getting INSERT STATEMENT triggers to fire was going to cause a lot of code duplication. But I took my last pre-SPI patch (v39 from 7 Aug 2024), restored its implementation for ExecForPortionOfLeftovers, and got the desired behavior with just these lines (executed once per temporal leftover): AfterTriggerBeginQuery() ExecSetupTransitionCaptureState(mtstate, estate); fireBSTriggers(mtstate); ExecInsert(context, resultRelInfo, leftoverSlot, node->canSetTag, NULL, NULL); fireASTriggers(mtstate); AfterTriggerEndQuery(estate); You'll be able to see all that with my next patch set, but for now I'm just saying: replacing SPI was easier than I thought. There were different opinions about whether this behavior is correct. Robert and Tom both thought that firing INSERT STATEMENT triggers was weird. (Please correct me if I misrepresent anything you said!) Robert pointed out that if you are using statement triggers for performance reasons (since that may be the only reason to prefer them to row triggers), you might be annoyed to find that your INSERT STATEMENT triggers fire up to two times every time you update a *row*. Robert also warned that some people implement replication with statement triggers (though maybe not people running v18), and they might not like INSERT STATEMENT triggers firing when there was no user-issued insert statement. This is especially true since C-based triggers have access to the FOR PORTION OF details, as do PL/pgSQL triggers (in a follow-on patch), so they don't need to hear about the implicit inserts. Also trigger-based auditing will see insert statements that were never explicitly sent by a user. (OTOH this is also true for inserts made from triggers, and (as we'll see below) several other commands fire statement triggers for implicit actions.) Robert & Tom agreed that if we leave out the statement triggers, then the NEW transition table for the overall UPDATE STATEMENT trigger should include all three rows: the updated version of the old row and the (up to) two temporal leftovers. A philosophical argument I can see for omitting INSERT STATEMENT is that the temporal leftovers only preserve the history that was already there. They don't add to what is asserted by the table. But reporting them as statements feels a bit like treating them as user assertions. (I'm not saying I find this argument very strong, but I can see how someone would make it.) Tom & Robert thought that firing the INSERT *ROW* triggers made sense and was valuable for some use-cases, e.g. auditing. Robert also thought that nesting was weird. He thought that the order should be this (and even better if omitting the INSERT STATEMENTs): BEFORE UPDATE STATEMENT BEFORE UPDATE ROW AFTER UPDATE ROW AFTER UPDATE STATEMENT BEFORE INSERT STATEMENT -- for the 2000-2009 leftovers BEFORE INSERT ROW AFTER INSERT ROW AFTER INSERT STATEMENT BEFORE INSERT STATEMENT -- for the 2011-2019 leftovers BEFORE INSERT ROW AFTER INSERT ROW AFTER INSERT STATEMENT But I think that the behavior I have is correct. My draft copy of the 2011 standard says this about inserting temporal leftovers (15.13, General Rules 10.c.ii): > The following is effectively executed without further Access Rule > and constraint checking: > INSERT INTO TN VALUES (VL1, ..., VLd) When I compared IBM DB2 and MariaDB, I found that DB2 does this: AFTER INSERT ROW -- for the 2000-2009 leftovers AFTER INSERT STATEMENT AFTER INSERT ROW -- for the 2011-2019 leftovers AFTER INSERT STATEMENT AFTER UPDATE ROW AFTER UPDATE STATEMENT (I didn't quickly find a way to observe BEFORE triggers firing, so they aren't show here. I was misremembering when I said at the session that it doesn't support BEFORE triggers. It does, but they can't do certain things, like insert into an auditing table.) And MariaDB (which doesn't have statement triggers) does this: BEFORE UPDATE ROW BEFORE INSERT ROW -- for the 2000-2009 leftovers AFTER INSERT ROW BEFORE INSERT ROW -- for the 2011-2019 leftovers AFTER INSERT ROW AFTER UPDATE ROW So both of those match the behavior I've implemented (including the nesting). Peter later looked up the current text of the standard, and he found several parts that confirm the existing behavior. (Thank you for checking that for me Peter!) To paraphrase a note from him: Paper SQL-026R2, which originally created this feature, says: > All UPDATE triggers defined on the table will get activated in the usual way for all rows that are > updated. In addition, all INSERT triggers will get activated for all rows that are inserted. He also found the same text I quoted above (now in section 15.14). He also brought up this other passage from SQL-026R2: > Currently it is not possible > for the body of an UPDATE trigger to gain access to the FROM and TO values in the FOR PORTION OF > clause if one is specified. The syntax of will need to be extended to allow > such access. We are not proposing to enhance the syntax of in this proposal. > We leave it as a future Language Opportunity. Since the standard still hasn't added that, firing at least INSERT ROW triggers is necessary if you want trigger-based replication. (I don't think this speaks strongly to INSERT STATEMENT triggers though.) Incidentally, note that my patches *do* include this information (as noted above): both in the TriggerData struct passed to C triggers, and (in a separate patch) via PL/pgSQL variables. I don't include it for SQL-language triggers, and perhaps those should wait to see what the standard recommends. In a world where we *do* fire statement triggers, I think each statement should get its own transition table contents. Robert also said that we should choose behavior that is consistent with other features in Postgres. I've attached a script to demonstrate a few interesting comparisons. It tests: - INSERT ON CONFLICT DO NOTHING (without then with a conflict) - INSERT ON CONFLICT DO UPDATE (without then with a conflict) - INSERT ON CONFLICT DO UPDATE WHERE (with a conflict) - MERGE DO NOTHING (without then with a conflict) - MERGE UPDATE (without then with a conflict) - cross-partition UPDATE - ON DELETE CASCADE - ON DELETE SET NULL ON CONFLICT DO NOTHING and MERGE DO NOTHING do not fire an UPDATE STATEMENT trigger (naturally). Cross-partition update does not fire extra statement triggers. Everything else does fire extra statement triggers. I think this is what I would have guessed if I hadn't tested it first. It feels like the natural choice for each feature. Note that commands have to "decide" a priori which statement triggers they'll fire, before they process rows. So ON CONFLICT DO UPDATE fires first BEFORE INSERT STATEMENT, then BEFORE UPDATE STATEMENT, then row triggers, and finally AFTER UPDATE STATEMENT and AFTER INSERT STATEMENT. MERGE UPDATE is the same. It fires BEFORE INSERT STATEMENT, then BEFORE UPDATE STATEMENT, then row triggers, and finally AFTER UPDATE STATEMENT and AFTER INSERT STATEMENT. And the referential integrity actions fire statement triggers (as expected, since they are implemented with SPI). In all cases we see nesting. With cross-partition update, the DELETE & INSERT triggers are nested inside the before/after UPDATE trigger (although interestingly the AFTER DELETE/INSERT triggers don't quite follow a nesting-like order with respect to each other): BEFORE UPDATE STATEMENT BEFORE UPDATE ROW BEFORE DELETE ROW BEFORE INSERT ROW AFTER DELETE ROW AFTER INSERT ROW AFTER UPDATE STATEMENT That covers all my research. My conclusion is that we *should* fire INSERT STATEMENT triggers, and they should be nested within the BEFORE & AFTER UPDATE triggers. I'm pleased that achieving that without SPI is not as hard as I expected. Please stay tuned for some actual patches! [1] https://www.postgresql.org/message-id/CA%2BrenyUZuWOxvY1Lv9O3F1LdpKc442EYvViR1DVzbD9ztaa6Yg%40mail.gmail.com Yours, -- Paul ~{:-) pj@illuminatedcomputing.com --------------Wy9z306B1utELZsM00vyO2mg Content-Type: application/sql; name="how-do-triggers-work.sql" Content-Disposition: attachment; filename="how-do-triggers-work.sql" Content-Transfer-Encoding: base64 LyoKICogVGhpcyBmaWxlIGlzIHRvIHNob3cgaG93IHRyaWdnZXJzIHdvcmsgaW4gdmFyaW91 cyBjb250ZXh0cyBpbiBQb3N0Z3Jlcy4KICogVGhlIGdvYWwgaXMgdG8gbWFrZSBzdXJlIHdo YXQgd2UgZG8gd2l0aCBVUERBVEUvREVMRVRFIEZPUiBQT1JUSU9OIE9GIGxlZnRvdmVycwog KiBpcyBjb25zaXN0ZW50LiBGb3IgZXhhbXBsZSBkbyB0aGUgbGVmdG92ZXJzJyBJTlNFUlRz IGZpcmUgc3RhdGVtZW50IHRyaWdnZXJzPwogKiBSb3cgdHJpZ2dlcnM/IEJlZm9yZSBvciBh ZnRlciB0aGUgQUZURVIgVVBEQVRFL0RFTEVURSB0cmlnZ2VycyBmaXJlPwogKiBXaGF0IHNo b3VsZCBiZSBpbiB0aGUgdHJhbnNpdGlvbiB0YWJsZXM/CiAqCiAqIFdlIHRlc3Q6CiAqCiAq IC0gSU5TRVJUIE9OIENPTkZMSUNUIERPIE5PVEhJTkcgKHdpdGhvdXQgJiB3aXRoIGEgY29u ZmxpY3QpCiAqIC0gSU5TRVJUIE9OIENPTkZMSUNUIERPIFVQREFURSAod2l0aG91dCAmIHdp dGggYSBjb25mbGljdCkKICogLSBJTlNFUlQgT04gQ09ORkxJQ1QgRE8gVVBEQVRFIFdIRVJF ICh3aXRoIGEgY29uZmxpY3QpCiAqIC0gTUVSR0UgRE8gTk9USElORyAod2l0aG91dCAmIHdp dGggYSBjb25mbGljdCkKICogLSBNRVJHRSBVUERBVEUgKHdpdGhvdXQgJiB3aXRoIGEgY29u ZmxpY3QpCiAqIC0gY3Jvc3MtcGFydGl0aW9uIFVQREFURQogKiAtIE9OIERFTEVURSBDQVND QURFCiAqIC0gT04gREVMRVRFIFNFVCBOVUxMCiAqLwpEUk9QIFRBQkxFIElGIEVYSVNUUyB0 LCB0MiwgdDMsIHQ0IENBU0NBREU7CkNSRUFURSBUQUJMRSB0ICgKICBpZCBpbnQgcHJpbWFy eSBrZXksCiAgbmFtZSB0ZXh0Cik7CgotLSBQcmludCBkZXRhaWxzIGFib3V0IHdoYXQgZmly ZWQuCkNSRUFURSBPUiBSRVBMQUNFIEZVTkNUSU9OIHByaW50X3RyaWcoKQpSRVRVUk5TIHRy aWdnZXIKQVMKJCQKREVDTEFSRQpCRUdJTgogIFJBSVNFIE5PVElDRSAnJTogJSAlICU6Jywg dGdfdGFibGVfbmFtZSwgdGdfd2hlbiwgdGdfb3AsIHRnX2xldmVsOwogIElGIHRnX2FyZ3Zb MF0gVEhFTgogICAgUkFJU0UgTk9USUNFICcgIG9sZDogJScsIChTRUxFQ1Qgc3RyaW5nX2Fn ZyhvbGR0Ojp0ZXh0LCAnXG4gICAgICAgJykgRlJPTSBvbGR0KTsKICBFTkQgSUY7CiAgSUYg dGdfYXJndlsxXSBUSEVOCiAgICBSQUlTRSBOT1RJQ0UgJyAgbmV3OiAlJywgKFNFTEVDVCBz dHJpbmdfYWdnKG5ld3Q6OnRleHQsICdcbiAgICAgICAnKSBGUk9NIG5ld3QpOwogIEVORCBJ RjsKICBJRiB0Z19vcCA9ICdJTlNFUlQnIE9SIHRnX29wID0gJ1VQREFURScgVEhFTgogICAg UkVUVVJOIE5FVzsKICBFTFNJRiB0Z19vcCA9ICdERUxFVEUnIFRIRU4KICAgIFJFVFVSTiBP TEQ7CiAgRU5EIElGOwpFTkQ7CiQkCkxBTkdVQUdFIHBscGdzcWw7CgpDUkVBVEUgT1IgUkVQ TEFDRSBGVU5DVElPTiBhZGRfdHJpZ2dlcnModGFibGVfbmFtZSB0ZXh0LCBwYXJ0aXRpb25l ZCBib29sZWFuID0gZmFsc2UpClJFVFVSTlMgVk9JRApBUwokJApCRUdJTgogIC0tIEZsYWdy YW50bHkgaWdub3JlIHNxbGkgZGFuZ2VyOgogIEVYRUNVVEUgZm9ybWF0KCRkZGwkCiAgICBD UkVBVEUgVFJJR0dFUiAlMSRzX2JlZm9yZV9zdG10IEJFRk9SRSBJTlNFUlQgT1IgVVBEQVRF IE9SIERFTEVURSBPTiAlMSRzCiAgICBGT1IgRUFDSCBTVEFURU1FTlQgRVhFQ1VURSBGVU5D VElPTiBwcmludF90cmlnKCk7CgogICAgQ1JFQVRFIFRSSUdHRVIgJTEkc19hZnRlcl9pbnNl cnRfc3RtdCBBRlRFUiBJTlNFUlQgT04gJTEkcwogICAgUkVGRVJFTkNJTkcgTkVXIFRBQkxF IEFTIG5ld3QKICAgIEZPUiBFQUNIIFNUQVRFTUVOVCBFWEVDVVRFIEZVTkNUSU9OIHByaW50 X3RyaWcoZmFsc2UsIHRydWUpOwoKICAgIENSRUFURSBUUklHR0VSICUxJHNfYWZ0ZXJfdXBk YXRlX3N0bXQgQUZURVIgVVBEQVRFIE9OICUxJHMKICAgIFJFRkVSRU5DSU5HIE9MRCBUQUJM RSBBUyBvbGR0IE5FVyBUQUJMRSBBUyBuZXd0CiAgICBGT1IgRUFDSCBTVEFURU1FTlQgRVhF Q1VURSBGVU5DVElPTiBwcmludF90cmlnKHRydWUsIHRydWUpOwoKICAgIENSRUFURSBUUklH R0VSICUxJHNfYWZ0ZXJfZGVsZXRlX3N0bXQgQUZURVIgREVMRVRFIE9OICUxJHMKICAgIFJF RkVSRU5DSU5HIE9MRCBUQUJMRSBBUyBvbGR0CiAgICBGT1IgRUFDSCBTVEFURU1FTlQgRVhF Q1VURSBGVU5DVElPTiBwcmludF90cmlnKHRydWUsIGZhbHNlKTsKCiAgICBDUkVBVEUgVFJJ R0dFUiAlMSRzX2JlZm9yZV9yb3cgQkVGT1JFIElOU0VSVCBPUiBVUERBVEUgT1IgREVMRVRF IE9OICUxJHMKICAgIEZPUiBFQUNIIFJPVyBFWEVDVVRFIEZVTkNUSU9OIHByaW50X3RyaWco KTsKICAkZGRsJCwgdGFibGVfbmFtZSk7CgogIElGIHBhcnRpdGlvbmVkIFRIRU4KICAgIC0t IFJvdyB0cmlnZ2VycyB3aXRoIHRyYW5zaXRpb24gdGFibGVzIGFyZSBub3QgcGVybWl0dGVk IG9uIHBhcnRpdGlvbmVkIHRhYmxlczoKICAgIEVYRUNVVEUgZm9ybWF0KCRkZGwkCiAgICAg IENSRUFURSBUUklHR0VSICUxJHNfYWZ0ZXJfcm93IEFGVEVSIElOU0VSVCBPUiBVUERBVEUg T1IgREVMRVRFIE9OICUxJHMKICAgICAgRk9SIEVBQ0ggUk9XIEVYRUNVVEUgRlVOQ1RJT04g cHJpbnRfdHJpZygpOwogICAgJGRkbCQsIHRhYmxlX25hbWUpOwogIEVMU0UKICAgIEVYRUNV VEUgZm9ybWF0KCRkZGwkCiAgICAgIENSRUFURSBUUklHR0VSICUxJHNfYWZ0ZXJfaW5zZXJ0 X3JvdyBBRlRFUiBJTlNFUlQgT04gJTEkcwogICAgICBSRUZFUkVOQ0lORyBORVcgVEFCTEUg QVMgbmV3dAogICAgICBGT1IgRUFDSCBST1cgRVhFQ1VURSBGVU5DVElPTiBwcmludF90cmln KGZhbHNlLCB0cnVlKTsKCiAgICAgIENSRUFURSBUUklHR0VSICUxJHNfYWZ0ZXJfdXBkYXRl X3JvdyBBRlRFUiBVUERBVEUgT04gJTEkcwogICAgICBSRUZFUkVOQ0lORyBPTEQgVEFCTEUg QVMgb2xkdCBORVcgVEFCTEUgQVMgbmV3dAogICAgICBGT1IgRUFDSCBST1cgRVhFQ1VURSBG VU5DVElPTiBwcmludF90cmlnKHRydWUsIHRydWUpOwoKICAgICAgQ1JFQVRFIFRSSUdHRVIg JTEkc19hZnRlcl9kZWxldGVfcm93IEFGVEVSIERFTEVURSBPTiAlMSRzCiAgICAgIFJFRkVS RU5DSU5HIE9MRCBUQUJMRSBBUyBvbGR0CiAgICAgIEZPUiBFQUNIIFJPVyBFWEVDVVRFIEZV TkNUSU9OIHByaW50X3RyaWcodHJ1ZSwgZmFsc2UpOwogICAgJGRkbCQsIHRhYmxlX25hbWUp OwogIEVORCBJRjsKRU5EOwokJApMQU5HVUFHRSBwbHBnc3FsOwoKU0VMRUNUIGFkZF90cmln Z2VycygndCcpOwoKSU5TRVJUIElOVE8gdCBWQUxVRVMgKDEsICdhJyk7CgpJTlNFUlQgSU5U TyB0IFZBTFVFUyAoMiwgJ2EnKSBPTiBDT05GTElDVCAoaWQpIERPIE5PVEhJTkc7CklOU0VS VCBJTlRPIHQgVkFMVUVTICgyLCAnYicpIE9OIENPTkZMSUNUIChpZCkgRE8gTk9USElORzsK CklOU0VSVCBJTlRPIHQgVkFMVUVTICgzLCAnYScpIE9OIENPTkZMSUNUIChpZCkgRE8gVVBE QVRFIFNFVCBuYW1lID0gRVhDTFVERUQubmFtZTsKSU5TRVJUIElOVE8gdCBWQUxVRVMgKDMs ICdiJykgT04gQ09ORkxJQ1QgKGlkKSBETyBVUERBVEUgU0VUIG5hbWUgPSBFWENMVURFRC5u YW1lOwpJTlNFUlQgSU5UTyB0IFZBTFVFUyAoMywgJ2MnKSBPTiBDT05GTElDVCAoaWQpIERP IFVQREFURSBTRVQgbmFtZSA9IEVYQ0xVREVELm5hbWUgV0hFUkUgZmFsc2U7CgpXSVRIIHNy YyhpZCwgbmFtZSkgQVMgKAogIChWQUxVRVMgKDQsICdhJykpCikKTUVSR0UgSU5UTyB0CiAg VVNJTkcgc3JjIE9OIHQuaWQgPSBzcmMuaWQKICBXSEVOIE1BVENIRUQgVEhFTiBETyBOT1RI SU5HCiAgV0hFTiBOT1QgTUFUQ0hFRCBCWSBUQVJHRVQgVEhFTiBJTlNFUlQgVkFMVUVTIChp ZCwgbmFtZSk7CgpXSVRIIHNyYyhpZCwgbmFtZSkgQVMgKAogIChWQUxVRVMgKDQsICdiJykp CikKTUVSR0UgSU5UTyB0CiAgVVNJTkcgc3JjIE9OIHQuaWQgPSBzcmMuaWQKICBXSEVOIE1B VENIRUQgVEhFTiBETyBOT1RISU5HCiAgV0hFTiBOT1QgTUFUQ0hFRCBCWSBUQVJHRVQgVEhF TiBJTlNFUlQgVkFMVUVTIChpZCwgbmFtZSk7CgpXSVRIIHNyYyhpZCwgbmFtZSkgQVMgKAog IChWQUxVRVMgKDUsICdhJykpCikKTUVSR0UgSU5UTyB0CiAgVVNJTkcgc3JjIE9OIHQuaWQg PSBzcmMuaWQKICBXSEVOIE1BVENIRUQgVEhFTiBVUERBVEUgU0VUIG5hbWUgPSBzcmMubmFt ZQogIFdIRU4gTk9UIE1BVENIRUQgQlkgVEFSR0VUIFRIRU4gSU5TRVJUIFZBTFVFUyAoaWQs IG5hbWUpOwoKV0lUSCBzcmMoaWQsIG5hbWUpIEFTICgKICAoVkFMVUVTICg1LCAnYicpKQop Ck1FUkdFIElOVE8gdAogIFVTSU5HIHNyYyBPTiB0LmlkID0gc3JjLmlkCiAgV0hFTiBNQVRD SEVEIFRIRU4gVVBEQVRFIFNFVCBuYW1lID0gc3JjLm5hbWUKICBXSEVOIE5PVCBNQVRDSEVE IEJZIFRBUkdFVCBUSEVOIElOU0VSVCBWQUxVRVMgKGlkLCBuYW1lKTsKCkNSRUFURSBUQUJM RSB0MiAoCiAgaWQgaW50IHByaW1hcnkga2V5LAogIG5hbWUgdGV4dAopIFBBUlRJVElPTiBC WSBMSVNUIChpZCk7CkNSRUFURSBUQUJMRSB0MnAxIFBBUlRJVElPTiBPRiB0MiBGT1IgVkFM VUVTIElOICgxLCAzLCA1LCA3KTsKQ1JFQVRFIFRBQkxFIHQycDIgUEFSVElUSU9OIE9GIHQy IEZPUiBWQUxVRVMgSU4gKDIsIDQsIDYsIDgpOwoKU0VMRUNUIGFkZF90cmlnZ2VycygndDIn LCB0cnVlKTsKCklOU0VSVCBJTlRPIHQyIFZBTFVFUyAoMSwgJ2EnKTsKVVBEQVRFIHQyIFNF VCBpZCA9IDIgV0hFUkUgaWQgPSAxOwoKLS0gRG8gZGVsZXRlIHRyaWdnZXJzIGZpcmUgd2hl biBhIGZvcmVpZ24ga2V5IGhhcyBPTiBERUxFVEUgQ0FTQ0FERT8KQ1JFQVRFIFRBQkxFIHQz ICgKICBpZCBpbnQgcHJpbWFyeSBrZXksCiAgcGFyZW50X2lkIGludCBSRUZFUkVOQ0VTIHQg KGlkKSBPTiBERUxFVEUgQ0FTQ0FERQopOwpTRUxFQ1QgYWRkX3RyaWdnZXJzKCd0MycpOwpJ TlNFUlQgSU5UTyB0IChpZCwgbmFtZSkgVkFMVUVTICgxMCwgJ2EnKTsKSU5TRVJUIElOVE8g dDMgKGlkLCBwYXJlbnRfaWQpIFZBTFVFUyAoMSwgMTApOwpERUxFVEUgRlJPTSB0IFdIRVJF IGlkID0gMTA7CgotLSBEbyBkZWxldGUgdHJpZ2dlcnMgZmlyZSB3aGVuIGEgZm9yZWlnbiBr ZXkgaGFzIE9OIERFTEVURSBTRVQgTlVMTD8KQ1JFQVRFIFRBQkxFIHQ0ICgKICBpZCBpbnQg cHJpbWFyeSBrZXksCiAgcGFyZW50X2lkIGludCBSRUZFUkVOQ0VTIHQgKGlkKSBPTiBERUxF VEUgU0VUIE5VTEwKKTsKU0VMRUNUIGFkZF90cmlnZ2VycygndDQnKTsKSU5TRVJUIElOVE8g dCAoaWQsIG5hbWUpIFZBTFVFUyAoMTEsICdhJyk7CklOU0VSVCBJTlRPIHQ0IChpZCwgcGFy ZW50X2lkKSBWQUxVRVMgKDEsIDExKTsKREVMRVRFIEZST00gdCBXSEVSRSBpZCA9IDExOwo= --------------Wy9z306B1utELZsM00vyO2mg--