sequence.c 30.3 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * sequence.c
4
 *	  PostgreSQL sequences support code.
5
 *
P
 
PostgreSQL Daemon 已提交
6
 * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
7 8 9 10
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
B
Bruce Momjian 已提交
11
 *	  $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.125 2005/10/15 02:49:15 momjian Exp $
12
 *
13 14
 *-------------------------------------------------------------------------
 */
15
#include "postgres.h"
16

17
#include "access/heapam.h"
18
#include "catalog/namespace.h"
19
#include "catalog/pg_type.h"
20
#include "commands/defrem.h"
21
#include "commands/tablecmds.h"
22
#include "commands/sequence.h"
B
Bruce Momjian 已提交
23
#include "miscadmin.h"
24
#include "utils/acl.h"
B
Bruce Momjian 已提交
25
#include "utils/builtins.h"
26
#include "utils/resowner.h"
27
#include "utils/syscache.h"
28

29

V
Vadim B. Mikheev 已提交
30
/*
31
 * We don't want to log each fetching of a value from a sequence,
V
Vadim B. Mikheev 已提交
32 33 34
 * so we pre-log a few fetches in advance. In the event of
 * crash we can lose as much as we pre-logged.
 */
B
Bruce Momjian 已提交
35
#define SEQ_LOG_VALS	32
36

37 38 39 40 41
/*
 * The "special area" of a sequence's buffer page looks like this.
 */
#define SEQ_MAGIC	  0x1717

42 43
typedef struct sequence_magic
{
44
	uint32		magic;
45
} sequence_magic;
46

47 48 49 50 51 52
/*
 * We store a SeqTable item for every sequence we have touched in the current
 * session.  This is needed to hold onto nextval/currval state.  (We can't
 * rely on the relcache, since it's only, well, a cache, and may decide to
 * discard entries.)
 *
B
Bruce Momjian 已提交
53
 * XXX We use linear search to find pre-existing SeqTable entries.	This is
54 55 56
 * good when only a small number of sequences are touched in a session, but
 * would suck with many different sequences.  Perhaps use a hashtable someday.
 */
57 58
typedef struct SeqTableData
{
59 60 61 62 63 64 65
	struct SeqTableData *next;	/* link to next SeqTable object */
	Oid			relid;			/* pg_class OID of this sequence */
	TransactionId xid;			/* xact in which we last did a seq op */
	int64		last;			/* value last returned by nextval */
	int64		cached;			/* last value already cached for nextval */
	/* if last != cached, we have not used up all the cached values */
	int64		increment;		/* copy of sequence's increment field */
66
} SeqTableData;
67 68 69

typedef SeqTableData *SeqTable;

70
static SeqTable seqtab = NULL;	/* Head of list of SeqTable items */
71

72 73 74 75 76
/*
 * last_used_seq is updated by nextval() to point to the last used
 * sequence.
 */
static SeqTableData *last_used_seq = NULL;
77

78
static int64 nextval_internal(Oid relid);
79
static void acquire_share_lock(Relation seqrel, SeqTable seq);
80
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
81
static Form_pg_sequence read_info(SeqTable elm, Relation rel, Buffer *buf);
82
static void init_params(List *options, Form_pg_sequence new, bool isInit);
83
static void do_setval(Oid relid, int64 next, bool iscalled);
84 85

/*
B
Bruce Momjian 已提交
86
 * DefineSequence
87
 *				Creates a new sequence relation
88 89
 */
void
90
DefineSequence(CreateSeqStmt *seq)
91
{
92
	FormData_pg_sequence new;
93
	CreateStmt *stmt = makeNode(CreateStmt);
94
	Oid			seqoid;
95 96 97
	Relation	rel;
	Buffer		buf;
	PageHeader	page;
98
	sequence_magic *sm;
99 100 101 102 103
	HeapTuple	tuple;
	TupleDesc	tupDesc;
	Datum		value[SEQ_COL_LASTCOL];
	char		null[SEQ_COL_LASTCOL];
	int			i;
104
	NameData	name;
105

106 107
	/* Check and set all option values */
	init_params(seq->options, &new, true);
108 109

	/*
110
	 * Create relation (and fill *null & *value)
111 112 113
	 */
	stmt->tableElts = NIL;
	for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++)
