sequence.c 30.4 KB
Newer Older
1 2
/*-------------------------------------------------------------------------
 *
3
 * sequence.c
4
 *	  PostgreSQL sequences support code.
5
 *
6
 * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
7 8 9 10
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
11
 *	  $PostgreSQL: pgsql/src/backend/commands/sequence.c,v 1.131 2006/03/29 21:17:38 tgl 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 "nodes/makefuncs.h"
25
#include "utils/acl.h"
B
Bruce Momjian 已提交
26
#include "utils/builtins.h"
27
#include "utils/resowner.h"
28
#include "utils/syscache.h"
29

30

V
Vadim B. Mikheev 已提交
31
/*
32
 * We don't want to log each fetching of a value from a sequence,
V
Vadim B. Mikheev 已提交
33 34 35
 * 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 已提交
36
#define SEQ_LOG_VALS	32
37

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

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

48 49 50 51 52 53
/*
 * 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 已提交
54
 * XXX We use linear search to find pre-existing SeqTable entries.	This is
55 56 57
 * 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.
 */
58 59
typedef struct SeqTableData
{
60 61 62 63 64 65 66
	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 */
67
} SeqTableData;
68 69 70

typedef SeqTableData *SeqTable;

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

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

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

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

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

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

118 119
		coldef->inhcount = 0;
		coldef->is_local = true;
120
		coldef->is_not_null = true;
121 122
		coldef->raw_default = NULL;
		coldef->cooked_default = NULL;
123 124 125
		coldef->constraints = NIL;
		coldef->support = NULL;

126 127 128 129
		null[i - 1] = ' ';

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

180 181
	stmt->relation = seq->sequence;
	stmt->inhRelations = NIL;
182
	stmt->constraints = NIL;
183
	stmt->hasoids = MUST_NOT_HAVE_OIDS;
184
	stmt->oncommit = ONCOMMIT_NOOP;
185
	stmt->tablespacename = NULL;
186

187
	seqoid = DefineRelation(stmt, RELKIND_SEQUENCE);
188

189
	rel = heap_open(seqoid, AccessExclusiveLock);
190
	tupDesc = RelationGetDescr(rel);
191

192 193
	/* Initialize first page of relation with special magic number */

194
	buf = ReadBuffer(rel, P_NEW);
195 196
	Assert(BufferGetBlockNumber(buf) == 0);

197 198 199 200 201 202
	page = (PageHeader) BufferGetPage(buf);

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

203 204 205
	/* hack: ensure heap_insert will insert on the just-created page */
	rel->rd_targblock = 0;

206
	/* Now form & insert sequence tuple */
207
	tuple = heap_formtuple(tupDesc, value, null);
208
	simple_heap_insert(rel, tuple);
209

210 211
	Assert(ItemPointerGetOffsetNumber(&(tuple->t_self)) == FirstOffsetNumber);

212
	/*
213 214
	 * Two special hacks here:
	 *
215 216
	 * 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 已提交
217
	 * invisible to SELECTs after 2G transactions.	It is okay to do this
218 219 220
	 * because if the current transaction aborts, no other xact will ever
	 * examine the sequence tuple anyway.
	 *
B
Bruce Momjian 已提交
221 222 223 224 225
	 * 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 :-(
226
	 */
227
	LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
228

229
	START_CRIT_SECTION();
230 231 232

	{
		/*
B
Bruce Momjian 已提交
233 234 235 236 237
		 * 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.
238 239 240 241 242 243 244
		 */
		ItemId		itemId;
		Item		item;

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

245
		HeapTupleHeaderSetXmin((HeapTupleHeader) item, FrozenTransactionId);
246 247
		((HeapTupleHeader) item)->t_infomask |= HEAP_XMIN_COMMITTED;

248
		HeapTupleHeaderSetXmin(tuple->t_data, FrozenTransactionId);
249 250 251
		tuple->t_data->t_infomask |= HEAP_XMIN_COMMITTED;
	}

252 253
	/* XLOG stuff */
	if (!rel->rd_istemp)
