select 列名1,列名2 into 变量1,变量2
这类调用形式的代码执行细节分析记录。
例如: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
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。
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返回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);
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;
}
...
上面read_into_target在收到token=T_DATUM后会启动拼接流程,即把into后面的变量组装起来:
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);
}
}