114
	{
115 116 117
		ColumnDef  *coldef;
		TypeName   *typnam;

118 119
		typnam = makeNode(TypeName);
		typnam->setof = FALSE;
120
		typnam->arrayBounds = NIL;
B
Bruce Momjian 已提交
121
		typnam->typmod = -1;
122

123 124
		coldef = makeNode(ColumnDef);
		coldef->typename = typnam;
125 126
		coldef->inhcount = 0;
		coldef->is_local = true;
127
		coldef->is_not_null = true;
128 129
		coldef->raw_default = NULL;
		coldef->cooked_default = NULL;
130 131 132
		coldef->constraints = NIL;
		coldef->support = NULL;

133 134 135 136
		null[i - 1] = ' ';

		switch (i)
		{
137
			case SEQ_COL_NAME:
138
				typnam->typeid = NAMEOID;
139
				coldef->colname = "sequence_name";
140
				namestrcpy(&name, seq->sequence->relname);
141
				value[i - 1] = NameGetDatum(&name);
142 143
				break;
			case SEQ_COL_LASTVAL:
144
				typnam->typeid = INT8OID;
145
				coldef->colname = "last_value";
146
				value[i - 1] = Int64GetDatumFast(new.last_value);
147 148
				break;
			case SEQ_COL_INCBY:
149
				typnam->typeid = INT8OID;
150
				coldef->colname = "increment_by";
151
				value[i - 1] = Int64GetDatumFast(new.increment_by);
152 153
				break;
			case SEQ_COL_MAXVALUE:
154
				typnam->typeid = INT8OID;
155
				coldef->colname = "max_value";
156
				value[i - 1] = Int64GetDatumFast(new.max_value);
157 158
				break;
			case SEQ_COL_MINVALUE:
159
				typnam->typeid = INT8OID;
160
				coldef->colname = "min_value";
161
				value[i - 1] = Int64GetDatumFast(new.min_value);
162 163
				break;
			case SEQ_COL_CACHE:
164
				typnam->typeid = INT8OID;
165
				coldef->colname = "cache_value";
166
				value[i - 1] = Int64GetDatumFast(new.cache_value);
167
				break;
V
Vadim B. Mikheev 已提交
168
			case SEQ_COL_LOG:
169
				typnam->typeid = INT8OID;
V
Vadim B. Mikheev 已提交
170
				coldef->colname = "log_cnt";
171
				value[i - 1] = Int64GetDatum((int64) 1);
V
Vadim B. Mikheev 已提交
172
				break;
173
			case SEQ_COL_CYCLE:
174
				typnam->typeid = BOOLOID;
175
				coldef->colname = "is_cycled";
176
				value[i - 1] = BoolGetDatum(new.is_cycled);
177 178
				break;
			case SEQ_COL_CALLED:
179
				typnam->typeid = BOOLOID;
180
				coldef->colname = "is_called";
181
				value[i - 1] = BoolGetDatum(false);
182
				break;
183 184 185 186
		}
		stmt->tableElts = lappend(stmt->tableElts, coldef);
	}

187 188
	stmt->relation = seq->sequence;
	stmt->inhRelations = NIL;
189
	stmt->constraints = NIL;
190
	stmt->hasoids = MUST_NOT_HAVE_OIDS;
191
	stmt->oncommit = ONCOMMIT_NOOP;
192
	stmt->tablespacename = NULL;
193

194
	seqoid = DefineRelation(stmt, RELKIND_SEQUENCE);
195

196
	rel = heap_open(seqoid, AccessExclusiveLock);
197
	tupDesc = RelationGetDescr(rel);
198

199 200
	/* Initialize first page of relation with special magic number */

201
	buf = ReadBuffer(rel, P_NEW);
202 203
	Assert(BufferGetBlockNumber(buf) == 0);

204 205 206 207 208 209
	page = (PageHeader) BufferGetPage(buf);

	PageInit((Page) page, BufferGetPageSize(buf), sizeof(sequence_magic));
	sm = (sequence_magic *) PageGetSpecialPointer(page);
	sm->magic = SEQ_MAGIC;

210 211 212
	/* hack: ensure heap_insert will insert on the just-created page */
	rel->rd_targblock = 0;

213
	/* Now form & insert sequence tuple */
214
	tuple = heap_formtuple(tupDesc, value, null);
215
	simple_heap_insert(rel, tuple);
216

217 218
	Assert(ItemPointerGetOffsetNumber(&(tuple->t_self)) == FirstOffsetNumber);

219
	/*
220 221
	 * Two special hacks here:
	 *
B
Bruce Momjian 已提交
222 223
	 * 1. Since VACUUM does not process sequences, we have to force the tuple to
	 * have xmin = FrozenTransactionId now.  Otherwise it would become
B
Bruce Momjian 已提交
224
	 * invisible to SELECTs after 2G transactions.	It is okay to do this
225 226 227
	 * because if the current transaction aborts, no other xact will ever
	 * examine the sequence tuple anyway.
	 *
B
Bruce Momjian 已提交
228 229 230 231 232
	 * 2. Even though heap_insert emitted a WAL log record, we have to emit an
	 * XLOG_SEQ_LOG record too, since (a) the heap_insert record will not have
	 * the right xmin, and (b) REDO of the heap_insert record would re-init
	 * page and sequence magic number would be lost.  This means two log
	 * records instead of one :-(
233
	 */
234
	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
235

236
	START_CRIT_SECTION();
237 238 239

	{
		/*
B
Bruce Momjian 已提交
240 241 242 243 244
		 * Note that the "tuple" structure is still just a local tuple record
		 * created by heap_formtuple; its t_data pointer doesn't point at the
		 * disk buffer.  To scribble on the disk buffer we need to fetch the
		 * item pointer.  But do the same to the local tuple, since that will
		 * be the source for the WAL log record, below.
245 246 247 248 249 250 251
		 */
		ItemId		itemId;
		Item		item;

		itemId = PageGetItemId((Page) page, FirstOffsetNumber);
		item = PageGetItem((Page) page, itemId);

252
		HeapTupleHeaderSetXmin((HeapTupleHeader) item, FrozenTransactionId);
253 254
		((HeapTupleHeader) item)->t_infomask |= HEAP_XMIN_COMMITTED;

255
		HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
256 257 258
		tuple->t_data->t_infomask |= HEAP_XMIN_COMMITTED;
	}

259 260
	/* XLOG stuff */
	if (!rel->rd_istemp)
261
	{
262 263 264 265
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
		XLogRecData rdata[2];
		Form_pg_sequence newseq = (Form_pg_sequence) GETSTRUCT(tuple);
266 267

		/* We do not log first nextval call, so "advance" sequence here */
268
		/* Note we are scribbling on local tuple, not the disk buffer */
269
		newseq->is_called = true;
270 271 272 273 274
		newseq->log_cnt = 0;

		xlrec.node = rel->rd_node;
		rdata[0].data = (char *) &xlrec;
		rdata[0].len = sizeof(xl_seq_rec);
275
		rdata[0].buffer = InvalidBuffer;
276 277
		rdata[0].next = &(rdata[1]);

278
		rdata[1].data = (char *) tuple->t_data;
279
		rdata[1].len = tuple->t_len;
280
		rdata[1].buffer = InvalidBuffer;
281 282 283 284 285
		rdata[1].next = NULL;

		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG | XLOG_NO_TRAN, rdata);

		PageSetLSN(page, recptr);