254
	{
255 256 257 258
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
		XLogRecData rdata[2];
		Form_pg_sequence newseq = (Form_pg_sequence) GETSTRUCT(tuple);
259 260

		/* We do not log first nextval call, so "advance" sequence here */
261
		/* Note we are scribbling on local tuple, not the disk buffer */
262
		newseq->is_called = true;
263 264 265 266 267
		newseq->log_cnt = 0;

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

271
		rdata[1].data = (char *) tuple->t_data;
272
		rdata[1].len = tuple->t_len;
273
		rdata[1].buffer = InvalidBuffer;
274 275 276 277 278
		rdata[1].next = NULL;

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

		PageSetLSN(page, recptr);
279
		PageSetTLI(page, ThisTimeLineID);
280
	}
281

282
	END_CRIT_SECTION();
283

284
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);
285 286
	WriteBuffer(buf);
	heap_close(rel, NoLock);
287 288
}

B
Bruce Momjian 已提交
289 290 291
/*
 * AlterSequence
 *
292
 * Modify the definition of a sequence relation
B
Bruce Momjian 已提交
293 294
 */
void
295
AlterSequence(AlterSeqStmt *stmt)
B
Bruce Momjian 已提交
296
{
297
	Oid			relid;
B
Bruce Momjian 已提交
298 299 300 301 302 303 304 305
	SeqTable	elm;
	Relation	seqrel;
	Buffer		buf;
	Page		page;
	Form_pg_sequence seq;
	FormData_pg_sequence new;

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

309
	/* allow ALTER to sequence owner only */
B
Bruce Momjian 已提交
310
	if (!pg_class_ownercheck(elm->relid, GetUserId()))
311 312
		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
					   stmt->sequence->relname);
B
Bruce Momjian 已提交
313 314

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

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

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

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

327
	/* Clear local cache so that we don't think we have cached numbers */
B
Bruce Momjian 已提交
328
	elm->last = new.last_value; /* last returned number */
B
Bruce Momjian 已提交
329 330
	elm->cached = new.last_value;		/* last cached number (forget cached
										 * values) */
331

B
Bruce Momjian 已提交
332 333 334 335 336 337 338 339 340 341 342 343
	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);
344
		rdata[0].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
345 346 347 348 349
		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;
350
		rdata[1].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
351 352 353 354 355
		rdata[1].next = NULL;

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

		PageSetLSN(page, recptr);
356
		PageSetTLI(page, ThisTimeLineID);
B
Bruce Momjian 已提交
357 358 359 360 361 362 363 364 365 366 367
	}

	END_CRIT_SECTION();

	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

	WriteBuffer(buf);

	relation_close(seqrel, NoLock);
}

368

369 370 371 372 373
/*
 * 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.
 */
374 375
Datum
nextval(PG_FUNCTION_ARGS)
376
{
377
	text	   *seqin = PG_GETARG_TEXT_P(0);
378
	RangeVar   *sequence;
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
	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)
{
398
	SeqTable	elm;
399
	Relation	seqrel;
400
	Buffer		buf;
401
	Page		page;
402
	Form_pg_sequence seq;
403
	int64		incby,
404 405
				maxv,
				minv,
V
Vadim B. Mikheev 已提交
406 407 408 409
				cache,
				log,
				fetch,
				last;
410
	int64		result,
411 412
				next,
				rescnt = 0;
V
Vadim B. Mikheev 已提交
413
	bool		logit = false;
414

V
Vadim B. Mikheev 已提交
415
	/* open and AccessShareLock sequence */
416
	init_sequence(relid, &elm, &seqrel);
417

418 419
	if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_USAGE) != ACLCHECK_OK &&
		pg_class_aclcheck(elm->relid, GetUserId(), ACL_UPDATE) != ACLCHECK_OK)
420 421
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
422
				 errmsg("permission denied for sequence %s",
423
						RelationGetRelationName(seqrel))));
424 425 426

	if (elm->last != elm->cached)		/* some numbers were cached */
	{
427
		last_used_seq = elm;
428
		elm->last += elm->increment;
429
		relation_close(seqrel, NoLock);
430
		return elm->last;
431
	}
432

433
	/* lock page' buffer and read tuple */
434
	seq = read_info(elm, seqrel, &buf);
435
	page = BufferGetPage(buf);
436

V
Vadim B. Mikheev 已提交
437
	last = next = result = seq->last_value;
438 439 440
	incby = seq->increment_by;
	maxv = seq->max_value;
	minv = seq->min_value;
V
Vadim B. Mikheev 已提交
441 442
	fetch = cache = seq->cache_value;
	log = seq->log_cnt;
443

444
	if (!seq->is_called)
V
Vadim B. Mikheev 已提交
445
	{
446
		rescnt++;				/* last_value if not called */
V
Vadim B. Mikheev 已提交
447 448 449
		fetch--;
		log--;
	}
