天天看点

PostgreSQL查询引擎——create table xxx(...)基础建表transformCreateStmt

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语法树。

PostgreSQL查询引擎——create table xxx(...)基础建表transformCreateStmt