286
		PageSetTLI(page, ThisTimeLineID);
287
	}
288

289
	END_CRIT_SECTION();
290

291
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
292 293
	WriteBuffer(buf);
	heap_close(rel, NoLock);
294 295
}

B
Bruce Momjian 已提交
296 297 298
/*
 * AlterSequence
 *
299
 * Modify the definition of a sequence relation
B
Bruce Momjian 已提交
300 301
 */
void
302
AlterSequence(AlterSeqStmt *stmt)
B
Bruce Momjian 已提交
303
{
304
	Oid			relid;
B
Bruce Momjian 已提交
305 306 307 308 309 310 311 312
	SeqTable	elm;
	Relation	seqrel;
	Buffer		buf;
	Page		page;
	Form_pg_sequence seq;
	FormData_pg_sequence new;

	/* open and AccessShareLock sequence */
313 314
	relid = RangeVarGetRelid(stmt->sequence, false);
	init_sequence(relid, &elm, &seqrel);
B
Bruce Momjian 已提交
315

316
	/* allow ALTER to sequence owner only */
B
Bruce Momjian 已提交
317
	if (!pg_class_ownercheck(elm->relid, GetUserId()))
318 319
		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
					   stmt->sequence->relname);
B
Bruce Momjian 已提交
320 321

	/* lock page' buffer and read tuple into new sequence structure */
322
	seq = read_info(elm, seqrel, &buf);
B
Bruce Momjian 已提交
323 324
	page = BufferGetPage(buf);

325 326
	/* Copy old values of options into workspace */
	memcpy(&new, seq, sizeof(FormData_pg_sequence));
B
Bruce Momjian 已提交
327

328 329
	/* Check and set new values */
	init_params(stmt->options, &new, false);
B
Bruce Momjian 已提交
330

331
	/* Now okay to update the on-disk tuple */
332
	memcpy(seq, &new, sizeof(FormData_pg_sequence));
B
Bruce Momjian 已提交
333

334
	/* Clear local cache so that we don't think we have cached numbers */
B
Bruce Momjian 已提交
335
	elm->last = new.last_value; /* last returned number */
B
Bruce Momjian 已提交
336 337
	elm->cached = new.last_value;		/* last cached number (forget cached
										 * values) */
338

B
Bruce Momjian 已提交
339 340 341 342 343 344 345 346 347 348 349 350
	START_CRIT_SECTION();

	/* XLOG stuff */
	if (!seqrel->rd_istemp)
	{
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
		XLogRecData rdata[2];

		xlrec.node = seqrel->rd_node;
		rdata[0].data = (char *) &xlrec;
		rdata[0].len = sizeof(xl_seq_rec);
351
		rdata[0].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
352 353 354 355 356
		rdata[0].next = &(rdata[1]);

		rdata[1].data = (char *) page + ((PageHeader) page)->pd_upper;
		rdata[1].len = ((PageHeader) page)->pd_special -
			((PageHeader) page)->pd_upper;
357
		rdata[1].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
358 359 360 361 362
		rdata[1].next = NULL;

		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG | XLOG_NO_TRAN, rdata);

		PageSetLSN(page, recptr);
363
		PageSetTLI(page, ThisTimeLineID);
B
Bruce Momjian 已提交
364 365 366 367 368 369 370 371 372 373 374
	}

	END_CRIT_SECTION();

	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

	WriteBuffer(buf);

	relation_close(seqrel, NoLock);
}

375

376 377 378 379 380
/*
 * Note: nextval with a text argument is no longer exported as a pg_proc
 * entry, but we keep it around to ease porting of C code that may have
 * called the function directly.
 */
381 382
Datum
nextval(PG_FUNCTION_ARGS)
383
{
384
	text	   *seqin = PG_GETARG_TEXT_P(0);
385
	RangeVar   *sequence;
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
	Oid			relid;

	sequence = makeRangeVarFromNameList(textToQualifiedNameList(seqin));
	relid = RangeVarGetRelid(sequence, false);

	PG_RETURN_INT64(nextval_internal(relid));
}

Datum
nextval_oid(PG_FUNCTION_ARGS)
{
	Oid			relid = PG_GETARG_OID(0);

	PG_RETURN_INT64(nextval_internal(relid));
}

static int64
nextval_internal(Oid relid)
{
405
	SeqTable	elm;
406
	Relation	seqrel;
407
	Buffer		buf;
408
	Page		page;
409
	Form_pg_sequence seq;
410
	int64		incby,
411 412
				maxv,
				minv,
V
Vadim B. Mikheev 已提交
413 414 415 416
				cache,
				log,
				fetch,
				last;
417
	int64		result,
418 419
				next,
				rescnt = 0;
V
Vadim B. Mikheev 已提交
420
	bool		logit = false;
421

V
Vadim B. Mikheev 已提交
422
	/* open and AccessShareLock sequence */
423
	init_sequence(relid, &elm, &seqrel);
424

425
	if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_UPDATE) != ACLCHECK_OK)
426 427
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
428
				 errmsg("permission denied for sequence %s",
429
						RelationGetRelationName(seqrel))));
430 431 432

	if (elm->last != elm->cached)		/* some numbers were cached */
	{
433
		last_used_seq = elm;
434
		elm->last += elm->increment;
435
		relation_close(seqrel, NoLock);
436
		return elm->last;
437
	}
438

439
	/* lock page' buffer and read tuple */
440
	seq = read_info(elm, seqrel, &buf);
