2 CREATE TABLE tidscan(id integer);
3 -- only insert a few rows, we don't want to spill onto a second table page
4 INSERT INTO tidscan VALUES (1), (2), (3);
6 SELECT ctid, * FROM tidscan;
14 -- ctid equality - implemented as tidscan
16 SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
18 -----------------------------------
20 TID Cond: (ctid = '(0,1)'::tid)
23 SELECT ctid, * FROM tidscan WHERE ctid = '(0,1)';
30 SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
32 -----------------------------------
34 TID Cond: ('(0,1)'::tid = ctid)
37 SELECT ctid, * FROM tidscan WHERE '(0,1)' = ctid;
45 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
47 --------------------------------------------------------------
49 TID Cond: ((ctid = '(0,2)'::tid) OR ('(0,1)'::tid = ctid))
52 SELECT ctid, * FROM tidscan WHERE ctid = '(0,2)' OR '(0,1)' = ctid;
59 -- ctid = ScalarArrayOp - implemented as tidscan
61 SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
63 -------------------------------------------------------
65 TID Cond: (ctid = ANY ('{"(0,1)","(0,2)"}'::tid[]))
68 SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
75 -- ctid != ScalarArrayOp - can't be implemented as tidscan
77 SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
79 ------------------------------------------------------
81 Filter: (ctid <> ANY ('{"(0,1)","(0,2)"}'::tid[]))
84 SELECT ctid, * FROM tidscan WHERE ctid != ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
92 -- tid equality extracted from sub-AND clauses
94 SELECT ctid, * FROM tidscan
95 WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
97 --------------------------------------------------------------------------------------------------------------
99 TID Cond: ((ctid = ANY ('{"(0,2)","(0,3)"}'::tid[])) OR (ctid = '(0,1)'::tid))
100 Filter: (((id = 3) AND (ctid = ANY ('{"(0,2)","(0,3)"}'::tid[]))) OR ((ctid = '(0,1)'::tid) AND (id = 1)))
103 SELECT ctid, * FROM tidscan
104 WHERE (id = 3 AND ctid IN ('(0,2)', '(0,3)')) OR (ctid = '(0,1)' AND id = 1);
111 -- nestloop-with-inner-tidscan joins on tid
112 SET enable_hashjoin TO off; -- otherwise hash join might win
114 SELECT t1.ctid, t1.*, t2.ctid, t2.*
115 FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
117 ------------------------------------
119 -> Seq Scan on tidscan t1
121 -> Tid Scan on tidscan t2
122 TID Cond: (ctid = t1.ctid)
125 SELECT t1.ctid, t1.*, t2.ctid, t2.*
126 FROM tidscan t1 JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
127 ctid | id | ctid | id
128 -------+----+-------+----
129 (0,1) | 1 | (0,1) | 1
133 SELECT t1.ctid, t1.*, t2.ctid, t2.*
134 FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
136 ------------------------------------
137 Nested Loop Left Join
138 -> Seq Scan on tidscan t1
140 -> Tid Scan on tidscan t2
141 TID Cond: (t1.ctid = ctid)
144 SELECT t1.ctid, t1.*, t2.ctid, t2.*
145 FROM tidscan t1 LEFT JOIN tidscan t2 ON t1.ctid = t2.ctid WHERE t1.id = 1;
146 ctid | id | ctid | id
147 -------+----+-------+----
148 (0,1) | 1 | (0,1) | 1
151 RESET enable_hashjoin;
152 -- exercise backward scan and rewind
155 SELECT ctid, * FROM tidscan WHERE ctid = ANY(ARRAY['(0,1)', '(0,2)']::tid[]);
163 FETCH BACKWARD 1 FROM c;
176 -- tidscan via CURRENT OF
178 DECLARE c CURSOR FOR SELECT ctid, * FROM tidscan;
179 FETCH NEXT FROM c; -- skip one row
192 EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
193 UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
195 ---------------------------------------------------
196 Update on tidscan (actual rows=1 loops=1)
197 -> Tid Scan on tidscan (actual rows=1 loops=1)
198 TID Cond: CURRENT OF c
208 EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
209 UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
211 ---------------------------------------------------
212 Update on tidscan (actual rows=1 loops=1)
213 -> Tid Scan on tidscan (actual rows=1 loops=1)
214 TID Cond: CURRENT OF c
217 SELECT * FROM tidscan;
225 -- position cursor past any rows
232 EXPLAIN (ANALYZE, COSTS OFF, SUMMARY OFF, TIMING OFF)
233 UPDATE tidscan SET id = -id WHERE CURRENT OF c RETURNING *;
234 ERROR: cursor "c" is not positioned on a row
236 -- bulk joins on CTID
237 -- (these plans don't use TID scans, but this still seems like an
238 -- appropriate place for these tests)
240 SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
242 ----------------------------------------
245 Hash Cond: (t1.ctid = t2.ctid)
246 -> Seq Scan on tenk1 t1
248 -> Seq Scan on tenk1 t2
251 SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
257 SET enable_hashjoin TO off;
259 SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
261 -----------------------------------------
264 Merge Cond: (t1.ctid = t2.ctid)
267 -> Seq Scan on tenk1 t1
270 -> Seq Scan on tenk1 t2
273 SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
279 RESET enable_hashjoin;
280 -- check predicate lock on CTID
281 BEGIN ISOLATION LEVEL SERIALIZABLE;
282 SELECT * FROM tidscan WHERE ctid = '(0,1)';
288 -- locktype should be 'tuple'
289 SELECT locktype, mode FROM pg_locks WHERE pid = pg_backend_pid() AND mode = 'SIReadLock';
291 ----------+------------