450

451
	/*
B
Bruce Momjian 已提交
452 453 454
	 * 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.)
455
	 *
456 457 458 459
	 * 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.
460
	 */
V
Vadim B. Mikheev 已提交
461 462
	if (log < fetch)
	{
463 464
		/* forced log to satisfy local demand for values */
		fetch = log = fetch + SEQ_LOG_VALS;
V
Vadim B. Mikheev 已提交
465 466
		logit = true;
	}
467 468 469 470 471 472 473 474 475 476 477
	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 已提交
478

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

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

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

542 543 544
	log -= fetch;				/* adjust for any unfetched numbers */
	Assert(log >= 0);

545 546
	/* save info in local cache */
	elm->last = result;			/* last returned number */
V
Vadim B. Mikheev 已提交
547 548
	elm->cached = last;			/* last fetched number */

549 550
	last_used_seq = elm;

551
	START_CRIT_SECTION();
552 553 554

	/* XLOG stuff */
	if (logit && !seqrel->rd_istemp)
V
Vadim B. Mikheev 已提交
555 556 557
	{
		xl_seq_rec	xlrec;
		XLogRecPtr	recptr;
B
Bruce Momjian 已提交
558
		XLogRecData rdata[2];
V
Vadim B. Mikheev 已提交
559

560
		xlrec.node = seqrel->rd_node;
B
Bruce Momjian 已提交
561
		rdata[0].data = (char *) &xlrec;
562
		rdata[0].len = sizeof(xl_seq_rec);
563
		rdata[0].buffer = InvalidBuffer;
564 565
		rdata[0].next = &(rdata[1]);

566
		/* set values that will be saved in xlog */
567
		seq->last_value = next;
568
		seq->is_called = true;
569
		seq->log_cnt = 0;
570

B
Bruce Momjian 已提交
571 572 573
		rdata[1].data = (char *) page + ((PageHeader) page)->pd_upper;
		rdata[1].len = ((PageHeader) page)->pd_special -
			((PageHeader) page)->pd_upper;
574
		rdata[1].buffer = InvalidBuffer;
575 576
		rdata[1].next = NULL;

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

579
		PageSetLSN(page, recptr);
580
		PageSetTLI(page, ThisTimeLineID);
V
Vadim B. Mikheev 已提交
581
	}
582

583
	/* update on-disk data */
V
Vadim B. Mikheev 已提交
584
	seq->last_value = last;		/* last fetched number */
585
	seq->is_called = true;
V
Vadim B. Mikheev 已提交
586
	seq->log_cnt = log;			/* how much is logged */
587

588
	END_CRIT_SECTION();
589

V
Vadim B. Mikheev 已提交
590 591
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

B
Bruce Momjian 已提交
592
	WriteBuffer(buf);
593

594 595
	relation_close(seqrel, NoLock);

596
	return result;
597 598
}

599
Datum
600
currval_oid(PG_FUNCTION_ARGS)
601
{
602 603
	Oid			relid = PG_GETARG_OID(0);
	int64		result;
604
	SeqTable	elm;
605
	Relation	seqrel;
606

V
Vadim B. Mikheev 已提交
607
	/* open and AccessShareLock sequence */
608
	init_sequence(relid, &elm, &seqrel);
609

610 611
	if (pg_class_aclcheck(elm->relid, GetUserId(), ACL_SELECT) != ACLCHECK_OK &&
		pg_class_aclcheck(elm->relid, GetUserId(), ACL_USAGE) != ACLCHECK_OK)
612 613
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
614
				 errmsg("permission denied for sequence %s",
615
						RelationGetRelationName(seqrel))));
616

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

	result = elm->last;

625 626
	relation_close(seqrel, NoLock);

627
	PG_RETURN_INT64(result);
628 629
}

630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
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);

655 656
	if (pg_class_aclcheck(last_used_seq->relid, GetUserId(), ACL_SELECT) != ACLCHECK_OK &&
		pg_class_aclcheck(last_used_seq->relid, GetUserId(), ACL_USAGE) != ACLCHECK_OK)
657 658 659 660 661 662 663
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied for sequence %s",
						RelationGetRelationName(seqrel))));

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

665 666 667
	PG_RETURN_INT64(result);
}