441
	page = BufferGetPage(buf);
442

V
Vadim B. Mikheev 已提交
443
	last = next = result = seq->last_value;
444 445 446
	incby = seq->increment_by;
	maxv = seq->max_value;
	minv = seq->min_value;
V
Vadim B. Mikheev 已提交
447 448
	fetch = cache = seq->cache_value;
	log = seq->log_cnt;
449

450
	if (!seq->is_called)
V
Vadim B. Mikheev 已提交
451
	{
452
		rescnt++;				/* last_value if not called */
V
Vadim B. Mikheev 已提交
453 454 455
		fetch--;
		log--;
	}
456

457
	/*
B
Bruce Momjian 已提交
458 459 460
	 * Decide whether we should emit a WAL log record.	If so, force up the
	 * fetch count to grab SEQ_LOG_VALS more values than we actually need to
	 * cache.  (These will then be usable without logging.)
461
	 *
B
Bruce Momjian 已提交
462 463 464 465
	 * If this is the first nextval after a checkpoint, we must force a new WAL
	 * record to be written anyway, else replay starting from the checkpoint
	 * would fail to advance the sequence past the logged values.  In this
	 * case we may as well fetch extra values.
466
	 */
V
Vadim B. Mikheev 已提交
467 468
	if (log < fetch)
	{
469 470
		/* forced log to satisfy local demand for values */
		fetch = log = fetch + SEQ_LOG_VALS;
V
Vadim B. Mikheev 已提交
471 472
		logit = true;
	}
473 474 475 476 477 478 479 480 481 482 483
	else
	{
		XLogRecPtr	redoptr = GetRedoRecPtr();

		if (XLByteLE(PageGetLSN(page), redoptr))
		{
			/* last update of seq was before checkpoint */
			fetch = log = fetch + SEQ_LOG_VALS;
			logit = true;
		}
	}
V
Vadim B. Mikheev 已提交
484

B
Bruce Momjian 已提交
485
	while (fetch)				/* try to fetch cache [+ log ] numbers */
486
	{
487
		/*
B
Bruce Momjian 已提交
488 489
		 * Check MAXVALUE for ascending sequences and MINVALUE for descending
		 * sequences
490
		 */
491
		if (incby > 0)
492
		{
493
			/* ascending sequence */
494 495 496 497
			if ((maxv >= 0 && next > maxv - incby) ||
				(maxv < 0 && next + incby > maxv))
			{
				if (rescnt > 0)
V
Vadim B. Mikheev 已提交
498
					break;		/* stop fetching */
499
				if (!seq->is_cycled)
500
				{
B
Bruce Momjian 已提交
501 502
					char		buf[100];

503
					snprintf(buf, sizeof(buf), INT64_FORMAT, maxv);
504
					ereport(ERROR,
B
Bruce Momjian 已提交
505 506 507
						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						   errmsg("nextval: reached maximum value of sequence \"%s\" (%s)",
								  RelationGetRelationName(seqrel), buf)));
508
				}
509 510 511 512 513 514 515
				next = minv;
			}
			else
				next += incby;
		}
		else
		{
516
			/* descending sequence */
517 518 519 520
			if ((minv < 0 && next < minv - incby) ||
				(minv >= 0 && next + incby < minv))
			{
				if (rescnt > 0)
V
Vadim B. Mikheev 已提交
521
					break;		/* stop fetching */
522
				if (!seq->is_cycled)
523
				{
B
Bruce Momjian 已提交
524 525
					char		buf[100];

526
					snprintf(buf, sizeof(buf), INT64_FORMAT, minv);
527
					ereport(ERROR,
B
Bruce Momjian 已提交
528 529 530
						  (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						   errmsg("nextval: reached minimum value of sequence \"%s\" (%s)",
								  RelationGetRelationName(seqrel), buf)));
531
				}
532 533 534 535 536
				next = maxv;
			}
			else
				next += incby;
		}
V
Vadim B. Mikheev 已提交
537 538 539 540 541 542
		fetch--;
		if (rescnt < cache)
		{
			log--;
			rescnt++;
			last = next;
B
Bruce Momjian 已提交
543 544
			if (rescnt == 1)	/* if it's first result - */
				result = next;	/* it's what to return */
V
Vadim B. Mikheev 已提交
545
		}
546 547
	}

548 549 550
	log -= fetch;				/* adjust for any unfetched numbers */
	Assert(log >= 0);

551 552
	/* save info in local cache */
	elm->last = result;			/* last returned number */
V
Vadim B. Mikheev 已提交
553 554
	elm->cached = last;			/* last fetched number */

555 556
	last_used_seq = elm;

557
	START_CRIT_SECTION();
558 559 560

	/* XLOG stuff */
	if (logit && !seqrel->rd_istemp)
V
Vadim B. Mikheev 已提交
561 562 563
	{
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
B
Bruce Momjian 已提交
564
		XLogRecData rdata[2];
V
Vadim B. Mikheev 已提交
565

566
		xlrec.node = seqrel->rd_node;
B
Bruce Momjian 已提交
567
		rdata[0].data = (char *) &xlrec;
568
		rdata[0].len = sizeof(xl_seq_rec);
569
		rdata[0].buffer = InvalidBuffer;
570 571
		rdata[0].next = &(rdata[1]);

572
		/* set values that will be saved in xlog */
573
		seq->last_value = next;
574
		seq->is_called = true;
575
		seq->log_cnt = 0;
576

B
Bruce Momjian 已提交
577 578 579
		rdata[1].data = (char *) page + ((PageHeader) page)->pd_upper;
		rdata[1].len = ((PageHeader) page)->pd_special -
			((PageHeader) page)->pd_upper;
580
		rdata[1].buffer = InvalidBuffer;
581 582
		rdata[1].next = NULL;

B
Bruce Momjian 已提交
583
		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG | XLOG_NO_TRAN, rdata);
