aboutsummaryrefslogtreecommitdiff
path: root/src/backend/access/nbtree/nbtscan.c
blob: a7ae57f91a852374078ec78ffb75423a73a65480 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*-------------------------------------------------------------------------
 *
 * btscan.c--
 *	  manage scans on btrees.
 *
 * Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/access/nbtree/Attic/nbtscan.c,v 1.13 1998/02/28 13:53:18 vadim Exp $
 *
 *
 * NOTES
 *	 Because we can be doing an index scan on a relation while we update
 *	 it, we need to avoid missing data that moves around in the index.
 *	 The routines and global variables in this file guarantee that all
 *	 scans in the local address space stay correctly positioned.  This
 *	 is all we need to worry about, since write locking guarantees that
 *	 no one else will be on the same page at the same time as we are.
 *
 *	 The scheme is to manage a list of active scans in the current backend.
 *	 Whenever we add or remove records from an index, or whenever we
 *	 split a leaf page, we check the list of active scans to see if any
 *	 has been affected.  A scan is affected only if it is on the same
 *	 relation, and the same page, as the update.
 *
 *-------------------------------------------------------------------------
 */

#include <postgres.h>

#include <storage/bufpage.h>
#include <access/nbtree.h>

typedef struct BTScanListData
{
	IndexScanDesc btsl_scan;
	struct BTScanListData *btsl_next;
} BTScanListData;

typedef BTScanListData *BTScanList;

static BTScanList BTScans = (BTScanList) NULL;

static void _bt_scandel(IndexScanDesc scan, int op, BlockNumber blkno, OffsetNumber offno);
static bool _bt_scantouched(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno);

/*
 *	_bt_regscan() -- register a new scan.
 */
void
_bt_regscan(IndexScanDesc scan)
{
	BTScanList	new_el;

	new_el = (BTScanList) palloc(sizeof(BTScanListData));
	new_el->btsl_scan = scan;
	new_el->btsl_next = BTScans;
	BTScans = new_el;
}

/*
 *	_bt_dropscan() -- drop a scan from the scan list
 */
void
_bt_dropscan(IndexScanDesc scan)
{
	BTScanList	chk,
				last;

	last = (BTScanList) NULL;
	for (chk = BTScans;
		 chk != (BTScanList) NULL && chk->btsl_scan != scan;
		 chk = chk->btsl_next)
	{
		last = chk;
	}

	if (chk == (BTScanList) NULL)
		elog(ERROR, "btree scan list trashed; can't find 0x%lx", scan);

	if (last == (BTScanList) NULL)
		BTScans = chk->btsl_next;
	else
		last->btsl_next = chk->btsl_next;

	pfree(chk);
}

/*
 *	_bt_adjscans() -- adjust all scans in the scan list to compensate
 *					  for a given deletion or insertion
 */
void
_bt_adjscans(Relation rel, ItemPointer tid, int op)
{
	BTScanList	l;
	Oid			relid;

	relid = rel->rd_id;
	for (l = BTScans; l != (BTScanList) NULL; l = l->btsl_next)
	{
		if (relid == l->btsl_scan->relation->rd_id)
			_bt_scandel(l->btsl_scan, op,
						ItemPointerGetBlockNumber(tid),
						ItemPointerGetOffsetNumber(tid));
	}
}

/*
 *	_bt_scandel() -- adjust a single scan
 *
 * because each index page is always maintained as an ordered array of
 * index tuples, the index tuples on a given page shift beneath any
 * given scan.	an index modification "behind" a scan position (i.e.,
 * same page, lower or equal offset number) will therefore force us to
 * adjust the scan in the following ways:
 *
 * - on insertion, we shift the scan forward by one item.
 * - on deletion, we shift the scan backward by one item.
 *
 * note that:
 *
 * - we need not worry about the actual ScanDirection of the scan
 * itself, since the problem is that the "current" scan position has
 * shifted.
 * - modifications "ahead" of our scan position do not change the
 * array index of the current scan position and so can be ignored.
 */
static void
_bt_scandel(IndexScanDesc scan, int op, BlockNumber blkno, OffsetNumber offno)
{
	ItemPointer current;
	Buffer		buf;
	BTScanOpaque so;

	if (!_bt_scantouched(scan, blkno, offno))
		return;

	so = (BTScanOpaque) scan->opaque;
	buf = so->btso_curbuf;

	current = &(scan->currentItemData);
	if (ItemPointerIsValid(current)
		&& ItemPointerGetBlockNumber(current) == blkno
		&& ItemPointerGetOffsetNumber(current) >= offno)
	{
		switch (op)
		{
			case BT_INSERT:
				_bt_step(scan, &buf, ForwardScanDirection);
				break;
			case BT_DELETE:
				_bt_step(scan, &buf, BackwardScanDirection);
				break;
			default:
				elog(ERROR, "_bt_scandel: bad operation '%d'", op);
				/* NOTREACHED */
		}
		so->btso_curbuf = buf;
	}

	current = &(scan->currentMarkData);
	if (ItemPointerIsValid(current)
		&& ItemPointerGetBlockNumber(current) == blkno
		&& ItemPointerGetOffsetNumber(current) >= offno)
	{
		ItemPointerData tmp;

		tmp = *current;
		*current = scan->currentItemData;
		scan->currentItemData = tmp;
		so->btso_curbuf = so->btso_mrkbuf;
		so->btso_mrkbuf = buf;
		buf = so->btso_curbuf;
		switch (op)
		{
			case BT_INSERT:
				_bt_step(scan, &buf, ForwardScanDirection);
				break;
			case BT_DELETE:
				_bt_step(scan, &buf, BackwardScanDirection);
				break;
			default:
				elog(ERROR, "_bt_scandel: bad operation '%d'", op);
				/* NOTREACHED */
		}
		so->btso_curbuf = so->btso_mrkbuf;
		so->btso_mrkbuf = buf;
		tmp = *current;
		*current = scan->currentItemData;
		scan->currentItemData = tmp;
	}
}

/*
 *	_bt_scantouched() -- check to see if a scan is affected by a given
 *						 change to the index
 */
static bool
_bt_scantouched(IndexScanDesc scan, BlockNumber blkno, OffsetNumber offno)
{
	ItemPointer current;

	current = &(scan->currentItemData);
	if (ItemPointerIsValid(current)
		&& ItemPointerGetBlockNumber(current) == blkno
		&& ItemPointerGetOffsetNumber(current) >= offno)
		return (true);

	current = &(scan->currentMarkData);
	if (ItemPointerIsValid(current)
		&& ItemPointerGetBlockNumber(current) == blkno
		&& ItemPointerGetOffsetNumber(current) >= offno)
		return (true);

	return (false);
}