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.96) (envelope-from ) id 1vJO4e-004FlU-3B for pgsql-hackers@arkaria.postgresql.org; Thu, 13 Nov 2025 03:32:39 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vJO4b-00FsXo-2Y for pgsql-hackers@arkaria.postgresql.org; Thu, 13 Nov 2025 03:32:37 +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.96) (envelope-from ) id 1vJO4b-00FsXb-19 for pgsql-hackers@lists.postgresql.org; Thu, 13 Nov 2025 03:32:37 +0000 Received: from mail-qv1-xf2b.google.com ([2607:f8b0:4864:20::f2b]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vJO4Z-006q8v-0R for pgsql-hackers@lists.postgresql.org; Thu, 13 Nov 2025 03:32:36 +0000 Received: by mail-qv1-xf2b.google.com with SMTP id 6a1803df08f44-88246676008so4456626d6.3 for ; Wed, 12 Nov 2025 19:32:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1763004754; x=1763609554; darn=lists.postgresql.org; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=UisB+3/6WjpR2wT0h1CWTUYO86/H1tvBg/tgivg6otI=; b=gf7Iz7Ka2EtgVC7dUnkpFT04OxkVpoo6mxpj/cEOBbshftVkcAWSoKrBZ978uYJixD cgW2TsotTOjHlUgowc82JRJ38trsJWFUOqWdq32bfa43D9w6BxK+CreBjRzM7AjN67IH y2Bdni78/UQka4HBpdOX5c/abaZPpUv/krH3tbZVerO50Aus6xiHpXmk/wlRDLb+McUt 1Xjq7xxuo34YCCsflNTXbB2QsXGWtxR84G5h1NigTN51gikGHC5ip1hCsjwAD1QZpRKZ YQJhnws3KpxKPTl+UmtkjvnB/wfJkSPjhZdu3SLeP9vvLqcBG8x1Fg7L5yMaby/iscCY 2JZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763004754; x=1763609554; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:x-gm-gg:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=UisB+3/6WjpR2wT0h1CWTUYO86/H1tvBg/tgivg6otI=; b=QgWF9eNoJwywwl0IQ/SxyuUMM8r+f2ABVi/ax1lgbB9mbfW3k4NJ6biLQM1ffyxXxV 5fNrTBH0hJSPTBse1BJKIpWnzgb7pCBgwMMgjz9cT602RatukbTxUwST4uOjqjJfNlU6 hBvKXS+1uSgVLx8/3dQLm62C5E71vlXg4bkoxsX4DdaTfNyweuLXgO8BBuegOyJPpPBY zYi4NnLQI4cC9j71zWI9IwgfVJ022rebnKQ0S/biZpgojSS/0Yh6uvprWmE3wIZP9+2a Q66FTBs5tzAKZexu/Qo9aCvDZP/+7ITdzozsvJ6/cX9z6NjHwK5WBK2ktGNI90Xfbik+ ahRg== X-Forwarded-Encrypted: i=1; AJvYcCX5vYOpdFDG8GfJpR0yTSJIbyiQE81oFzbZyNsTLV+Xlr/mdGw/7ArpBJZxizdaj0e+eUJtQKt7wZdK8Mtj@lists.postgresql.org X-Gm-Message-State: AOJu0Yy3EdRnji5GPMtZFFbcz7dwiVedM3lzCCURKlVqzlbuxA9AeC4y YG62PPYx10dyqO5BZU4QwEuIwpqlwW32mjfJBpMswd2gEKK3SJRMIhdZ X-Gm-Gg: ASbGncvlVVnODCiwo6Z+iAKoMOLpUlO2ssZfDiq3Z89qSFKnJnK1+F18b5iNLfPzMQV xoPhZOC5FOn/YdkrdP+wfqe6gf0/j3SAy21VOcjxPgW1pFQ6IuWwRf3Weq9NCW8xmvHwQYk6G8G bAihUx/tBLz1NKtay2UKxD0ydSTeh60kI/Vre4iV8P2jOAqdx7EX1yi9i/qYB6sIl5DKqHtaft9 zbLmf3CvR+cO7nWCn+cvRNnpfREtQ63mEcGCTjxQoy+N1SiNfTB/1nOp7EvcQadzj1GhVuGxTlw ARzCB6B85gj/tlOD2t3aBVRREfvHb2kWYSv9Ge2Aw3CnS1+cp++dfr+02+CZAJfi33CL+bNvDig waMsdXvKzG/aTttft5HF/J2SDg9VXjyOii8fvE08qe3shRnFe5eX22ZlAVkkPNv/cF5zphZ5ZvW XjN4TicAPB8A== X-Google-Smtp-Source: AGHT+IHhTRrzctFxkobbXSOtcKl3NkrseKYQHC2das4uqMmTuxUSwyaS1j/4ZSD+kXATtr4hBdy1nA== X-Received: by 2002:ac8:5989:0:b0:4ec:b598:2544 with SMTP id d75a77b69052e-4eddbddbcbcmr71162571cf.54.1763004753779; Wed, 12 Nov 2025 19:32:33 -0800 (PST) Received: from smtpclient.apple ([209.127.78.222]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8828631454csm4424976d6.18.2025.11.12.19.32.31 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 12 Nov 2025 19:32:33 -0800 (PST) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.700.81\)) Subject: Re: SQL:2011 Application Time Update & Delete From: Chao Li In-Reply-To: <7A20FF56-1A90-4221-A95E-5A605E36410A@gmail.com> Date: Thu, 13 Nov 2025 11:31:57 +0800 Cc: Peter Eisentraut , PostgreSQL Hackers Content-Transfer-Encoding: quoted-printable Message-Id: <33505902-8524-430B-A39A-C71119937E91@gmail.com> References: <2f5364f3-a1d3-4410-98f3-d788b11e6525@eisentraut.org> <7A20FF56-1A90-4221-A95E-5A605E36410A@gmail.com> To: Paul A Jungwirth X-Mailer: Apple Mail (2.3826.700.81) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk > On Nov 12, 2025, at 17:31, Chao Li wrote: >=20 >=20 >=20 >> On Nov 5, 2025, at 03:12, Paul A Jungwirth = wrote: >>=20 >> On Wed, Oct 29, 2025 at 11:02=E2=80=AFPM Paul A Jungwirth >> wrote: >>>=20 >>> On Tue, Oct 28, 2025 at 3:49=E2=80=AFAM Peter Eisentraut = wrote: >>>> On 24.10.25 19:08, Paul A Jungwirth wrote: >>>>> The first 3 doc patches all apply to features that we released in = v18, >>>>> so it would be nice to get those reviewed/merged soon if possible. >>>>=20 >>>> I have looked through the documentation patches 0001 through 0003. >>>=20 >>> Thanks for taking a look! New patches attached; details below. >>=20 >> Hi Hackers, >>=20 >> Here is another set of patches. I added isolation tests for FOR >> PORTION OF. In REPEATABLE READ and SERIALIZABLE you get >> easy-to-predict results. In READ COMMITTED you get a lot of lost >> updates/deletes, because the second operation doesn't see the >> leftovers created by the first (and sometimes the first operation >> changes the start/end times in a way that EvalPlanQual no longer sees >> the being-changed row either). I think those results make sense, if >> you think step-by-step what Postgres is doing, but they are not = really >> what a user wants. >>=20 >> I tested the same sequences in MariaDB, and they also gave nonsense >> results, although not always the same nonsense as Postgres. At >> UNCOMMITTED READ it actually gave the results you'd want, but at that >> level I assume you will have other problems. >>=20 >> I also tested DB2. It doesn't have READ COMMITTED, but I think READ >> STABILITY is the closest. At that level (as well as CURSOR STABILITY >> and REPEATABLE READ), you get correct results. >>=20 >> Back to Postgres, you can get "desired" results IN READ COMMITTED by >> explicitly locking rows (with SELECT FOR UPDATE) just before >> updating/deleting them. Since you acquire the lock before the >> update/delete starts, there can be no new leftovers created within >> that span of history, and the update/delete sees everything that is >> there. The same approach also gives correct results in MariaDB. I >> think it is just the way you have to do things with temporal tables = in >> READ COMMITTED whenever you expect concurrent updates to the same >> history. >>=20 >> I considered whether we should make EvalPlanQual (or something else) >> automatically rescan for leftovers when it's a temporal operation. >> Then you wouldn't have to explicitly lock anything. But it seems like >> that is more than the isolation level "contract", and maybe even = plain >> violates it (but arguably not, if you say the update shouldn't = *start* >> until the other session commits). But since there is a workaround, = and >> since other RDBMSes also scramble temporal data in READ COMMITTED, = and >> since it is a lot of work and seems tricky, I didn't attempt it. >>=20 >> Another idea (or maybe nearly the same thing) would be to >> automatically do the same thing that SELECT FOR UPDATE is doing, >> whenever we see a FOR PORTION OF DML command---i.e. scan for rows and >> lock them first, then do the update. But that has similar issues. If >> it adds locks the user doesn't expect, is it really the right thing? >> And it means users pay the cost even when no concurrency is expected. >> It offers strictly fewer options than requiring users to do SELECT = FOR >> UPDATE explicitly. >>=20 >> The isolation tests are a separate patch for now, because they felt >> like a significant chunk, and I wanted to emphasize them, but really >> they should be part of the main FOR PORTION OF commit. Probably I'll >> squash them in future submissions. That patch also makes some small >> updates to a comment in ExecForPortionOf and the docs for >> UPDATE/DELETE FOR PORTION OF, to raise awareness of the READ = COMMITTED >> issues. >>=20 >> Rebased to 65f4976189. >>=20 >> Yours, >>=20 >> --=20 >> Paul ~{:-) >> pj@illuminatedcomputing.com >> = >=20 > I tried to review this patch. Though I =E2=80=9Cgit reset=E2=80=9D to = commit 65f4976189, =E2=80=9Cgit am=E2=80=9D still failed at 0009. >=20 > Today I only reviewed 0001, it was a happy reading. I found a small = typo and got a suggestion: >=20 > 1 - 0001 > ``` > + entity described by a table. In a typical non-temporal table, = there is > + single row for each entity. In a temporal table, an entity may = have=20 > ``` >=20 > =E2=80=9CThere is single row=E2=80=9D should be =E2=80=9Cthere is a = single row=E2=80=9D. >=20 >=20 > 2 - 0001 - The doc mentions rangetypes which is the key factor for = defining a temporal table, can we add a hyper link on =E2=80=9Crangetype=E2= =80=9D so that readers can easily jump to learn which rangetypes can be = used. >=20 > I will continue to review the rest of commits tomorrow. >=20 I spent a hour reading through 0002-0004 and got my brain stuck. I=E2=80=99= d stop here today, and maybe continue tomorrow. A few more comments: 3 - 0002 ``` + +CREATE TABLE variants ( + id integer NOT NULL, + product_id integer NOT NULL, + name text NOT NULL, + valid_at daterange NOT NULL, + CONSTRAINT variants_pkey + PRIMARY KEY (id, valid_at WITHOUT OVERLAPS), +); + ``` The common before ) is not needed. 4 - 0002 ``` + + + In a table, these records would be: + + id | product_id | name | valid_at +----+------------+--------+------------------------- + 8 | 5 | Medium | [2021-01-01,2023-06-01) + 9 | 5 | XXL | [2022-03-01,2024-06-01) + + ``` The blank line after =E2=80=9C=E2=80=9D is not needed. 5 - 0003 ``` + zero, one, or two stretches of history that where not = updated/deleted ``` Typo: where -> were 6 - 0004 - func-range.sgml ``` multirange_minus_multi multirange_minus_multi ( = anymultirange, anymultirange ) setof anymultirange Returns the non-empty multirange(s) remaining after subtracting = the second multirange from the first. If the subtraction yields an empty multirange, no rows are = returned. Two rows are never returned, because a single multirange can = always accommodate any result. range_minus_multi('[0,10)'::int4range, = '[3,4)'::int4range) {[0,3), [4,10)} ``` I believe in " range_minus_multi('[0,10)'::int4range, = '[3,4)'::int4range)=E2=80=9D, it should be = =E2=80=9Cmultirange_minus_multi=E2=80=9D. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/