V
Vadim B. Mikheev 已提交
584

585
		PageSetLSN(page, recptr);
586
		PageSetTLI(page, ThisTimeLineID);
V
Vadim B. Mikheev 已提交
587
	}
588

589
	/* update on-disk data */
V
Vadim B. Mikheev 已提交
590
	seq->last_value = last;		/* last fetched number */
591
	seq->is_called = true;
V
Vadim B. Mikheev 已提交
592
	seq->log_cnt = log;			/* how much is logged */
593

594
	END_CRIT_SECTION();
595

V
Vadim B. Mikheev 已提交
596 597
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

B
Bruce Momjian 已提交
598
	WriteBuffer(buf);
599

600 601
	relation_close(seqrel, NoLock);

602
	return result;
603 604
}

605
Datum
606
currval_oid(PG_FUNCTION_ARGS)
607
{
608 609
	Oid			relid = PG_GETARG_OID(0);
	int64		result;
610
	SeqTable	elm;
611
	Relation	seqrel;
612

V
Vadim B. Mikheev 已提交
613
	/* open and AccessShareLock sequence */
614
	init_sequence(relid, &elm, &seqrel);
615

616
	if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
617 618
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
619
				 errmsg("permission denied for sequence %s",
620
						RelationGetRelationName(seqrel))));
621

622
	if (elm->increment == 0)	/* nextval/read_info were not called */
623 624
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
625
				 errmsg("currval of sequence \"%s\" is not yet defined in this session",
626
						RelationGetRelationName(seqrel))));
627 628 629

	result = elm->last;

630 631
	relation_close(seqrel, NoLock);

632
	PG_RETURN_INT64(result);
633 634
}

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
Datum
lastval(PG_FUNCTION_ARGS)
{
	Relation	seqrel;
	int64		result;

	if (last_used_seq == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("lastval is not yet defined in this session")));

	/* Someone may have dropped the sequence since the last nextval() */
	if (!SearchSysCacheExists(RELOID,
							  ObjectIdGetDatum(last_used_seq->relid),
							  0, 0, 0))
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("lastval is not yet defined in this session")));

	seqrel = relation_open(last_used_seq->relid, NoLock);
	acquire_share_lock(seqrel, last_used_seq);

	/* nextval() must have already been called for this sequence */
	Assert(last_used_seq->increment != 0);

	if (pg_class_aclcheck(last_used_seq->relid, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied for sequence %s",
						RelationGetRelationName(seqrel))));

	result = last_used_seq->last;
	relation_close(seqrel, NoLock);
668

669 670 671
	PG_RETURN_INT64(result);
}

B
Bruce Momjian 已提交
672
/*
673 674 675 676
 * Main internal procedure that handles 2 & 3 arg forms of SETVAL.
 *
 * Note that the 3 arg version (which sets the is_called flag) is
 * only for use in pg_dump, and setting the is_called flag may not
B
Bruce Momjian 已提交
677
 * work if multiple users are attached to the database and referencing
678 679
 * the sequence (unlikely if pg_dump is restoring it).
 *
B
Bruce Momjian 已提交
680
 * It is necessary to have the 3 arg version so that pg_dump can
681 682 683 684
 * restore the state of a sequence exactly during data-only restores -
 * it is the only way to clear the is_called flag in an existing
 * sequence.
 */
B
Bruce Momjian 已提交
685
static void
686
do_setval(Oid relid, int64 next, bool iscalled)
M
 
Marc G. Fournier 已提交
687 688
{
	SeqTable	elm;
689
	Relation	seqrel;
690
	Buffer		buf;
691
	Form_pg_sequence seq;
M
 
Marc G. Fournier 已提交
692

693
	/* open and AccessShareLock sequence */
694
	init_sequence(relid, &elm, &seqrel);
695 696

	if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_UPDATE) != ACLCHECK_OK)
697 698
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
699
				 errmsg("permission denied for sequence %s",
700
						RelationGetRelationName(seqrel))));
M
 
Marc G. Fournier 已提交
701

702
	/* lock page' buffer and read tuple */
703
	seq = read_info(elm, seqrel, &buf);
M
 
Marc G. Fournier 已提交
704

705
	if ((next < seq->min_value) || (next > seq->max_value))
706
	{
B
Bruce Momjian 已提交
707 708 709 710
		char		bufv[100],
					bufm[100],
					bufx[100];

711 712 713
		snprintf(bufv, sizeof(bufv), INT64_FORMAT, next);
		snprintf(bufm, sizeof(bufm), INT64_FORMAT, seq->min_value);
		snprintf(bufx, sizeof(bufx), INT64_FORMAT, seq->max_value);
714 715
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
716
				 errmsg("setval: value %s is out of bounds for sequence \"%s\" (%s..%s)",
717 718
						bufv, RelationGetRelationName(seqrel),
						bufm, bufx)));
719
	}
M
 
Marc G. Fournier 已提交
720 721 722

	/* save info in local cache */
	elm->last = next;			/* last returned number */
B
Bruce Momjian 已提交
723
	elm->cached = next;			/* last cached number (forget cached values) */
M
 
Marc G. Fournier 已提交
724

725
	START_CRIT_SECTION();
726 727 728

	/* XLOG stuff */
	if (!seqrel->rd_istemp)
