sequence.c 30.6 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.138 2006/07/31 20:09:00 tgl Exp $
12
 *
13 14
 *-------------------------------------------------------------------------
 */
15
#include "postgres.h"
16

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

32

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

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

45 46
typedef struct sequence_magic
{
47
	uint32		magic;
48
} sequence_magic;
49

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

typedef SeqTableData *SeqTable;

73
static SeqTable seqtab = NULL;	/* Head of list of SeqTable items */
74

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

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

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

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

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

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

128 129 130 131
		null[i - 1] = ' ';

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

182 183
	stmt->relation = seq->sequence;
	stmt->inhRelations = NIL;
184
	stmt->constraints = NIL;
B
Bruce Momjian 已提交
185
	stmt->options = list_make1(defWithOids(false));
186
	stmt->oncommit = ONCOMMIT_NOOP;
187
	stmt->tablespacename = NULL;
188

189
	seqoid = DefineRelation(stmt, RELKIND_SEQUENCE);
190

191
	rel = heap_open(seqoid, AccessExclusiveLock);
192
	tupDesc = RelationGetDescr(rel);
193

194 195
	/* Initialize first page of relation with special magic number */

196
	buf = ReadBuffer(rel, P_NEW);
197 198
	Assert(BufferGetBlockNumber(buf) == 0);

199 200 201 202 203 204
	page = (PageHeader) BufferGetPage(buf);

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

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

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

212 213
	Assert(ItemPointerGetOffsetNumber(&(tuple->t_self)) == FirstOffsetNumber);

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

231
	START_CRIT_SECTION();
232 233 234

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

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

247
		HeapTupleHeaderSetXmin((HeapTupleHeader) item, FrozenTransactionId);
248 249
		((HeapTupleHeader) item)->t_infomask |= HEAP_XMIN_COMMITTED;

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

254 255
	MarkBufferDirty(buf);

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

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

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

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

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

		PageSetLSN(page, recptr);
283
		PageSetTLI(page, ThisTimeLineID);
284
	}
285

286
	END_CRIT_SECTION();
287

288 289
	UnlockReleaseBuffer(buf);

290
	heap_close(rel, NoLock);
291 292
}

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

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

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

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

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

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

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

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

B
Bruce Momjian 已提交
336 337
	START_CRIT_SECTION();

338 339
	MarkBufferDirty(buf);

B
Bruce Momjian 已提交
340 341 342 343 344 345 346 347 348 349
	/* 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);
350
		rdata[0].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
351 352 353 354 355
		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;
356
		rdata[1].buffer = InvalidBuffer;
B
Bruce Momjian 已提交
357 358 359 360 361
		rdata[1].next = NULL;

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

		PageSetLSN(page, recptr);
362
		PageSetTLI(page, ThisTimeLineID);
B
Bruce Momjian 已提交
363 364 365 366
	}

	END_CRIT_SECTION();

367
	UnlockReleaseBuffer(buf);
B
Bruce Momjian 已提交
368 369 370 371

	relation_close(seqrel, NoLock);
}

372

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

V
Vadim B. Mikheev 已提交
419
	/* open and AccessShareLock sequence */
420
	init_sequence(relid, &elm, &seqrel);
421

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

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

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

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

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

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

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

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

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

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

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

553 554
	last_used_seq = elm;

555
	START_CRIT_SECTION();
556

557 558
	MarkBufferDirty(buf);

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

596
	UnlockReleaseBuffer(buf);
597

598 599
	relation_close(seqrel, NoLock);

600
	return result;
601 602
}

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

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

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

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

	result = elm->last;

629 630
	relation_close(seqrel, NoLock);

631
	PG_RETURN_INT64(result);
632 633
}

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

653
	seqrel = open_share_lock(last_used_seq);
654 655 656 657

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

658 659
	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)
660 661 662 663 664 665 666
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("permission denied for sequence %s",
						RelationGetRelationName(seqrel))));

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

668 669 670
	PG_RETURN_INT64(result);
}

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

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

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

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

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

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

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

724
	START_CRIT_SECTION();
725

726 727
	MarkBufferDirty(buf);

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

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

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

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

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

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

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

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

766
	UnlockReleaseBuffer(buf);
767 768

	relation_close(seqrel, NoLock);
769 770
}

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

781
	do_setval(relid, next, true);
782

783
	PG_RETURN_INT64(next);
784 785
}

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

797
	do_setval(relid, next, iscalled);
798

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

802

803
/*
804 805
 * Open the sequence and acquire AccessShareLock if needed
 *
806
 * If we haven't touched the sequence already in this transaction,
B
Bruce Momjian 已提交
807
 * we need to acquire AccessShareLock.	We arrange for the lock to
808 809 810
 * be owned by the top transaction, so that we don't need to do it
 * more than once per xact.
 */
