暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

PostgreSQL源码分析——CREATE TABLE(series)

原创 chirpyli 2023-01-04
2482

这里分析一下建表时含有序列的时候PostgreSQL是如何创建的,比如下面的SQL语句。

create table t1(a int, b serial);

建表时含有序列

之前分析过CREATE TABLE语句的创建过程,这里,分析一下当建表中的列有serial时是如何处理的。以下面的例句为例:

-- 建表,含有序列 create table t1(a int, b serial); -- 可以看到建表时,同时创建了一个序列t1_b_seq postgres@postgres=# \d List of relations Schema | Name | Type | Owner ------------+------------------+-------------------+---------- public | t1 | table | postgres public | t1_b_seq | sequence | postgres -- 查看这个序列,owned by t1, 当删除t1时,会同步删除t1_b_seq postgres@postgres=# \d t1_b_seq Sequence "public.t1_b_seq" Type | Start | Minimum | Maximum | Increment | Cycles? | Cache ---------+-------+---------+------------+-----------+---------+------- integer | 1 | 1 | 2147483647 | 1 | no | 1 Owned by: public.t1.b -- 查看表t1, b列的默认值为序列t1_b_seq的nextval postgres@postgres=# \d t1 Table "public.t1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+------------------------------- a | integer | | | b | integer | | not null | nextval('t1_b_seq'::regclass) -- 查看系统表pg_class postgres@postgres=# select oid,relname,relkind from pg_class where relname='t1_b_seq'; oid | relname | relkind -------+----------+--------- 25813 | t1_b_seq | S -- 可以看到表中类型为序列 (1 row) -- 查看系统表pg_sequence 可查询序列信息 postgres@postgres=# select * from pg_sequence where seqrelid = 25813; seqrelid | seqtypid | seqstart | seqincrement | seqmax | seqmin | seqcache | seqcycle ----------+----------+----------+--------------+------------+--------+----------+---------- 25813 | 23 | 1 | 1 | 2147483647 | 1 | 1 | f (1 row)

从上面的结果来看,我们大致就能清楚其创建过程了,先创建序列t1_b_seq,再创建表t1,设置t1中b列的默认值为序列的下一个值。下面我们就跟踪源码,看一下是否和上面分析的一样。

即:

CREATE TABLE tablename ( colname SERIAL ); -- 等同于以下语句 CREATE SEQUENCE tablename_colname_seq AS integer; CREATE TABLE tablename ( colname integer NOT NULL DEFAULT nextval('tablename_colname_seq') ); ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

源码分析

因前面文章中已分析过建表的解析过程,这里直接上主流程,

exec_simple_query --> pg_parse_query --> raw_parser --> pg_analyze_and_rewrite_fixedparams --> parse_analyze_fixedparams --> transformStmt --> pg_rewrite_query --> pg_plan_queries --> PortalStart --> PortalRun --> standard_ProcessUtility --> ProcessUtilitySlow --> case T_CreateStmt: // 建表流程 --> transformCreateStmt // 处理CreateStmt,处理列,约束等 --> transformColumnDefinition // 处理列 // 如果该列是序列,则 // 1. 创建CreateSeqStmt节点 // 2. 创建Constraint约束,CONSTR_DEFAULT, 默认值为序列的nextval // 3. 创建Constraint约束,CONSTR_NOTNULL --> generateSerialExtraStmts --> ChooseRelationName // 构造序列名: tablename_columnname_seq --> transformTableConstraint // 处理约束 --> DefineRelation --> case T_CreateSeqStmt: // 创建序列 --> DefineSeqence // Creates a new sequence relation --> init_params // 处理序列的相关设置 --> DefineRelation --> 向pg_sequence系统表中插入序列信息 --> PortalDrop

需要单独分析一下函数ProcessUtilitySlow,在这个函数中,负责处理建表,建序列的逻辑。在处理CreateStmt时,会新创建一个CreateSeqStmt节点,用于创建序列,再创建表。