V
Vadim B. Mikheev 已提交
729 730 731
	{
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
B
Bruce Momjian 已提交
732
		XLogRecData rdata[2];
733
		Page		page = BufferGetPage(buf);
V
Vadim B. Mikheev 已提交
734

735
		xlrec.node = seqrel->rd_node;
B
Bruce Momjian 已提交
736
		rdata[0].data = (char *) &xlrec;
737
		rdata[0].len = sizeof(xl_seq_rec);
738
		rdata[0].buffer = InvalidBuffer;
739 740
		rdata[0].next = &(rdata[1]);

741
		/* set values that will be saved in xlog */
742
		seq->last_value = next;
743
		seq->is_called = true;
744
		seq->log_cnt = 0;
745

B
Bruce Momjian 已提交
746 747 748
		rdata[1].data = (char *) page + ((PageHeader) page)->pd_upper;
		rdata[1].len = ((PageHeader) page)->pd_special -
			((PageHeader) page)->pd_upper;
749
		rdata[1].buffer = InvalidBuffer;
750 751
		rdata[1].next = NULL;

B
Bruce Momjian 已提交
752
		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG | XLOG_NO_TRAN, rdata);
753 754

		PageSetLSN(page, recptr);
755
		PageSetTLI(page, ThisTimeLineID);
V
Vadim B. Mikheev 已提交
756
	}
757

758 759
	/* save info in sequence relation */
	seq->last_value = next;		/* last fetched number */
760
	seq->is_called = iscalled;
761
	seq->log_cnt = (iscalled) ? 0 : 1;
762

763
	END_CRIT_SECTION();
M
 
Marc G. Fournier 已提交
764

V
Vadim B. Mikheev 已提交
765 766
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

B
Bruce Momjian 已提交
767
	WriteBuffer(buf);
768 769

	relation_close(seqrel, NoLock);
770 771
}

772 773 774 775
/*
 * Implement the 2 arg setval procedure.
 * See do_setval for discussion.
 */
776
Datum
777
setval_oid(PG_FUNCTION_ARGS)
778
{
779
	Oid			relid = PG_GETARG_OID(0);
780
	int64		next = PG_GETARG_INT64(1);
781

782
	do_setval(relid, next, true);
783

784
	PG_RETURN_INT64(next);
785 786
}

787 788 789 790
/*
 * Implement the 3 arg setval procedure.
 * See do_setval for discussion.
 */
791
Datum
792
setval3_oid(PG_FUNCTION_ARGS)
793
{
794
	Oid			relid = PG_GETARG_OID(0);
795
	int64		next = PG_GETARG_INT64(1);
796 797
	bool		iscalled = PG_GETARG_BOOL(2);

798
	do_setval(relid, next, iscalled);
799

800
	PG_RETURN_INT64(next);
M
 
Marc G. Fournier 已提交
801 802
}

803

804 805
/*
 * If we haven't touched the sequence already in this transaction,
B
Bruce Momjian 已提交
806
 * we need to acquire AccessShareLock.	We arrange for the lock to
807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838
 * be owned by the top transaction, so that we don't need to do it
 * more than once per xact.
 */
static void
acquire_share_lock(Relation seqrel, SeqTable seq)
{
	TransactionId thisxid = GetTopTransactionId();

	if (seq->xid != thisxid)
	{
		ResourceOwner currentOwner;

		currentOwner = CurrentResourceOwner;
		PG_TRY();
		{
			CurrentResourceOwner = TopTransactionResourceOwner;
			LockRelation(seqrel, AccessShareLock);
		}
		PG_CATCH();
		{
			/* Ensure CurrentResourceOwner is restored on error */
			CurrentResourceOwner = currentOwner;
			PG_RE_THROW();
		}
		PG_END_TRY();
		CurrentResourceOwner = currentOwner;

		/* Flag that we have a lock in the current xact. */
		seq->xid = thisxid;
	}
}

839
/*
840
 * Given a relation OID, open and lock the sequence.  p_elm and p_rel are
841 842 843
 * output parameters.
 */
static void
844
init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
845
{
846
	Relation	seqrel;
847
	volatile SeqTable elm;
B
Bruce Momjian 已提交
848

849
	/*
850
	 * Open the sequence relation.
851
	 */
852
	seqrel = relation_open(relid, NoLock);
853

854
	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
855 856 857
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
858
						RelationGetRelationName(seqrel))));
859

860 861 862 863 864 865 866
	/* Look to see if we already have a seqtable entry for relation */
	for (elm = seqtab; elm != NULL; elm = elm->next)
	{
		if (elm->relid == relid)
			break;
	}

867
	/*
868
	 * Allocate new seqtable entry if we didn't find one.
869
	 *
B
Bruce Momjian 已提交
870 871 872
	 * NOTE: seqtable entries remain in the list for the life of a backend. If
	 * the sequence itself is deleted then the entry becomes wasted memory,
	 * but it's small enough that this should not matter.
B
Bruce Momjian 已提交
873
	 */
874
	if (elm == NULL)
875
	{
876
		/*
B
Bruce Momjian 已提交
877 878
		 * Time to make a new seqtable entry.  These entries live as long as
		 * the backend does, so we use plain malloc for them.
879 880
		 */
		elm = (SeqTable) malloc(sizeof(SeqTableData));
T
Tom Lane 已提交
881
		if (elm == NULL)
882 883 884
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
885
		elm->relid = relid;
886
		elm->xid = InvalidTransactionId;
887 888 889 890
		/* increment is set to 0 until we do read_info (see currval) */
		elm->last = elm->cached = elm->increment = 0;
		elm->next = seqtab;
		seqtab = elm;
891 892
	}

893
	acquire_share_lock(seqrel, elm);
894 895 896

	*p_elm = elm;
	*p_rel = seqrel;
897 898 899
}