B
Bruce Momjian 已提交
668
/*
669 670 671 672
 * 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 已提交
673
 * work if multiple users are attached to the database and referencing
674 675
 * the sequence (unlikely if pg_dump is restoring it).
 *
B
Bruce Momjian 已提交
676
 * It is necessary to have the 3 arg version so that pg_dump can
677 678 679 680
 * 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 已提交
681
static void
682
do_setval(Oid relid, int64 next, bool iscalled)
M
 
Marc G. Fournier 已提交
683 684
{
	SeqTable	elm;
685
	Relation	seqrel;
686
	Buffer		buf;
687
	Form_pg_sequence seq;
M
 
Marc G. Fournier 已提交
688

689
	/* open and AccessShareLock sequence */
690
	init_sequence(relid, &elm, &seqrel);
691 692

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

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

701
	if ((next < seq->min_value) || (next > seq->max_value))
702
	{
B
Bruce Momjian 已提交
703 704 705 706
		char		bufv[100],
					bufm[100],
					bufx[100];

707 708 709
		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);
710 711
		ereport(ERROR,
				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
712
				 errmsg("setval: value %s is out of bounds for sequence \"%s\" (%s..%s)",
713 714
						bufv, RelationGetRelationName(seqrel),
						bufm, bufx)));
715
	}
M
 
Marc G. Fournier 已提交
716 717 718

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

721
	START_CRIT_SECTION();
722 723 724

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

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

737
		/* set values that will be saved in xlog */
738
		seq->last_value = next;
739
		seq->is_called = true;
740
		seq->log_cnt = 0;
741

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

B
Bruce Momjian 已提交
748
		recptr = XLogInsert(RM_SEQ_ID, XLOG_SEQ_LOG | XLOG_NO_TRAN, rdata);
749 750

		PageSetLSN(page, recptr);
751
		PageSetTLI(page, ThisTimeLineID);
V
Vadim B. Mikheev 已提交
752
	}
753

754 755
	/* save info in sequence relation */
	seq->last_value = next;		/* last fetched number */
756
	seq->is_called = iscalled;
757
	seq->log_cnt = (iscalled) ? 0 : 1;
758

759
	END_CRIT_SECTION();
M
 
Marc G. Fournier 已提交
760

V
Vadim B. Mikheev 已提交
761 762
	LockBuffer(buf, BUFFER_LOCK_UNLOCK);

B
Bruce Momjian 已提交
763
	WriteBuffer(buf);
764 765

	relation_close(seqrel, NoLock);
766 767
}

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

778
	do_setval(relid, next, true);
779

780
	PG_RETURN_INT64(next);
781 782
}

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

794
	do_setval(relid, next, iscalled);
795

796
	PG_RETURN_INT64(next);
M
 
Marc G. Fournier 已提交
797 798
}

799

800 801
/*
 * If we haven't touched the sequence already in this transaction,
B
Bruce Momjian 已提交
802
 * we need to acquire AccessShareLock.	We arrange for the lock to
803 804 805 806 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
 * 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;
	}
}

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

845
	/*
846
	 * Open the sequence relation.
847
	 */
848
	seqrel = relation_open(relid, NoLock);
849

850
	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
851 852 853
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
854
						RelationGetRelationName(seqrel))));
855

856 857 858 859 860 861 862
	/* 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;
	}

863
	/*
864
	 * Allocate new seqtable entry if we didn't find one.
865
	 *
B
Bruce Momjian 已提交
866 867 868
	 * 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 已提交
869
	 */
870
	if (elm == NULL)
871
	{
872
		/*
B
Bruce Momjian 已提交
873 874
		 * Time to make a new seqtable entry.  These entries live as long as
		 * the backend does, so we use plain malloc for them.
875 876
		 */
		elm = (SeqTable) malloc(sizeof(SeqTableData));
T
Tom Lane 已提交
877
		if (elm == NULL)
878 879 880
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
881
		elm->relid = relid;
882
		elm->xid = InvalidTransactionId;
883 884 885 886
		/* increment is set to 0 until we do read_info (see currval) */
		elm->last = elm->cached = elm->increment = 0;
		elm->next = seqtab;
		seqtab = elm;
887 888
	}

889
	acquire_share_lock(seqrel, elm);
890 891 892

	*p_elm = elm;
	*p_rel = seqrel;
893 894 895
}


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

906 907 908 909 910 911 912
	*buf = ReadBuffer(rel, 0);
	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);

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

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

	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;
925 926
}

