ProcessUtilitySlow (pstate=0x236fd00, pstmt=0x2408578,
queryString=0x234add0 "CREATE TABLE films ( code char(5), title varchar(40), did integer, date_prod date, kind varchar(10), len interval hour t o minute);", context=PROCESS_UTILITY_TOPLEVEL, params=0x0, queryEnv=0x0, dest=0x2408658, completionTag=0x7ffcf8fb0e60 "") at utility.c:958
958 Node *parsetree = pstmt->utilityStmt;
(gdb) n
959 bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
(gdb) n
960 bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND);
(gdb) n
962 bool commandCollected = false;
(gdb) n
964 ObjectAddress secondaryObject = InvalidObjectAddress;
(gdb) n
967 needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery();
(gdb) n
970 PG_TRY();
(gdb) n
972 if (isCompleteQuery)
(gdb) n
973 EventTriggerDDLCommandStart(parsetree);
(gdb) n
975 switch (nodeTag(parsetree))
(gdb) n
997 RangeVar *table_rv = NULL;
(gdb) n
1000 stmts = transformCreateStmt((CreateStmt *) parsetree,
(gdb) n
1008 while (stmts != NIL)
(gdb) n
1010 Node *stmt = (Node *) linitial(stmts);
(gdb) n
1012 stmts = list_delete_first(stmts);
(gdb) n
1014 if (IsA(stmt, CreateStmt))
(gdb) n
1016 CreateStmt *cstmt = (CreateStmt *) stmt;
(gdb) n
1021 table_rv = cstmt->relation;
(gdb) n
1024 address = DefineRelation(cstmt,
(gdb) n
1028 EventTriggerCollectSimpleCommand(address,
(gdb) n
1036 CommandCounterIncrement();
(gdb) n
1042 toast_options = transformRelOptions((Datum) 0,
(gdb) n
1048 (void) heap_reloptions(RELKIND_TOASTVALUE,
(gdb) n
1052 NewRelationCreateToastTable(address.objectId,
(gdb) n
1115 if (stmts != NIL)
(gdb) n
1008 while (stmts != NIL)
(gdb) n
1123 commandCollected = true;
(gdb) n
1125 break;
(gdb) n
1748 if (!commandCollected)
(gdb) n
1752 if (isCompleteQuery)
(gdb) n
1754 EventTriggerSQLDrop(parsetree);
(gdb) n
1755 EventTriggerDDLCommandEnd(parsetree);
(gdb) n
1764 PG_END_TRY();
(gdb) n
1766 if (needCleanup)
(gdb) n
1768 }
(gdb) n
transformCreateStmt函数
首先看transformCreateStmt函数,其位于parse_utilcmd.c文件中,函数声明如下
List * transformCreateStmt(CreateStmt *stmt, const char *queryString)
。
1 首先查询需创建表所在的namespace,主要是检查该namespace的权限、加锁(防止并发drop)、检查namespace是否已经存在需要创建的表、如果namespace是temparary的,则需要更新
stmt->relation->relpersistence
。需要了解一下ParseCallbackState结构体,其用于支持parser_errposition_callback函数。setup_parser_errposition_callback函数将形参pstate、location设置到ParseCallbackState对应的成员中;将pcb_error_callback回调函数设置到
errcallback.callback
,将pcbstate设置到
errcallback.arg
,作为回调函数的参数;将
errcallback.previous
设置为全局变量error_context_stack,然后将全局变量error_context_stack指向当前的errcallback,做成栈(在PG中很常见)。cancel_parser_errposition_callback函数就是将error context从上述栈中pop出来,其实就是
error_context_stack = pcbstate->errcallback.previous
。引入该机制的原因在注释中有,这里摘抄一下:Sometimes the parser calls functions that aren’t part of the parser subsystem and can’t reasonably be passed a ParseState; yet we would like any errors thrown in those functions to be tagged with a parse error location. Use this function to set up an error context stack entry that will accomplish that.
/* Support for parser_errposition_callback function */
typedef struct ParseCallbackState
{
ParseState *pstate;
int location;
ErrorContextCallback errcallback;
} ParseCallbackState;
typedef struct ErrorContextCallback
{
struct ErrorContextCallback *previous;
void (*callback) (void *arg);
void *arg;
} ErrorContextCallback;
ParseCallbackState pcbstate;
setup_parser_errposition_callback(&pcbstate, pstate, stmt->relation->location);
namespaceid = RangeVarGetAndCheckCreationNamespace(stmt->relation, NoLock, &existing_relid);
cancel_parser_errposition_callback(&pcbstate);
RangeVarGetAndCheckCreationNamespace函数返回新表创建所在的namespace的OID。如果用户没有CREATE权限,函数会ERROR。如果形参existing_relation_id不为null,则如果查到有相同的表已经创建,则通过该参数返回该表的oid,否则返回InvalidOid。如果lockmode形参不等于NoLock,则指定的锁会加到已经存在同名表上,前提是当前用户拥有目标表。如果lockmode形参不等于NoLock,当前用户不拥有目标表,抛出一个错误,不能尝试锁定用户没有权限的表。该函数会在目标namespace上持有AccessShareLock,会阻塞对该namespace的drop操作。如果目标namespace是a temporary namespace,则需要将relation->relpresistence设置为RELPERSISTENCE_TEMP。下面是该函数主要流程:在死循环中,调用
nspid = RangeVarGetCreationNamespace(relation)
获取namespace oid;如果指明返回存在表oid,调用
get_relname_relid(relation->relname, nspid)
获取一把是否存在名字相同的表,返回其reloid;检查用户是否能在该namepsace下创建表
pg_namespace_aclcheck(nspid, GetUserId(), ACL_CREATE)
;如果上一轮没有获取到锁,则进如retry,释放namepsace表oldnspid AccessShareLock和oldrelid上的锁(指明lockmode != NoLock)【说明获取锁过程中something may have changed, need recheck】;然后对namespace表
LockDatabaseObject(NamespaceRelationId, nspid, 0, AccessShareLock)
和relid获取锁
LockRelationOid(relid, lockmode)
;如果没有invalidation message,说明成功,否则需要设置retry为true,再次循环。
2 如果
stmt->relation->schemaname
为NULL,且
stmt->relation->relpersistence
不为RELPERSISTENCE_TEMP表,需要将stmt->relation->schemaname字段填充好
get_namespace_name(namespaceid)
。
3 对CreateStmtContext进行一系列的初始化
4 primary element transform,主要处理列定义、Constraint和LikeClause。主要关注transformColumnDefinition列定义的转换。由于列数据类型中没有SERIAL pseudo-types,所以不关注函数第一步的序列类型的检查工作;执行
transformColumnType(cxt, column)
流程(确认类型是合法的、处理Collation关键字)
/* Run through each primary element in the table creation clause. Separate column defs from constraints, and do preliminary analysis. */
foreach(elements, stmt->tableElts){
Node *element = lfirst(elements);
switch (nodeTag(element)){
case T_ColumnDef:transformColumnDefinition(&cxt, (ColumnDef *) element);
break;
case T_Constraint:transformTableConstraint(&cxt, (Constraint *) element);
break;
case T_TableLikeClause:transformTableLikeClause(&cxt, (TableLikeClause *) element);
break;
default:elog(ERROR, "unrecognized node type: %d",(int) nodeTag(element));
break;
}
}
5 输出结果,由于没有约束等信息,所以result仅仅包含transform后的stmt语法树。