900 901
/* Given an opened relation, lock the page buffer and find the tuple */
static Form_pg_sequence
902
read_info(SeqTable elm, Relation rel, Buffer *buf)
903
{
904 905 906 907 908
	PageHeader	page;
	ItemId		lp;
	HeapTupleData tuple;
	sequence_magic *sm;
	Form_pg_sequence seq;
909

910 911 912 913 914 915 916
	*buf = ReadBuffer(rel, 0);
	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);

	page = (PageHeader) BufferGetPage(*buf);
	sm = (sequence_magic *) PageGetSpecialPointer(page);

	if (sm->magic != SEQ_MAGIC)
917 918
		elog(ERROR, "bad magic number in sequence \"%s\": %08X",
			 RelationGetRelationName(rel), sm->magic);
919 920 921 922 923 924 925 926 927 928

	lp = PageGetItemId(page, FirstOffsetNumber);
	Assert(ItemIdIsUsed(lp));
	tuple.t_data = (HeapTupleHeader) PageGetItem((Page) page, lp);

	seq = (Form_pg_sequence) GETSTRUCT(&tuple);

	elm->increment = seq->increment_by;

	return seq;
929 930
}

931 932 933 934 935 936 937
/*
 * init_params: process the options list of CREATE or ALTER SEQUENCE,
 * and store the values into appropriate fields of *new.
 *
 * If isInit is true, fill any unspecified options with default values;
 * otherwise, do not change existing options that aren't explicitly overridden.
 */
938
static void
939
init_params(List *options, Form_pg_sequence new, bool isInit)
940
{
941 942 943 944 945
	DefElem    *last_value = NULL;
	DefElem    *increment_by = NULL;
	DefElem    *max_value = NULL;
	DefElem    *min_value = NULL;
	DefElem    *cache_value = NULL;
946
	DefElem    *is_cycled = NULL;
947
	ListCell   *option;
948

B
Bruce Momjian 已提交
949
	foreach(option, options)
950
	{
951
		DefElem    *defel = (DefElem *) lfirst(option);
952

953
		if (strcmp(defel->defname, "increment") == 0)
954 955
		{
			if (increment_by)
956 957 958
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
959
			increment_by = defel;
960
		}
B
Bruce Momjian 已提交
961

B
Bruce Momjian 已提交
962
		/*
B
Bruce Momjian 已提交
963
		 * start is for a new sequence restart is for alter
B
Bruce Momjian 已提交
964
		 */
965 966
		else if (strcmp(defel->defname, "start") == 0 ||
				 strcmp(defel->defname, "restart") == 0)
967 968
		{
			if (last_value)
969 970 971
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
972
			last_value = defel;
973
		}
974
		else if (strcmp(defel->defname, "maxvalue") == 0)
975 976
		{
			if (max_value)
977 978 979
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
980
			max_value = defel;
981
		}
982
		else if (strcmp(defel->defname, "minvalue") == 0)
983 984
		{
			if (min_value)
985 986 987
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
988
			min_value = defel;
989
		}
990
		else if (strcmp(defel->defname, "cache") == 0)
991 992
		{
			if (cache_value)
993 994 995
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
996
			cache_value = defel;
997
		}
998
		else if (strcmp(defel->defname, "cycle") == 0)
999
		{
1000
			if (is_cycled)
1001 1002 1003
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("conflicting or redundant options")));
1004
			is_cycled = defel;
1005
		}
1006
		else
1007
			elog(ERROR, "option \"%s\" not recognized",
1008 1009 1010
				 defel->defname);
	}

B
Bruce Momjian 已提交
1011
	/* INCREMENT BY */
1012
	if (increment_by != NULL)
B
Bruce Momjian 已提交
1013 1014
	{
		new->increment_by = defGetInt64(increment_by);
1015 1016 1017
		if (new->increment_by == 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1018
					 errmsg("INCREMENT must not be zero")));
B
Bruce Momjian 已提交
1019
	}
1020 1021 1022 1023
	else if (isInit)
		new->increment_by = 1;

	/* CYCLE */
1024
	if (is_cycled != NULL)
1025 1026 1027 1028 1029 1030
	{
		new->is_cycled = intVal(is_cycled->arg);
		Assert(new->is_cycled == false || new->is_cycled == true);
	}
	else if (isInit)
		new->is_cycled = false;
1031

1032
	/* MAXVALUE (null arg means NO MAXVALUE) */
1033
	if (max_value != NULL && max_value->arg)
1034
		new->max_value = defGetInt64(max_value);
1035
	else if (isInit || max_value != NULL)
1036
	{
1037
		if (new->increment_by > 0)
B
Bruce Momjian 已提交
1038
			new->max_value = SEQ_MAXVALUE;		/* ascending seq */
1039
		else
B
Bruce Momjian 已提交
1040
			new->max_value = -1;	/* descending seq */
1041
	}
1042

1043
	/* MINVALUE (null arg means NO MINVALUE) */
1044
	if (min_value != NULL && min_value->arg)
1045
		new->min_value = defGetInt64(min_value);
1046
	else if (isInit || min_value != NULL)
1047
	{
1048
		if (new->increment_by > 0)
B
Bruce Momjian 已提交
1049
			new->min_value = 1; /* ascending seq */
1050
		else
B
Bruce Momjian 已提交
1051
			new->min_value = SEQ_MINVALUE;		/* descending seq */
1052
	}
1053

1054
	/* crosscheck min/max */
1055
	if (new->min_value >= new->max_value)
1056
	{
B
Bruce Momjian 已提交
1057 1058 1059
		char		bufm[100],
					bufx[100];

1060 1061
		snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->min_value);
		snprintf(bufx, sizeof(bufx), INT64_FORMAT, new->max_value);
1062 1063 1064 1065
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("MINVALUE (%s) must be less than MAXVALUE (%s)",
						bufm, bufx)));
1066
	}
1067

