• Postgresql使用Plpgsql编译SELECT INTO细节


    select 列名1,列名2 into 变量1,变量2 这类调用形式的代码执行细节分析记录。

    总结速查

    • lex在解析到into后,会进入函数把into后面的字符全部拿出来处理。
    • lex在看到变量1、变量2后,会去ns里面搜索名字,如果搜到了,返回token=T_DATUM,附带PLwdatum数据,指向plpgsql_Datums数组中的同名变量结构。
    • yacc在拿到T_DATUM后开始处理PLwdatum,拿到plpgsql_Datums数组中的变量,如果变量类型是row或record则可以直接使用,如果是var则需要拼接into后面所有字符 到一个 PLpgSQL_row中。
    • PLpgSQL_row中记录了变量名字 和 变量在plpgsql_Datums数组中的位置。

    例如:SELECT userid, username INTO userid, username

    stmt_execsql : K_IMPORT             {}
                       | T_WORD         {make_execsql_stmt}
    
    make_execsql_stmt
      for (;;)
        if (tok == K_INTO)
          read_into_target
            read_into_scalar_list
              while ((tok = yylex()) == ',')
                row = palloc0(sizeof(PLpgSQL_row));   -- 申请row
                while (--nfields >= 0)
                {
                     row->fieldnames[nfields] = fieldnames[nfields];
                     row->varnos[nfields] = varnos[nfields];
                 }
    
    (gdb) p row->fieldnames[0]
    $43 = 0x178f538 "userid"
    
    (gdb) p row->fieldnames[1]
    $44 = 0x178f558 "username"
    
    (gdb) p varnos[0]
    $45 = 2
    
    (gdb) p varnos[1]
    $46 = 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    实例

    create table users(username text, userid int);
    insert into users values ('a', 1);
    insert into users values ('b', 2);
    insert into users values ('b', 3);
    
    CREATE OR REPLACE FUNCTION get_userid(name text) RETURNS int
    AS $$
    #print_strict_params on
    DECLARE
    userid int;
    username text;
    BEGIN
        SELECT userid, username INTO userid, username FROM users WHERE users.username = name;
        RAISE NOTICE 'userid: %', userid;
        RETURN 0;
    END;
    $$ LANGUAGE plpgsql;
    
    select get_userid('a');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    编译细节

    lex token解析过程,调试时使用gdb断plpgsql_yylex。

    BEGIN
    287
        SELECT    -- IDENT -> T_WORD
        userid,   -- > make_execsql_stmt 向前预读:  IDENT -> T_WORD  
        username  -- > make_execsql_stmt 向前预读:  IDENT -> T_WORD
        INTO      -- > make_execsql_stmt 发现 K_INTO 进入 read_into_target
        userid,   -- > read_into_target 继续向前预读 IDENT -> T_DATUM 数据拼到PLwdatum中
                  -- > read_into_target 拿到T_DATUM确认wdatum.datum->dtype类型
                  -- >   如果是PLPGSQL_DTYPE_ROW/PLPGSQL_DTYPE_REC则直接组装一个PLpgSQL_variable,返回
                  -- >   如果不是上面两种,则read_into_scalar_list拼装一个PLpgSQL_row可以指向多个变量,返回
        username  -- > read_into_scalar_list继续向前读一个组装PLpgSQL_row
        FROM users WHERE users.username = name;
          
        RAISE NOTICE 'userid: %', userid;
     
        RETURN 0;
    END;
    $$ LANGUAGE plpgsql;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    lex遇到into后发生什么?

    lex返回T_WORD使yacc进入make_execsql_stmt分支

    stmt_execsql	: K_IMPORT
    					{
    						$$ = make_execsql_stmt(K_IMPORT, @1);
    					}
    				| K_INSERT
    					{
    						$$ = make_execsql_stmt(K_INSERT, @1);
    					}
    				| T_WORD    <<<<<---------<<<<<<<-------
    					{
    						int			tok;
    
    						tok = yylex();
    						plpgsql_push_back_token(tok);
    						if (tok == '=' || tok == COLON_EQUALS ||
    							tok == '[' || tok == '.')
    							word_is_not_variable(&($1), @1);
    						$$ = make_execsql_stmt(T_WORD, @1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    make_execsql_stmt会调用read_into_target在内部把面需要的字符全部读出来:

    read_into_target(PLpgSQL_variable **target, bool *strict)
      ...
    	tok = yylex();
    	...
      switch (tok)
    	{
    		case T_DATUM:
    		...
    			if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
    				yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
    			{
    				*target = (PLpgSQL_variable *) yylval.wdatum.datum;
    				plpgsql_push_back_token(tok);
    			}
    			else
    			{
    				*target = (PLpgSQL_variable *)
    					read_into_scalar_list(NameOfDatum(&(yylval.wdatum)),
    										  yylval.wdatum.datum, yylloc);
    			}
    			break;
    		}
    	  ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    上面read_into_target在收到token=T_DATUM后会启动拼接流程,即把into后面的变量组装起来:

    • 情况一:如果lex返回的wdatum.datum->dtype类型是PLPGSQL_DTYPE_ROW或PLPGSQL_DTYPE_REC,会直接使用plpgsql_Datums数组中的某一个变量结构。
    • 情况二:如果lex返回其他类型,则会用read_into_scalar_list拼接一个PLpgSQL_row,row可以存放多个变量名,并可以指向对应的plpgsql_Datums数组位置,应对select a,b into c,d的情况。

    所以在lex返回T_DATUM的时候,wdatum.datum->dtype的类型依据是什么?

    依据是:wdatum.datum起始就是plpgsql_Datums数组中的变量结构,所以类型就是指向的变量的类型了。

    plpgsql_parse_word (
      word1=0x178f538 "userid", 
      yytxt=0x17abd53 "userid,", 
      lookup=true, 
      wdatum=0x7ffd124cde90, 
      word=0x7ffd124cde90)
    
    // 在命名空间中找到userid
    		ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
    							   word1, NULL, NULL,
    							   NULL);
    // 把datums直接包装好了放到wdatum->datum中,返回token=T_DATUM,read_into_target从yylval.wdatum拿出数据按类型解析
    		if (ns != NULL)
    		{
    			switch (ns->itemtype)
    			{
    				case PLPGSQL_NSTYPE_VAR:
    				case PLPGSQL_NSTYPE_REC:
    					wdatum->datum = plpgsql_Datums[ns->itemno];
    					wdatum->ident = word1;
    					wdatum->quoted = (yytxt[0] == '"');
    					wdatum->idents = NIL;
    					return true;
    
    				default:
    					/* plpgsql_ns_lookup should never return anything else */
    					elog(ERROR, "unrecognized plpgsql itemtype: %d",
    						 ns->itemtype);
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
  • 相关阅读:
    Autosar深入-MPU
    I Pa?sWorD(2023icpc网络预选赛)
    云安全解决方案安全保障体系框架
    django rest framework 学习笔记-实战商城
    中考后还有一条路可选,很多家长还不知道,盐城北大青鸟告诉你
    cadence SPB17.4 - orcad - Hanging Wires规则的验证实验
    centos6服务器升级glibc失败后操作系统命令不能执行处理
    并发包初识
    【数据结构与算法】通过双向链表和HashMap实现LRU缓存 详解
    中高级前端工程师都需要熟悉的技能--前端缓存
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/126867920