typedef struct CreateSeqStmt { NodeTag type; RangeVar *sequence; /* the sequence to create */ // 序列名 List *options; Oid ownerId; /* ID of owner, or InvalidOid for default */ // 序列属于谁 bool for_identity; bool if_not_exists; /* just do nothing if it already exists? */ } CreateSeqStmt;

建表过程流程如下:

/* * The "Slow" variant of ProcessUtility should only receive statements * supported by the event triggers facility. Therefore, we always * perform the trigger support calls if the context allows it. */ static void ProcessUtilitySlow(ParseState *pstate, PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, ParamListInfo params, QueryEnvironment *queryEnv, DestReceiver *dest, QueryCompletion *qc) { Node *parsetree = pstmt->utilityStmt; bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); bool needCleanup; bool commandCollected = false; ObjectAddress address; ObjectAddress secondaryObject = InvalidObjectAddress; /* All event trigger calls are done only when isCompleteQuery is true */ needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); /* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */ PG_TRY(); { if (isCompleteQuery) EventTriggerDDLCommandStart(parsetree); switch (nodeTag(parsetree)) { case T_CreateStmt: case T_CreateForeignTableStmt: { List *stmts; RangeVar *table_rv = NULL; // 建表语句语义分析,会处理serial,增加一个CreateSeqStmt节点 /* Run parse analysis ... */ stmts = transformCreateStmt((CreateStmt *) parsetree, queryString); /* * ... and do it. We can't use foreach() because we may * modify the list midway through, so pick off the * elements one at a time, the hard way. */ while (stmts != NIL) { Node *stmt = (Node *) linitial(stmts); stmts = list_delete_first(stmts); if (IsA(stmt, CreateStmt)) { CreateStmt *cstmt = (CreateStmt *) stmt; Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; /* Remember transformed RangeVar for LIKE */ table_rv = cstmt->relation; /* Create the table itself */ address = DefineRelation(cstmt, RELKIND_RELATION, InvalidOid, NULL, queryString); EventTriggerCollectSimpleCommand(address, secondaryObject, stmt); /* * Let NewRelationCreateToastTable decide if this * one needs a secondary relation too. */ CommandCounterIncrement(); /* parse and validate reloptions for the toast table */ toast_options = transformRelOptions((Datum) 0, cstmt->options, "toast", validnsps, true, false); (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); NewRelationCreateToastTable(address.objectId, toast_options); } else if (IsA(stmt, CreateForeignTableStmt)) { // ... } else if (IsA(stmt, TableLikeClause)) { // ... } else { /* * Recurse for anything else. Note the recursive * call will stash the objects so created into our event trigger context. */ PlannedStmt *wrapper; wrapper = makeNode(PlannedStmt); wrapper->commandType = CMD_UTILITY; wrapper->canSetTag = false; wrapper->utilityStmt = stmt; wrapper->stmt_location = pstmt->stmt_location; wrapper->stmt_len = pstmt->stmt_len; ProcessUtility(wrapper, queryString, false, PROCESS_UTILITY_SUBCOMMAND, params, NULL, None_Receiver, NULL); } /* Need CCI between commands */ if (stmts != NIL) CommandCounterIncrement(); } /* * The multiple commands generated here are stashed * individually, so disable collection below. */ commandCollected = true; } break; // 创建序列 case T_CreateSeqStmt: address = DefineSequence(pstate, (CreateSeqStmt *) parsetree); break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(parsetree)); break; } } // ... }

处理序列列的代码在函数transformColumnDefinition中,负责创建CreateSeqStmt,创建Default约束,创建not null约束。

/* * transformColumnDefinition - * transform a single ColumnDef within CREATE TABLE * Also used in ALTER TABLE ADD COLUMN */ static void transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) { bool is_serial; bool saw_nullable; bool saw_default; bool saw_identity; bool saw_generated; ListCell *clist; cxt->columns = lappend(cxt->columns, column); /* Check for SERIAL pseudo-types */ is_serial = false; if (column->typeName && list_length(column->typeName->names) == 1 && !column->typeName->pct_type) { char *typname = strVal(linitial(column->typeName->names)); if (strcmp(typname, "smallserial") == 0 || strcmp(typname, "serial2") == 0) { is_serial = true; column->typeName->names = NIL; column->typeName->typeOid = INT2OID; } else if (strcmp(typname, "serial") == 0 || strcmp(typname, "serial4") == 0) { is_serial = true; column->typeName->names = NIL; column->typeName->typeOid = INT4OID; } else if (strcmp(typname, "bigserial") == 0 || strcmp(typname, "serial8") == 0) { is_serial = true; column->typeName->names = NIL; column->typeName->typeOid = INT8OID; } /* * We have to reject "serial[]" explicitly, because once we've set * typeid, LookupTypeName won't notice arrayBounds. We don't need any * special coding for serial(typmod) though. */ if (is_serial && column->typeName->arrayBounds != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("array of serial is not implemented"), parser_errposition(cxt->pstate, column->typeName->location))); } /* Do necessary work on the column type declaration */ if (column->typeName) transformColumnType(cxt, column); /* Special actions for SERIAL pseudo-types */ if (is_serial) { char *snamespace; char *sname; char *qstring; A_Const *snamenode; TypeCast *castnode; FuncCall *funccallnode; Constraint *constraint; // 创建CreateSeqStmt节点 generateSerialExtraStmts(cxt, column, column->typeName->typeOid, NIL, false, false, &snamespace, &sname); /* * Create appropriate constraints for SERIAL. We do this in full, * rather than shortcutting, so that we will detect any conflicting * constraints the user wrote (like a different DEFAULT). * * Create an expression tree representing the function call * nextval('sequencename'). We cannot reduce the raw tree to cooked * form until after the sequence is created, but there's no need to do * so. */ qstring = quote_qualified_identifier(snamespace, sname); snamenode = makeNode(A_Const); snamenode->val.node.type = T_String; snamenode->val.sval.sval = qstring; snamenode->location = -1; castnode = makeNode(TypeCast); castnode->typeName = SystemTypeName("regclass"); castnode->arg = (Node *) snamenode; castnode->location = -1; funccallnode = makeFuncCall(SystemFuncName("nextval"), list_make1(castnode),COERCE_EXPLICIT_CALL,-1); constraint = makeNode(Constraint); constraint->contype = CONSTR_DEFAULT; // 默认约束 constraint->location = -1; constraint->raw_expr = (Node *) funccallnode; // 默认值为序列的nextval constraint->cooked_expr = NULL; column->constraints = lappend(column->constraints, constraint); constraint = makeNode(Constraint); constraint->contype = CONSTR_NOTNULL; constraint->location = -1; column->constraints = lappend(column->constraints, constraint); } /* Process column constraints, if any... */ transformConstraintAttrs(cxt, column->constraints); saw_nullable = false; saw_default = false; saw_identity = false; saw_generated = false; foreach(clist, column->constraints) { Constraint *constraint = lfirst_node(Constraint, clist); switch (constraint->contype) { case CONSTR_NULL: if (saw_nullable && column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); column->is_not_null = false; saw_nullable = true; break; case CONSTR_NOTNULL: if (saw_nullable && !column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); column->is_not_null = true; saw_nullable = true; break; case CONSTR_DEFAULT: if (saw_default) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple default values specified for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate,constraint->location))); column->raw_default = constraint->raw_expr; saw_default = true; break; case CONSTR_IDENTITY: { Type ctype; Oid typeOid; if (cxt->ofType) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("identity columns are not supported on typed tables"))); if (cxt->partbound) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("identity columns are not supported on partitions"))); ctype = typenameType(cxt->pstate, column->typeName, NULL); typeOid = ((Form_pg_type) GETSTRUCT(ctype))->oid; ReleaseSysCache(ctype); if (saw_identity) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple identity specifications for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); generateSerialExtraStmts(cxt, column, typeOid, constraint->options, true, false, NULL, NULL); column->identity = constraint->generated_when; saw_identity = true; /* An identity column is implicitly NOT NULL */ if (saw_nullable && !column->is_not_null) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting NULL/NOT NULL declarations for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); column->is_not_null = true; saw_nullable = true; break; } case CONSTR_GENERATED: if (cxt->ofType) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("generated columns are not supported on typed tables"))); if (cxt->partbound) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("generated columns are not supported on partitions"))); if (saw_generated) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("multiple generation clauses specified for column \"%s\" of table \"%s\"", column->colname, cxt->relation->relname), parser_errposition(cxt->pstate, constraint->location))); column->generated = ATTRIBUTE_GENERATED_STORED; column->raw_default = constraint->raw_expr; saw_generated = true; break; // ... default: elog(ERROR, "unrecognized constraint type: %d", constraint->contype); break; } // ... } // ... }

具体的生成CreateSeqStmt节点的函数:

/* * generateSerialExtraStmts * Generate CREATE SEQUENCE and ALTER SEQUENCE ... OWNED BY statements * to create the sequence for a serial or identity column. * * This includes determining the name the sequence will have. The caller * can ask to get back the name components by passing non-null pointers * for snamespace_p and sname_p. */ static void generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, Oid seqtypid, List *seqoptions, bool for_identity, bool col_exists, char **snamespace_p, char **sname_p) { ListCell *option; DefElem *nameEl = NULL; Oid snamespaceid; char *snamespace; char *sname; CreateSeqStmt *seqstmt; AlterSeqStmt *altseqstmt; List *attnamelist; /* * Determine namespace and name to use for the sequence. * * First, check if a sequence name was passed in as an option. This is * used by pg_dump. Else, generate a name. * * Although we use ChooseRelationName, it's not guaranteed that the * selected sequence name won't conflict; given sufficiently long field * names, two different serial columns in the same table could be assigned * the same sequence name, and we'd not notice since we aren't creating * the sequence quite yet. In practice this seems quite unlikely to be a * problem, especially since few people would need two serial columns in * one table. */ foreach(option, seqoptions) { DefElem *defel = lfirst_node(DefElem, option); if (strcmp(defel->defname, "sequence_name") == 0) { if (nameEl) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); nameEl = defel; } } if (nameEl) { RangeVar *rv = makeRangeVarFromNameList(castNode(List, nameEl->arg)); snamespace = rv->schemaname; if (!snamespace) { /* Given unqualified SEQUENCE NAME, select namespace */ if (cxt->rel) snamespaceid = RelationGetNamespace(cxt->rel); else snamespaceid = RangeVarGetCreationNamespace(cxt->relation); snamespace = get_namespace_name(snamespaceid); } sname = rv->relname; /* Remove the SEQUENCE NAME item from seqoptions */ seqoptions = list_delete_ptr(seqoptions, nameEl); } else { if (cxt->rel) snamespaceid = RelationGetNamespace(cxt->rel); else { snamespaceid = RangeVarGetCreationNamespace(cxt->relation); RangeVarAdjustRelationPersistence(cxt->relation, snamespaceid); } snamespace = get_namespace_name(snamespaceid); // 序列名 sname = ChooseRelationName(cxt->relation->relname, column->colname, "seq", snamespaceid, false); } ereport(DEBUG1, (errmsg("%s will create implicit sequence \"%s\" for serial column \"%s.%s\"", cxt->stmtType, sname, cxt->relation->relname, column->colname))); /* * Build a CREATE SEQUENCE command to create the sequence object, and add * it to the list of things to be done before this CREATE/ALTER TABLE. */ // 构造CreateSeqStmt节点 seqstmt = makeNode(CreateSeqStmt); seqstmt->for_identity = for_identity; seqstmt->sequence = makeRangeVar(snamespace, sname, -1); // 序列名 seqstmt->options = seqoptions; /* * If a sequence data type was specified, add it to the options. Prepend * to the list rather than append; in case a user supplied their own AS * clause, the "redundant options" error will point to their occurrence, * not our synthetic one. */ if (seqtypid) seqstmt->options = lcons(makeDefElem("as", (Node *) makeTypeNameFromOid(seqtypid, -1), -1), seqstmt->options); /* * If this is ALTER ADD COLUMN, make sure the sequence will be owned by * the table's owner. The current user might be someone else (perhaps a * superuser, or someone who's only a member of the owning role), but the * SEQUENCE OWNED BY mechanisms will bleat unless table and sequence have * exactly the same owning role. */ if (cxt->rel) seqstmt->ownerId = cxt->rel->rd_rel->relowner; else seqstmt->ownerId = InvalidOid; cxt->blist = lappend(cxt->blist, seqstmt); // 可以看到是放在before list中,在建表的前面创建序列 /* * Store the identity sequence name that we decided on. ALTER TABLE ... * ADD COLUMN ... IDENTITY needs this so that it can fill the new column * with values from the sequence, while the association of the sequence * with the table is not set until after the ALTER TABLE. */ column->identitySequence = seqstmt->sequence; /* * Build an ALTER SEQUENCE ... OWNED BY command to mark the sequence as * owned by this column, and add it to the appropriate list of things to * be done along with this CREATE/ALTER TABLE. In a CREATE or ALTER ADD * COLUMN, it must be done after the statement because we don't know the * column's attnum yet. But if we do have the attnum (in AT_AddIdentity), * we can do the marking immediately, which improves some ALTER TABLE * behaviors. */ altseqstmt = makeNode(AlterSeqStmt); altseqstmt->sequence = makeRangeVar(snamespace, sname, -1); attnamelist = list_make3(makeString(snamespace), makeString(cxt->relation->relname), makeString(column->colname)); altseqstmt->options = list_make1(makeDefElem("owned_by", (Node *) attnamelist, -1)); altseqstmt->for_identity = for_identity; if (col_exists) cxt->blist = lappend(cxt->blist, altseqstmt); else cxt->alist = lappend(cxt->alist, altseqstmt); if (snamespace_p) *snamespace_p = snamespace; if (sname_p) *sname_p = sname; }

最后看一下这个函数DefineSequence创建序列,其中还是需要调用函数DefineRelation来实现。

/* * DefineSequence * Creates a new sequence relation */ ObjectAddress DefineSequence(ParseState *pstate, CreateSeqStmt *seq) { FormData_pg_sequence seqform; FormData_pg_sequence_data seqdataform; bool need_seq_rewrite; List *owned_by; CreateStmt *stmt = makeNode(CreateStmt); Oid seqoid; ObjectAddress address; Relation rel; HeapTuple tuple; TupleDesc tupDesc; Datum value[SEQ_COL_LASTCOL]; bool null[SEQ_COL_LASTCOL]; Datum pgs_values[Natts_pg_sequence]; bool pgs_nulls[Natts_pg_sequence]; int i; /* Unlogged sequences are not implemented -- not clear if useful. */ if (seq->sequence->relpersistence == RELPERSISTENCE_UNLOGGED) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unlogged sequences are not supported"))); /* * If if_not_exists was given and a relation with the same name already * exists, bail out. (Note: we needn't check this when not if_not_exists, * because DefineRelation will complain anyway.) */ if (seq->if_not_exists) { RangeVarGetAndCheckCreationNamespace(seq->sequence, NoLock, &seqoid); if (OidIsValid(seqoid)) { /* * If we are in an extension script, insist that the pre-existing * object be a member of the extension, to avoid security risks. */ ObjectAddressSet(address, RelationRelationId, seqoid); checkMembershipInCurrentExtension(&address); /* OK to skip */ ereport(NOTICE, (errcode(ERRCODE_DUPLICATE_TABLE), errmsg("relation \"%s\" already exists, skipping", seq->sequence->relname))); return InvalidObjectAddress; } } /* Check and set all option values */ init_params(pstate, seq->options, seq->for_identity, true, &seqform, &seqdataform, &need_seq_rewrite, &owned_by); /* * Create relation (and fill value[] and null[] for the tuple) */ stmt->tableElts = NIL; for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) { ColumnDef *coldef = makeNode(ColumnDef); coldef->inhcount = 0; coldef->is_local = true; coldef->is_not_null = true; coldef->is_from_type = false; coldef->storage = 0; coldef->raw_default = NULL; coldef->cooked_default = NULL; coldef->collClause = NULL; coldef->collOid = InvalidOid; coldef->constraints = NIL; coldef->location = -1; null[i - 1] = false; switch (i) { case SEQ_COL_LASTVAL: coldef->typeName = makeTypeNameFromOid(INT8OID, -1); coldef->colname = "last_value"; value[i - 1] = Int64GetDatumFast(seqdataform.last_value); break; case SEQ_COL_LOG: coldef->typeName = makeTypeNameFromOid(INT8OID, -1); coldef->colname = "log_cnt"; value[i - 1] = Int64GetDatum((int64) 0); break; case SEQ_COL_CALLED: coldef->typeName = makeTypeNameFromOid(BOOLOID, -1); coldef->colname = "is_called"; value[i - 1] = BoolGetDatum(false); break; } stmt->tableElts = lappend(stmt->tableElts, coldef); } stmt->relation = seq->sequence; stmt->inhRelations = NIL; stmt->constraints = NIL; stmt->options = NIL; stmt->oncommit = ONCOMMIT_NOOP; stmt->tablespacename = NULL; stmt->if_not_exists = seq->if_not_exists; address = DefineRelation(stmt, RELKIND_SEQUENCE, seq->ownerId, NULL, NULL); seqoid = address.objectId; Assert(seqoid != InvalidOid); rel = table_open(seqoid, AccessExclusiveLock); tupDesc = RelationGetDescr(rel); /* now initialize the sequence's data */ tuple = heap_form_tuple(tupDesc, value, null); fill_seq_with_data(rel, tuple); /* process OWNED BY if given */ if (owned_by) process_owned_by(rel, owned_by, seq->for_identity); table_close(rel, NoLock); /* fill in pg_sequence */ rel = table_open(SequenceRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); memset(pgs_nulls, 0, sizeof(pgs_nulls)); pgs_values[Anum_pg_sequence_seqrelid - 1] = ObjectIdGetDatum(seqoid); pgs_values[Anum_pg_sequence_seqtypid - 1] = ObjectIdGetDatum(seqform.seqtypid); pgs_values[Anum_pg_sequence_seqstart - 1] = Int64GetDatumFast(seqform.seqstart); pgs_values[Anum_pg_sequence_seqincrement - 1] = Int64GetDatumFast(seqform.seqincrement); pgs_values[Anum_pg_sequence_seqmax - 1] = Int64GetDatumFast(seqform.seqmax); pgs_values[Anum_pg_sequence_seqmin - 1] = Int64GetDatumFast(seqform.seqmin); pgs_values[Anum_pg_sequence_seqcache - 1] = Int64GetDatumFast(seqform.seqcache); pgs_values[Anum_pg_sequence_seqcycle - 1] = BoolGetDatum(seqform.seqcycle); tuple = heap_form_tuple(tupDesc, pgs_values, pgs_nulls); CatalogTupleInsert(rel, tuple); heap_freetuple(tuple); table_close(rel, RowExclusiveLock); return address; }
最后修改时间:2023-01-06 15:43:21
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论