B
Bruce Momjian 已提交
1068
	/* START WITH */
1069
	if (last_value != NULL)
1070
	{
1071
		new->last_value = defGetInt64(last_value);
1072 1073 1074
		new->is_called = false;
		new->log_cnt = 1;
	}
1075
	else if (isInit)
1076
	{
1077 1078 1079 1080
		if (new->increment_by > 0)
			new->last_value = new->min_value;	/* ascending seq */
		else
			new->last_value = new->max_value;	/* descending seq */
1081 1082
		new->is_called = false;
		new->log_cnt = 1;
1083
	}
1084

1085
	/* crosscheck */
1086
	if (new->last_value < new->min_value)
1087
	{
B
Bruce Momjian 已提交
1088 1089 1090
		char		bufs[100],
					bufm[100];

1091 1092
		snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->last_value);
		snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->min_value);
1093 1094
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
B
Bruce Momjian 已提交
1095 1096
				 errmsg("START value (%s) can't be less than MINVALUE (%s)",
						bufs, bufm)));
1097
	}
1098
	if (new->last_value > new->max_value)
1099
	{
B
Bruce Momjian 已提交
1100 1101 1102
		char		bufs[100],
					bufm[100];

1103 1104
		snprintf(bufs, sizeof(bufs), INT64_FORMAT, new->last_value);
		snprintf(bufm, sizeof(bufm), INT64_FORMAT, new->max_value);
1105 1106
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
B
Bruce Momjian 已提交
1107 1108
			   errmsg("START value (%s) can't be greater than MAXVALUE (%s)",
					  bufs, bufm)));
1109
	}
1110

B
Bruce Momjian 已提交
1111
	/* CACHE */
1112
	if (cache_value != NULL)
1113
	{
1114 1115 1116 1117
		new->cache_value = defGetInt64(cache_value);
		if (new->cache_value <= 0)
		{
			char		buf[100];
B
Bruce Momjian 已提交
1118

1119 1120 1121 1122 1123 1124
			snprintf(buf, sizeof(buf), INT64_FORMAT, new->cache_value);
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("CACHE (%s) must be greater than zero",
							buf)));
		}
1125
	}
1126 1127
	else if (isInit)
		new->cache_value = 1;
1128 1129
}

V
Vadim B. Mikheev 已提交
1130

B
Bruce Momjian 已提交
1131 1132
void
seq_redo(XLogRecPtr lsn, XLogRecord *record)
V
Vadim B. Mikheev 已提交
1133
{
B
Bruce Momjian 已提交
1134 1135 1136 1137 1138 1139 1140
	uint8		info = record->xl_info & ~XLR_INFO_MASK;
	Relation	reln;
	Buffer		buffer;
	Page		page;
	char	   *item;
	Size		itemsz;
	xl_seq_rec *xlrec = (xl_seq_rec *) XLogRecGetData(record);
1141
	sequence_magic *sm;
V
Vadim B. Mikheev 已提交
1142

1143
	if (info != XLOG_SEQ_LOG)
1144
		elog(PANIC, "seq_redo: unknown op code %u", info);
V
Vadim B. Mikheev 已提交
1145

1146
	reln = XLogOpenRelation(xlrec->node);
V
Vadim B. Mikheev 已提交
1147 1148 1149
	if (!RelationIsValid(reln))
		return;

1150
	buffer = XLogReadBuffer(true, reln, 0);
V
Vadim B. Mikheev 已提交
1151
	if (!BufferIsValid(buffer))
1152
		elog(PANIC, "seq_redo: can't read block 0 of rel %u/%u/%u",
B
Bruce Momjian 已提交
1153
			 xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
V
Vadim B. Mikheev 已提交
1154 1155 1156

	page = (Page) BufferGetPage(buffer);

1157 1158
	/* Always reinit the page and reinstall the magic number */
	/* See comments in DefineSequence */
1159 1160 1161
	PageInit((Page) page, BufferGetPageSize(buffer), sizeof(sequence_magic));
	sm = (sequence_magic *) PageGetSpecialPointer(page);
	sm->magic = SEQ_MAGIC;
V
Vadim B. Mikheev 已提交
1162

B
Bruce Momjian 已提交
1163
	item = (char *) xlrec + sizeof(xl_seq_rec);
1164 1165
	itemsz = record->xl_len - sizeof(xl_seq_rec);
	itemsz = MAXALIGN(itemsz);
B
Bruce Momjian 已提交
1166
	if (PageAddItem(page, (Item) item, itemsz,
1167
					FirstOffsetNumber, LP_USED) == InvalidOffsetNumber)
1168
		elog(PANIC, "seq_redo: failed to add item to page");
V
Vadim B. Mikheev 已提交
1169 1170

	PageSetLSN(page, lsn);
1171
	PageSetTLI(page, ThisTimeLineID);
1172 1173
	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
	WriteBuffer(buffer);
V
Vadim B. Mikheev 已提交
1174 1175
}

B
Bruce Momjian 已提交
1176 1177
void
seq_desc(char *buf, uint8 xl_info, char *rec)
V
Vadim B. Mikheev 已提交
1178
{
B
Bruce Momjian 已提交
1179 1180
	uint8		info = xl_info & ~XLR_INFO_MASK;
	xl_seq_rec *xlrec = (xl_seq_rec *) rec;
V
Vadim B. Mikheev 已提交
1181 1182 1183 1184 1185 1186 1187 1188 1189

	if (info == XLOG_SEQ_LOG)
		strcat(buf, "log: ");
	else
	{
		strcat(buf, "UNKNOWN");
		return;
	}

1190 1191
	sprintf(buf + strlen(buf), "rel %u/%u/%u",
			xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
V
Vadim B. Mikheev 已提交
1192
}