811 812
static Relation
open_share_lock(SeqTable seq)
813 814 815
{
	TransactionId thisxid = GetTopTransactionId();

816
	/* Get the lock if not already held in this xact */
817 818 819 820 821 822 823 824
	if (seq->xid != thisxid)
	{
		ResourceOwner currentOwner;

		currentOwner = CurrentResourceOwner;
		PG_TRY();
		{
			CurrentResourceOwner = TopTransactionResourceOwner;
825
			LockRelationOid(seq->relid, AccessShareLock);
826 827 828 829 830 831 832 833 834 835
		}
		PG_CATCH();
		{
			/* Ensure CurrentResourceOwner is restored on error */
			CurrentResourceOwner = currentOwner;
			PG_RE_THROW();
		}
		PG_END_TRY();
		CurrentResourceOwner = currentOwner;

836
		/* Flag that we have a lock in the current xact */
837 838
		seq->xid = thisxid;
	}
839 840 841

	/* We now know we have AccessShareLock, and can safely open the rel */
	return relation_open(seq->relid, NoLock);
842 843
}

844
/*
845
 * Given a relation OID, open and lock the sequence.  p_elm and p_rel are
846 847 848
 * output parameters.
 */
static void
849
init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
850
{
851
	SeqTable elm;
852
	Relation	seqrel;
853

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

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

887 888 889 890 891 892 893 894 895 896
	/*
	 * Open the sequence relation.
	 */
	seqrel = open_share_lock(elm);

	if (seqrel->rd_rel->relkind != RELKIND_SEQUENCE)
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a sequence",
						RelationGetRelationName(seqrel))));
897 898 899

	*p_elm = elm;
	*p_rel = seqrel;
900 901 902
}


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

913 914 915 916 917 918 919
	*buf = ReadBuffer(rel, 0);
	LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE);

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

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

	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;
932 933
}

934 935 936 937 938 939 940
/*
 * 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.
 */
941
static void
942
init_params(List *options, Form_pg_sequence new, bool isInit)
943
{
944 945 946 947 948
	DefElem    *last_value = NULL;
	DefElem    *increment_by = NULL;
	DefElem    *max_value = NULL;
	DefElem    *min_value = NULL;
	DefElem    *cache_value = NULL;
949
	DefElem    *is_cycled = NULL;
950
	ListCell   *option;
951

B
Bruce Momjian 已提交
952
	foreach(option, options)
953
	{
954
		DefElem    *defel = (DefElem *) lfirst(option);
955

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

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

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

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

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

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

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

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

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

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

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

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

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

1122 1123 1124 1125 1126 1127
			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)));
		}
1128
	}
1129 1130
	else if (isInit)
		new->cache_value = 1;
1131 1132
}

V
Vadim B. Mikheev 已提交
1133

B
Bruce Momjian 已提交
1134 1135
void
seq_redo(XLogRecPtr lsn, XLogRecord *record)
V
Vadim B. Mikheev 已提交
1136
{
B
Bruce Momjian 已提交
1137 1138 1139 1140 1141 1142 1143
	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);
1144
	sequence_magic *sm;
V
Vadim B. Mikheev 已提交
1145

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

1149
	reln = XLogOpenRelation(xlrec->node);
1150 1151
	buffer = XLogReadBuffer(reln, 0, true);
	Assert(BufferIsValid(buffer));
V
Vadim B. Mikheev 已提交
1152 1153
	page = (Page) BufferGetPage(buffer);

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

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

	PageSetLSN(page, lsn);
1168
	PageSetTLI(page, ThisTimeLineID);
1169 1170
	MarkBufferDirty(buffer);
	UnlockReleaseBuffer(buffer);
V
Vadim B. Mikheev 已提交
1171 1172
}

B
Bruce Momjian 已提交
1173
void
1174
seq_desc(StringInfo buf, uint8 xl_info, char *rec)
V
Vadim B. Mikheev 已提交
1175
{
B
Bruce Momjian 已提交
1176 1177
	uint8		info = xl_info & ~XLR_INFO_MASK;
	xl_seq_rec *xlrec = (xl_seq_rec *) rec;
V
Vadim B. Mikheev 已提交
1178 1179

	if (info == XLOG_SEQ_LOG)
1180
		appendStringInfo(buf, "log: ");
V
Vadim B. Mikheev 已提交
1181 1182
	else
	{
1183
		appendStringInfo(buf, "UNKNOWN");
V
Vadim B. Mikheev 已提交
1184 1185 1186
		return;
	}

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