前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql使用Plpgsql编译SELECT INTO细节

Postgresql使用Plpgsql编译SELECT INTO细节

作者头像
mingjie
发布2022-09-26 21:55:59
6370
发布2022-09-26 21:55:59
举报
文章被收录于专栏:Postgresql源码分析

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

代码语言:javascript
复制
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

实例

代码语言:javascript
复制
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');

编译细节

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

代码语言:javascript
复制
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;

lex遇到into后发生什么?

lex返回T_WORD使yacc进入make_execsql_stmt分支

代码语言:javascript
复制
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);

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

代码语言:javascript
复制
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;
		}
	  ...

上面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数组中的变量结构,所以类型就是指向的变量的类型了。

代码语言:javascript
复制
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);
			}
		}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 总结速查
  • 实例
  • 编译细节
  • lex遇到into后发生什么?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档