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.135 2006/07/11 17:26:58 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/sequence.h"
22
#include "commands/tablecmds.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;
B
Bruce Momjian 已提交
183
	stmt->options = list_make1(defWithOids(false));
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
	MarkBufferDirty(buf);

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

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

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

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

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

		PageSetLSN(page, recptr);
281
		PageSetTLI(page, ThisTimeLineID);
282
	}
283

284
	END_CRIT_SECTION();
285

286 287
	UnlockReleaseBuffer(buf);

288
	heap_close(rel, NoLock);
289 290
}

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

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

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

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

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

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

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

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

B
Bruce Momjian 已提交
334 335
	START_CRIT_SECTION();

336 337
	MarkBufferDirty(buf);

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

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

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

	END_CRIT_SECTION();

365
	UnlockReleaseBuffer(buf);
B
Bruce Momjian 已提交
366 367 368 369

	relation_close(seqrel, NoLock);
}

370

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

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

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

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

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

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

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

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

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

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

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

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

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

551 552
	last_used_seq = elm;

553
	START_CRIT_SECTION();
554

555 556
	MarkBufferDirty(buf);

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

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

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

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

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

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

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

592
	END_CRIT_SECTION();
593

594
	UnlockReleaseBuffer(buf);
595

596 597
	relation_close(seqrel, NoLock);

598
	return result;
599 600
}

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

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

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

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

	result = elm->last;

627 628
	relation_close(seqrel, NoLock);

629
	PG_RETURN_INT64(result);
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 655 656
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);

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

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

667 668 669
	PG_RETURN_INT64(result);
}

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

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

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

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

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

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

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

723
	START_CRIT_SECTION();
724

725 726
	MarkBufferDirty(buf);

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

765
	UnlockReleaseBuffer(buf);
766 767

	relation_close(seqrel, NoLock);
768 769
}

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

780
	do_setval(relid, next, true);
781

782
	PG_RETURN_INT64(next);
783 784
}

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

796
	do_setval(relid, next, iscalled);
797

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

801

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

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

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

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

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

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

891
	acquire_share_lock(seqrel, elm);
892 893 894

	*p_elm = elm;
	*p_rel = seqrel;
895 896 897
}


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

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

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

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

	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;
927 928
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

V
Vadim B. Mikheev 已提交
1128

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

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

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

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

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

	PageSetLSN(page, lsn);
1163
	PageSetTLI(page, ThisTimeLineID);
1164 1165
	MarkBufferDirty(buffer);
	UnlockReleaseBuffer(buffer);
V
Vadim B. Mikheev 已提交
1166 1167
}

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

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

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