927 928 929 930 931 932 933
/*
 * 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.
 */
934
static void
935
init_params(List *options, Form_pg_sequence new, bool isInit)
936
{
937 938 939 940 941
	DefElem    *last_value = NULL;
	DefElem    *increment_by = NULL;
	DefElem    *max_value = NULL;
	DefElem    *min_value = NULL;
	DefElem    *cache_value = NULL;
942
	DefElem    *is_cycled = NULL;
943
	ListCell   *option;
944

B
Bruce Momjian 已提交
945
	foreach(option, options)
946
	{
947
		DefElem    *defel = (DefElem *) lfirst(option);
948

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

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

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

	/* CYCLE */
1020
	if (is_cycled != NULL)
1021 1022 1023 1024 1025 1026
	{
		new->is_cycled = intVal(is_cycled->arg);
		Assert(new->is_cycled == false || new->is_cycled == true);
	}
	else if (isInit)
		new->is_cycled = false;
1027

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

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

1050
	/* crosscheck min/max */
1051
	if (new->min_value >= new->max_value)
1052
	{
B
Bruce Momjian 已提交
1053 1054 1055
		char		bufm[100],
					bufx[100];

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

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

1081
	/* crosscheck */
1082
	if (new->last_value < new->min_value)
1083
	{
B
Bruce Momjian 已提交
1084 1085 1086
		char		bufs[100],
					bufm[100];

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

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

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

1115 1116 1117 1118 1119 1120
			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)));
		}
1121
	}
1122 1123
	else if (isInit)
		new->cache_value = 1;
1124 1125
}

V
Vadim B. Mikheev 已提交
1126

B
Bruce Momjian 已提交
1127 1128
void
seq_redo(XLogRecPtr lsn, XLogRecord *record)
V
Vadim B. Mikheev 已提交
1129
{
B
Bruce Momjian 已提交
1130 1131 1132 1133 1134 1135 1136
	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);
1137
	sequence_magic *sm;
V
Vadim B. Mikheev 已提交
1138

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

1142
	reln = XLogOpenRelation(xlrec->node);
1143 1144
	buffer = XLogReadBuffer(reln, 0, true);
	Assert(BufferIsValid(buffer));
V
Vadim B. Mikheev 已提交
1145 1146
	page = (Page) BufferGetPage(buffer);

1147 1148
	/* Always reinit the page and reinstall the magic number */
	/* See comments in DefineSequence */
1149 1150 1151
	PageInit((Page) page, BufferGetPageSize(buffer), sizeof(sequence_magic));
	sm = (sequence_magic *) PageGetSpecialPointer(page);
	sm->magic = SEQ_MAGIC;
V
Vadim B. Mikheev 已提交
1152

B
Bruce Momjian 已提交
1153
	item = (char *) xlrec + sizeof(xl_seq_rec);
1154 1155
	itemsz = record->xl_len - sizeof(xl_seq_rec);
	itemsz = MAXALIGN(itemsz);
B
Bruce Momjian 已提交
1156
	if (PageAddItem(page, (Item) item, itemsz,
1157
					FirstOffsetNumber, LP_USED) == InvalidOffsetNumber)
1158
		elog(PANIC, "seq_redo: failed to add item to page");
V
Vadim B. Mikheev 已提交
1159 1160

	PageSetLSN(page, lsn);
1161
	PageSetTLI(page, ThisTimeLineID);
1162 1163
	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
	WriteBuffer(buffer);
V
Vadim B. Mikheev 已提交
1164 1165
}

B
Bruce Momjian 已提交
1166
void
1167
seq_desc(StringInfo buf, uint8 xl_info, char *rec)
V
Vadim B. Mikheev 已提交
1168
{
B
Bruce Momjian 已提交
1169 1170
	uint8		info = xl_info & ~XLR_INFO_MASK;
	xl_seq_rec *xlrec = (xl_seq_rec *) rec;
V
Vadim B. Mikheev 已提交
1171 1172

	if (info == XLOG_SEQ_LOG)
1173
		appendStringInfo(buf, "log: ");
V
Vadim B. Mikheev 已提交
1174 1175
	else
	{
1176
		appendStringInfo(buf, "UNKNOWN");
V
Vadim B. Mikheev 已提交
1177 1178 1179
		return;
	}

1180
	appendStringInfo(buf, "rel %u/%u/%u",
1181
			xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
V
Vadim B. Mikheev 已提交
1182
}