diff --git a/doc/src/sgml/ref/select_into.sgmlin b/doc/src/sgml/ref/select_into.sgmlin index 3627d4560..76dff5abf 100644 --- a/doc/src/sgml/ref/select_into.sgmlin +++ b/doc/src/sgml/ref/select_into.sgmlin @@ -13,7 +13,7 @@ [ WITH [ RECURSIVE ] with_query [, ...] ] SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] { * | {expression [ [ AS ] output_name ]} [, ...] } - INTO [ UNLOGGED ] [ TABLE ] new_table + INTO { [ UNLOGGED ] [ TABLE ] new_table | varlist } [ FROM from_item [, ...] ] [ WHERE condition ] [ GROUP BY expression [, ...] ] @@ -27,4 +27,4 @@ SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] [ {FOR { UPDATE | SHARE } [ OF table_name [, ...] ] [ NOWAIT | WAIT N ]} [...] ]; - \ No newline at end of file + diff --git a/src/common/backend/nodes/copyfuncs.cpp b/src/common/backend/nodes/copyfuncs.cpp index fa4468873..97b90e04c 100644 --- a/src/common/backend/nodes/copyfuncs.cpp +++ b/src/common/backend/nodes/copyfuncs.cpp @@ -2305,6 +2305,7 @@ static IntoClause* _copyIntoClause(const IntoClause* from) COPY_SCALAR_FIELD(skipData); COPY_SCALAR_FIELD(ivm); COPY_SCALAR_FIELD(relkind); + COPY_NODE_FIELD(userVarList); #ifdef PGXC COPY_NODE_FIELD(distributeby); COPY_NODE_FIELD(subcluster); @@ -4725,6 +4726,16 @@ static SelectStmt* _copySelectStmt(const SelectStmt* from) return newnode; } +static SelectIntoVarList* _copySelectIntoVarList(const SelectIntoVarList* from) +{ + SelectIntoVarList* newnode = makeNode(SelectIntoVarList); + + COPY_NODE_FIELD(sublink); + COPY_NODE_FIELD(userVarList); + + return newnode; +} + static SetOperationStmt* _copySetOperationStmt(const SetOperationStmt* from) { SetOperationStmt* newnode = makeNode(SetOperationStmt); @@ -7578,6 +7589,9 @@ void* copyObject(const void* from) case T_SelectStmt: retval = _copySelectStmt((SelectStmt*)from); break; + case T_SelectIntoVarList: + retval = _copySelectIntoVarList((SelectIntoVarList*)from); + break; case T_SetOperationStmt: retval = _copySetOperationStmt((SetOperationStmt*)from); break; diff --git a/src/common/backend/nodes/equalfuncs.cpp b/src/common/backend/nodes/equalfuncs.cpp index 26a7dd1c6..f069046c1 100644 --- a/src/common/backend/nodes/equalfuncs.cpp +++ b/src/common/backend/nodes/equalfuncs.cpp @@ -136,6 +136,7 @@ static bool _equalIntoClause(const IntoClause* a, const IntoClause* b) COMPARE_SCALAR_FIELD(skipData); COMPARE_SCALAR_FIELD(ivm); COMPARE_SCALAR_FIELD(relkind); + COMPARE_NODE_FIELD(userVarList); return true; } diff --git a/src/common/backend/nodes/nodes.cpp b/src/common/backend/nodes/nodes.cpp index 050e14e18..b24b14b32 100755 --- a/src/common/backend/nodes/nodes.cpp +++ b/src/common/backend/nodes/nodes.cpp @@ -297,6 +297,7 @@ static const TagStr g_tagStrArr[] = {{T_Invalid, "Invalid"}, {T_UpdateStmt, "UpdateStmt"}, {T_MergeStmt, "MergeStmt"}, {T_SelectStmt, "SelectStmt"}, + {T_SelectIntoVarList, "SelectIntoVarList"}, {T_AlterTableStmt, "AlterTableStmt"}, {T_AlterTableCmd, "AlterTableCmd"}, {T_AlterDomainStmt, "AlterDomainStmt"}, diff --git a/src/common/backend/nodes/outfuncs.cpp b/src/common/backend/nodes/outfuncs.cpp index 642627b3d..2bc478080 100755 --- a/src/common/backend/nodes/outfuncs.cpp +++ b/src/common/backend/nodes/outfuncs.cpp @@ -2074,6 +2074,9 @@ static void _outIntoClause(StringInfo str, IntoClause* node) if (t_thrd.proc->workingVersionNum >= MATVIEW_VERSION_NUM) { WRITE_CHAR_FIELD(relkind); } + if (t_thrd.proc->workingVersionNum >= SELECT_INTO_VAR_VERSION_NUM) { + WRITE_NODE_FIELD(userVarList); + } } static void _outVar(StringInfo str, Var* node) diff --git a/src/common/backend/nodes/readfuncs.cpp b/src/common/backend/nodes/readfuncs.cpp index 4a0c37495..6c06b5e26 100755 --- a/src/common/backend/nodes/readfuncs.cpp +++ b/src/common/backend/nodes/readfuncs.cpp @@ -1845,7 +1845,9 @@ static IntoClause* _readIntoClause(void) READ_STRING_FIELD(tableSpaceName); READ_BOOL_FIELD(skipData); READ_CHAR_FIELD(relkind); - + IF_EXIST(userVarList) { + READ_NODE_FIELD(userVarList); + } READ_DONE(); } diff --git a/src/common/backend/parser/analyze.cpp b/src/common/backend/parser/analyze.cpp index 0549c770c..88ac15e4d 100644 --- a/src/common/backend/parser/analyze.cpp +++ b/src/common/backend/parser/analyze.cpp @@ -283,22 +283,53 @@ Query* transformTopLevelStmt(ParseState* pstate, Node* parseTree, bool isFirstNo AssertEreport(stmt && IsA(stmt, SelectStmt) && stmt->larg == NULL, MOD_OPT, "failure to check parseTree"); if (stmt->intoClause) { - CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt); + if (stmt->intoClause->userVarList) { + UserSetElem* uset = makeNode(UserSetElem); + uset->name = stmt->intoClause->userVarList; + stmt->intoClause = NULL; - ctas->query = parseTree; - ctas->into = stmt->intoClause; - ctas->relkind = OBJECT_TABLE; - ctas->is_select_into = true; + SubLink* sl = makeNode(SubLink); + sl->subLinkType = EXPR_SUBLINK; + sl->testexpr = NULL; + sl->operName = NIL; + sl->subselect = (Node *)stmt; + sl->location = -1; - /* - * Remove the intoClause from the SelectStmt. This makes it safe - * for transformSelectStmt to complain if it finds intoClause set - * (implying that the INTO appeared in a disallowed place). - */ - stmt->intoClause = NULL; + SelectIntoVarList *sis = makeNode(SelectIntoVarList); + sis->sublink = sl; + sis->userVarList = uset->name; - parseTree = (Node*)ctas; + uset->val = (Expr *)sis; + + VariableSetStmt* vss = makeNode(VariableSetStmt); + vss->kind = VAR_SET_DEFINED; + vss->name = "SELECT INTO VARLIST"; + vss->defined_args = list_make1((Node *)uset); + vss->is_local = false; + vss->is_multiset = true; + + VariableMultiSetStmt* vmss = makeNode(VariableMultiSetStmt); + vmss->args = list_make1((Node *)vss); + parseTree = (Node *)vmss; + } else { + CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt); + + ctas->query = parseTree; + ctas->into = stmt->intoClause; + ctas->relkind = OBJECT_TABLE; + ctas->is_select_into = true; + + /* + * Remove the intoClause from the SelectStmt. This makes it safe + * for transformSelectStmt to complain if it finds intoClause set + * (implying that the INTO appeared in a disallowed place). + */ + stmt->intoClause = NULL; + + parseTree = (Node*)ctas; + } } + } if (u_sess->hook_cxt.transformStmtHook != NULL) { diff --git a/src/common/backend/parser/gram.y b/src/common/backend/parser/gram.y index 4b7bb5846..a9f8d5572 100644 --- a/src/common/backend/parser/gram.y +++ b/src/common/backend/parser/gram.y @@ -503,7 +503,7 @@ static void setDelimiterName(core_yyscan_t yyscanner, char*input, VariableSetStm create_generic_options alter_generic_options relation_expr_list dostmt_opt_list merge_values_clause publication_name_list - relation_expr_opt_alias_list + relation_expr_opt_alias_list into_user_var_list /* b compatibility: comment start */ %type opt_index_options index_options opt_table_options table_options opt_column_options column_options @@ -524,7 +524,7 @@ static void setDelimiterName(core_yyscan_t yyscanner, char*input, VariableSetStm %type fdw_option %type OptTempTableName -%type into_clause create_as_target create_mv_target +%type into_clause create_as_target create_mv_target opt_into_clause %type createfunc_opt_item createproc_opt_item common_func_opt_item dostmt_opt_item %type func_arg func_arg_with_default table_func_column @@ -997,6 +997,8 @@ static void setDelimiterName(core_yyscan_t yyscanner, char*input, VariableSetStm %right UMINUS BY NAME_P PASSING ROW TYPE_P VALUE_P %left '[' ']' %left '(' ')' +%left EMPTY_FROM_CLAUSE +%right INTO %left TYPECAST %left '.' /* @@ -20799,14 +20801,29 @@ select_no_parens: yyscanner); $$ = $1; } - | select_clause opt_sort_clause for_locking_clause opt_select_limit + | select_clause opt_sort_clause for_locking_clause opt_select_limit opt_into_clause { - FilterStartWithUseCases((SelectStmt *) $1, $3, yyscanner, @3); + FilterStartWithUseCases((SelectStmt *) $1, $3, yyscanner, @3); insertSelectOptions((SelectStmt *) $1, $2, $3, (Node*)list_nth($4, 0), (Node*)list_nth($4, 1), NULL, yyscanner); - $$ = $1; + SelectStmt *stmt = (SelectStmt *) $1; + if ($5 != NULL) { + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $5; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + } + $$ = (Node *)stmt; } | select_clause opt_sort_clause select_limit opt_for_locking_clause { @@ -20817,6 +20834,50 @@ select_no_parens: yyscanner); $$ = $1; } + | select_clause opt_sort_clause select_limit for_locking_clause into_clause + { + FilterStartWithUseCases((SelectStmt *) $1, $4, yyscanner, @4); + insertSelectOptions((SelectStmt *) $1, $2, $4, + (Node*)list_nth($3, 0), (Node*)list_nth($3, 1), + NULL, + yyscanner); + SelectStmt *stmt = (SelectStmt *) $1; + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $5; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + $$ = (Node *)stmt; + } + | select_clause opt_sort_clause opt_select_limit into_clause opt_for_locking_clause + { + FilterStartWithUseCases((SelectStmt *) $1, $5, yyscanner, @5); + insertSelectOptions((SelectStmt *) $1, $2, $5, + (Node*)list_nth($3, 0), (Node*)list_nth($3, 1), + NULL, + yyscanner); + SelectStmt *stmt = (SelectStmt *) $1; + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $4; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + $$ = (Node *)stmt; + } | with_clause select_clause { insertSelectOptions((SelectStmt *) $2, NULL, NIL, @@ -20833,14 +20894,73 @@ select_no_parens: yyscanner); $$ = $2; } - | with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit + | with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit opt_into_clause { - FilterStartWithUseCases((SelectStmt *) $2, $4, yyscanner, @4); + FilterStartWithUseCases((SelectStmt *) $2, $4, yyscanner, @4); insertSelectOptions((SelectStmt *) $2, $3, $4, (Node*)list_nth($5, 0), (Node*)list_nth($5, 1), $1, yyscanner); - $$ = $2; + SelectStmt *stmt = (SelectStmt *) $2; + if ($6 != NULL) { + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $6; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + } + $$ = (Node *)stmt; + } + | with_clause select_clause opt_sort_clause select_limit for_locking_clause into_clause + { + FilterStartWithUseCases((SelectStmt *) $2, $5, yyscanner, @5); + insertSelectOptions((SelectStmt *) $2, $3, $5, + (Node*)list_nth($4, 0), (Node*)list_nth($4, 1), + $1, + yyscanner); + SelectStmt *stmt = (SelectStmt *) $2; + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $6; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + $$ = (Node *)stmt; + } + | with_clause select_clause opt_sort_clause opt_select_limit into_clause opt_for_locking_clause + { + FilterStartWithUseCases((SelectStmt *) $2, $6, yyscanner, @6); + insertSelectOptions((SelectStmt *) $2, $3, $6, + (Node*)list_nth($4, 0), (Node*)list_nth($4, 1), + $1, + yyscanner); + SelectStmt *stmt = (SelectStmt *) $2; + if (stmt->intoClause != NULL) { + ereport(errstate, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("select statement can contain only one into_clause"))); + } + IntoClause *itc = (IntoClause *) $5; + if (itc->rel != NULL && u_sess->attr.attr_sql.sql_compatibility != B_FORMAT) { + ereport(errstate, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("into new table here only support in B-format database"))); + } + stmt->intoClause = itc; + $$ = (Node *)stmt; } | with_clause select_clause opt_sort_clause select_limit opt_for_locking_clause { @@ -20883,7 +21003,7 @@ select_clause: */ simple_select: SELECT hint_string opt_distinct target_list - into_clause from_clause where_clause start_with_clause + opt_into_clause from_clause where_clause start_with_clause group_clause having_clause window_clause { SelectStmt *n = makeNode(SelectStmt); @@ -21003,6 +21123,12 @@ opt_with_clause: | /*EMPTY*/ { $$ = NULL; } ; +opt_into_clause: + into_clause { $$ = $1; } + | /*EMPTY*/ %prec INTO + { $$ = NULL; } + ; + into_clause: INTO OptTempTableName { @@ -21017,9 +21143,25 @@ into_clause: $$->skipData = false; $$->relkind = INTO_CLAUSE_RELKIND_DEFAULT; } - | /*EMPTY*/ - { $$ = NULL; } - ; + | INTO into_user_var_list + { + $$ = makeNode(IntoClause); + $$->rel = NULL; + $$->colNames = NIL; + $$->options = NIL; + $$->onCommit = ONCOMMIT_NOOP; + $$->row_compress = REL_CMPRS_PAGE_PLAIN; + $$->tableSpaceName = NULL; + $$->skipData = false; + $$->relkind = INTO_CLAUSE_RELKIND_DEFAULT; + $$->userVarList = $2; + } + ; + +into_user_var_list: + uservar_name { $$ = list_make1($1); } + | into_user_var_list ',' uservar_name { $$ = lappend($1,$3); } + ; /* * Redundancy here is needed to avoid shift/reduce conflicts, @@ -21519,7 +21661,8 @@ values_clause: from_clause: FROM from_list { $$ = $2; } - | /*EMPTY*/ { $$ = NIL; } + | /*EMPTY*/ %prec EMPTY_FROM_CLAUSE + { $$ = NIL; } ; from_list: @@ -27991,6 +28134,7 @@ makeCallFuncStmt(List* funcname,List* parameters, bool is_call) ColumnRef *column = NULL; ResTarget *resTarget = NULL; FuncCall *funcCall = NULL; + List *userVarList = NIL; RangeFunction *rangeFunction = NULL; char *schemaname = NULL; char *name = NULL; @@ -28117,6 +28261,15 @@ makeCallFuncStmt(List* funcname,List* parameters, bool is_call) { get_arg_mode_by_pos(i, p_argmodes, narg, have_assigend, &argmode); in_parameters = append_inarg_list(argmode, cell, in_parameters); + + if (argmode == FUNC_PARAM_OUT || argmode == FUNC_PARAM_INOUT) { + if (IsA(arg, TypeCast)) { + arg = (*(TypeCast *)arg).arg; + } + if (IsA(arg, UserVar)) { + userVarList = lappend(userVarList,arg); + } + } } i++; @@ -28173,6 +28326,11 @@ makeCallFuncStmt(List* funcname,List* parameters, bool is_call) newm->whereClause = NULL; newm->havingClause= NULL; newm->groupClause = NIL; + if (userVarList != NIL) { + IntoClause *n = makeNode(IntoClause); + n->userVarList = userVarList; + newm->intoClause = n; + } return (Node*)newm; } diff --git a/src/common/backend/parser/parse_expr.cpp b/src/common/backend/parser/parse_expr.cpp index 9d6f087d2..515edf9d0 100644 --- a/src/common/backend/parser/parse_expr.cpp +++ b/src/common/backend/parser/parse_expr.cpp @@ -69,6 +69,7 @@ static Node* transformUserVar(UserVar *uservar); static Node* transformFuncCall(ParseState* pstate, FuncCall* fn); static Node* transformCaseExpr(ParseState* pstate, CaseExpr* c); static Node* transformSubLink(ParseState* pstate, SubLink* sublink); +static Node* transformSelectIntoVarList(ParseState* pstate, SelectIntoVarList* sis); static Node* transformArrayExpr(ParseState* pstate, A_ArrayExpr* a, Oid array_type, Oid element_type, int32 typmod); static Node* transformRowExpr(ParseState* pstate, RowExpr* r); static Node* transformCoalesceExpr(ParseState* pstate, CoalesceExpr* c); @@ -267,6 +268,10 @@ Node* transformExpr(ParseState* pstate, Node* expr) result = transformSubLink(pstate, (SubLink*)expr); break; + case T_SelectIntoVarList: + result = transformSelectIntoVarList(pstate, (SelectIntoVarList*)expr); + break; + case T_CaseExpr: result = transformCaseExpr(pstate, (CaseExpr*)expr); break; @@ -1863,6 +1868,31 @@ Node* transformSetVariableExpr(SetVariableExpr* set) return (Node *)result; } +static Node* transformSelectIntoVarList(ParseState* pstate, SelectIntoVarList* sis) +{ + SubLink* sublink = (SubLink *)sis->sublink; + Query* qtree = NULL; + + if (IsA(sublink->subselect, Query)) { + return (Node *)sis; + } + pstate->p_hasSubLinks = true; + qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false, true); + + if (!IsA(qtree, Query) || qtree->commandType != CMD_SELECT || qtree->utilityStmt != NULL) { + ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("unexpected non-SELECT command in SubLink"))); + } + sublink->subselect = (Node*)qtree; + + if (list_length(qtree->targetList) != list_length(sis->userVarList)) { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("number of variables must equal the number of columns"), + parser_errposition(pstate, sublink->location))); + } + return (Node *)sis; +} + static Node* transformSubLink(ParseState* pstate, SubLink* sublink) { Node* result = (Node*)sublink; diff --git a/src/common/backend/utils/init/globals.cpp b/src/common/backend/utils/init/globals.cpp index bc2f61d69..47a2fc4b6 100644 --- a/src/common/backend/utils/init/globals.cpp +++ b/src/common/backend/utils/init/globals.cpp @@ -59,8 +59,9 @@ bool open_join_children = true; bool will_shutdown = false; /* hard-wired binary version number */ -const uint32 GRAND_VERSION_NUM = 92833; +const uint32 GRAND_VERSION_NUM = 92834; +const uint32 SELECT_INTO_VAR_VERSION_NUM = 92834; const uint32 DOLPHIN_ENABLE_DROP_NUM = 92830; const uint32 SQL_PATCH_VERSION_NUM = 92675; const uint32 PREDPUSH_SAME_LEVEL_VERSION_NUM = 92522; diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index 97fa66fe5..5722151c5 100755 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -9026,11 +9026,21 @@ void ExecSetVariableStmt(VariableSetStmt* stmt, ParamListInfo paramInfo) elem->val = (Expr *)const_expression_to_const(QueryRewriteNonConstant(node)); } - int ret = check_set_user_message(elem); - if (ret == -1) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), errmsg("invalid name or hash table is nill."))); - } + check_set_user_message(elem); + } + } else if (strcmp(stmt->name, "SELECT INTO VARLIST") == 0) { + ListCell *head = list_head(stmt->defined_args); + UserSetElem *elem = (UserSetElem *)lfirst(head); + Node *node = (Node *)copyObject(((SelectIntoVarList *)elem->val)->sublink); + node = simplify_subselect_expression(node, paramInfo); + List *val_list = QueryRewriteSelectIntoVarList(node); + + ListCell *name_cur = NULL; + ListCell *val_cur = NULL; + + forboth (name_cur, elem->name, val_cur, val_list) { + Expr *var_expr = (Expr *)const_expression_to_const((Node *)lfirst(val_cur)); + check_variable_value_info(((UserVar *)lfirst(name_cur))->name, var_expr); } } break; @@ -12873,54 +12883,56 @@ void init_set_user_params_htab(void) /* * @Description: find user_deined variable in hash table * @IN elem: UserSetElem - * @Return: 0: append success - * -1: append fail + * @Return: void * @See also: */ -int check_set_user_message(const UserSetElem *elem) +void check_set_user_message(const UserSetElem *elem) { ListCell *head = NULL; - char *name = NULL; + foreach (head, elem->name) { + UserVar *n = (UserVar *)lfirst(head); + check_variable_value_info(n->name, elem->val); + } +} + +/* + * @Description: check variable and value info in hash table + * @IN elem: UserSetElem + * @Return: void + * @See also: + */ +void check_variable_value_info(const char* var_name, const Expr* var_expr) +{ bool found = false; - if (nodeTag(elem->val) != T_Const) { + if (nodeTag(var_expr) != T_Const) { ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("The value of user_defined variable must be a const"))); } /* no hash table, we can only choose appending mode */ - if (u_sess->utils_cxt.set_user_params_htab == NULL) { - return -1; + if (u_sess->utils_cxt.set_user_params_htab == NULL || !StringIsValid(var_name)) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OPERATION), errmsg("invalid name or hash table is null."))); } - foreach (head, elem->name) { - UserVar *n = (UserVar *)lfirst(head); - name = n->name; - - if (!StringIsValid(name)) { - return -1; - } - - GucUserParamsEntry *entry = (GucUserParamsEntry *)hash_search(u_sess->utils_cxt.set_user_params_htab, - name, HASH_ENTER, &found); - if (entry == NULL) { - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to create user_defined entry due to out of memory"))); - } - - USE_MEMORY_CONTEXT(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB)); - - if (found) { - entry->value = (Const *)copyObject((Const *)(elem->val)); - } else { - errno_t errval = strncpy_s(entry->name, sizeof(entry->name), name, sizeof(entry->name) - 1); - securec_check_errval(errval, , LOG); - - entry->value = (Const *)copyObject((Const *)(elem->val)); - } + GucUserParamsEntry *entry = (GucUserParamsEntry *)hash_search(u_sess->utils_cxt.set_user_params_htab, + var_name, HASH_ENTER, &found); + if (entry == NULL) { + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to create user_defined entry due to out of memory"))); } - return 0; + USE_MEMORY_CONTEXT(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB)); + + if (found) { + entry->value = (Const *)copyObject((Const *)(var_expr)); + } else { + errno_t errval = strncpy_s(entry->name, sizeof(entry->name), var_name, sizeof(entry->name) - 1); + securec_check_errval(errval, , LOG); + + entry->value = (Const *)copyObject((Const *)(var_expr)); + } } /*get set commant in input_set_message*/ diff --git a/src/common/pl/plpgsql/src/gram.y b/src/common/pl/plpgsql/src/gram.y index 651b41c9a..4606b7ed7 100755 --- a/src/common/pl/plpgsql/src/gram.y +++ b/src/common/pl/plpgsql/src/gram.y @@ -180,7 +180,7 @@ static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr, static char *NameOfDatum(PLwdatum *wdatum); static char *CopyNameOfDatum(PLwdatum *wdatum); static void check_assignable(PLpgSQL_datum *datum, int location); -static void read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, +static bool read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict, bool bulk_collect); static PLpgSQL_row *read_into_scalar_list(char *initial_name, PLpgSQL_datum *initial_datum, @@ -9001,6 +9001,7 @@ make_execsql_stmt(int firsttoken, int location) /* For support InsertStmt:Insert into table_name values record_var */ bool insert_stmt = false; bool prev_values = false; + bool is_user_var = false; bool insert_record = false; bool insert_array_record = false; int values_end_loc = -1; @@ -9065,15 +9066,20 @@ make_execsql_stmt(int firsttoken, int location) continue; /* ALTER ... INTO is not an INTO-target */ if (prev_tok == K_MERGE) continue; /* MERGE INTO is not an INTO-target */ - if (have_into) + if (have_into || is_user_var) yyerror("INTO specified more than once"); have_into = true; if (!have_bulk_collect) { into_start_loc = yylloc; } u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL; - read_into_target(&rec, &row, &have_strict, have_bulk_collect); - u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; + is_user_var = read_into_target(&rec, &row, &have_strict, have_bulk_collect); + if (is_user_var) { + u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = save_IdentifierLookup; + have_into = false; + } else { + u_sess->plsql_cxt.curr_compile_context->plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR; + } } /* @@ -10432,9 +10438,9 @@ read_into_using_add_tableelem(char **fieldnames, int *varnos, int *nfields, int /* * Read the argument of an INTO clause. On entry, we have just read the - * INTO keyword. + * INTO keyword. If it is into_user_defined_variable_list_clause return true. */ -static void +static bool read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict, bool bulk_collect) { int tok; @@ -10445,6 +10451,9 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict, bool bulk_c if (strict) *strict = true; tok = yylex(); + if (tok == SET_USER_IDENT) { + return true; + } if (strict && tok == K_STRICT) { *strict = true; @@ -10530,6 +10539,7 @@ read_into_target(PLpgSQL_rec **rec, PLpgSQL_row **row, bool *strict, bool bulk_c current_token_is_not_variable(tok); break; } + return false; } /* diff --git a/src/gausskernel/optimizer/commands/prepare.cpp b/src/gausskernel/optimizer/commands/prepare.cpp index 530c00f7c..19825e1cf 100755 --- a/src/gausskernel/optimizer/commands/prepare.cpp +++ b/src/gausskernel/optimizer/commands/prepare.cpp @@ -163,6 +163,9 @@ void PrepareQuery(PrepareStmt* stmt, const char* queryString) /* OK */ break; default: + if (IsA(query->utilityStmt, VariableMultiSetStmt)) { + break; + } ereport(ERROR, (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), errmsg("utility statements cannot be prepared"))); break; diff --git a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp index d9c618720..c03de5a6e 100644 --- a/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp +++ b/src/gausskernel/optimizer/rewrite/rewriteHandler.cpp @@ -3102,6 +3102,29 @@ List* QueryRewrite(Query* parsetree) return results; } +Const* processResToConst(char* value, Oid atttypid) +{ + Const *con = NULL; + uint len = strlen(value); + char *str_value = (char *)palloc(len + 1); + errno_t rc = strncpy_s(str_value, len + 1, value, len + 1); + securec_check(rc, "\0", "\0"); + str_value[len] = '\0'; + Datum str_datum = CStringGetDatum(str_value); + + /* convert value to const expression. */ + if (atttypid == BOOLOID) { + if (strcmp(str_value, "t") == 0) { + con = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(true), false, true); + } else { + con = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(false), false, true); + } + } else { + con = makeConst(UNKNOWNOID, -1, InvalidOid, -2, str_datum, false, false); + } + return con; +} + #ifdef PGXC char* GetCreateViewStmt(Query* parsetree, CreateTableAsStmt* stmt) @@ -3753,30 +3776,63 @@ Node* QueryRewriteNonConstant(Node *node) } char* value = (char *)linitial((List *)linitial(result->tuples)); - uint len = strlen(value); - char* str_value = (char *)palloc(len + 1); - errno_t rc = strncpy_s(str_value, len + 1, value, len + 1); - securec_check(rc, "\0", "\0"); - str_value[len] = '\0'; - Datum str_datum = CStringGetDatum(str_value); Oid atttypid = result->atttypids[0]; - /* convert value to const expression. */ - if (atttypid == BOOLOID) { - if (strcmp(str_value, "t") == 0) { - con = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(true), false, true); - } else { - con = makeConst(BOOLOID, -1, InvalidOid, sizeof(bool), BoolGetDatum(false), false, true); - } - res = (Node *)con; - } else { - con = makeConst(UNKNOWNOID, -1, InvalidOid, -2, str_datum, false, false); - res = type_transfer((Node *)con, atttypid, true); - } + con = processResToConst(value, atttypid); + res = atttypid == BOOLOID ? (Node *)con : type_transfer((Node *)con, atttypid, true); (*result->pub.rDestroy)((DestReceiver *)result); return res; } +List* QueryRewriteSelectIntoVarList(Node *node) +{ + Query *parsetree = (Query *)((SubLink *)node)->subselect; + List *resList = NIL; + int res_len = parsetree->targetList->length; + + StringInfo select_sql = makeStringInfo(); + deparse_query(parsetree, select_sql, NIL, false, false); + + StmtResult *result = execute_stmt(select_sql->data, true); + DestroyStringInfo(select_sql); + + if (result->tuples == NULL) { + for (int i = 0; i < res_len; i++) { + Const *con = makeConst(UNKNOWNOID, -1, InvalidOid, -2, (Datum)0, true, false); + resList = lappend(resList, con); + } + + (*result->pub.rDestroy)((DestReceiver *)result); + return resList; + } + + if(result->tuples->length > 1) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OPERATION), + errmsg("select result consisted of more than one row"))); + } + + ListCell *stmt_res_cur = list_head((List *)linitial(result->tuples)); + + for (int idx = 0; idx < res_len; idx++) { + if (result->isnulls[idx]) { + Const *con = makeConst(UNKNOWNOID, -1, InvalidOid, -2, (Datum)0, true, false); + resList = lappend(resList, con); + } else { + char *value = (char *)lfirst(stmt_res_cur); + Oid atttypid = result->atttypids[idx]; + /* convert value to const expression. */ + Const *con = processResToConst(value, atttypid); + Node* rnode = atttypid == BOOLOID ? (Node*)con : type_transfer((Node *)con, atttypid, true); + resList = lappend(resList, rnode); + stmt_res_cur = lnext(stmt_res_cur); + } + } + + (*result->pub.rDestroy)((DestReceiver *)result); + return resList; +} + #endif diff --git a/src/gausskernel/optimizer/util/clauses.cpp b/src/gausskernel/optimizer/util/clauses.cpp index 02ce49198..2c32ac8a6 100644 --- a/src/gausskernel/optimizer/util/clauses.cpp +++ b/src/gausskernel/optimizer/util/clauses.cpp @@ -46,6 +46,7 @@ #include "parser/parse_agg.h" #include "parser/parse_coerce.h" #include "parser/parse_func.h" +#include "parser/parsetree.h" #include "pgxc/pgxc.h" #include "rewrite/rewriteManip.h" #include "tcop/tcopprot.h" @@ -2400,6 +2401,59 @@ Node* estimate_expression_value(PlannerInfo* root, Node* node, EState* estate) return eval_const_expressions_mutator(node, &context); } +/* -------------------- + * simplify_subselect_expression + * + * Only for select ... into varlist statement. + * + * This function attempts to bind the value of parameter + * in the part of Select part without into_clause + * for the following process to calculate the value. + * -------------------- + */ +Node* simplify_subselect_expression(Node* node, ParamListInfo boundParams) +{ + eval_const_expressions_context context; + + context.boundParams = boundParams; + context.root = NULL; /* for inlined-function dependencies */ + context.active_fns = NIL; /* nothing being recursively simplified */ + context.case_val = NULL; /* no CASE being examined */ + context.estimate = true; /* unsafe transformations OK */ + + Query *qt = (Query *)((SubLink *)node)->subselect; + List *fromList = qt->jointree->fromlist; + ListCell *fc = NULL; + foreach (fc, fromList) { + Node *jtnode = (Node *)lfirst(fc); + if (IsA(jtnode, RangeTblRef)) { + int varno = ((RangeTblRef *)jtnode)->rtindex; + RangeTblEntry *rte = rt_fetch(varno, qt->rtable); + if (rte->funcexpr != NULL && IsA(rte->funcexpr, FuncExpr)) { + rte->funcexpr = eval_const_expressions_mutator((Node *)rte->funcexpr, &context); + } + } + } + + ListCell *lc = NULL; + AttrNumber attno = 1; + List *newTargetList = NIL; + foreach (lc, qt->targetList) { + TargetEntry *te = (TargetEntry *)lfirst(lc); + Node *tn = (Node *)te->expr; + if(IsA(tn, Param) || IsA(tn, OpExpr)) { + tn = eval_const_expressions_mutator(tn, &context); + te = makeTargetEntry((Expr *)tn, attno++, te->resname, false); + newTargetList = lappend(newTargetList, te); + } + } + if (newTargetList != NIL) { + qt->targetList = newTargetList; + } + + return node; +} + Node* eval_const_expressions_mutator(Node* node, eval_const_expressions_context* context) { if (node == NULL) diff --git a/src/gausskernel/process/tcop/utility.cpp b/src/gausskernel/process/tcop/utility.cpp index 18c59aee0..8751d8920 100755 --- a/src/gausskernel/process/tcop/utility.cpp +++ b/src/gausskernel/process/tcop/utility.cpp @@ -8043,7 +8043,12 @@ const char* CreateCommandTag(Node* parse_tree) break; case T_SelectStmt: - tag = "SELECT"; + if (((SelectStmt *)parse_tree)->intoClause != NULL && + ((SelectStmt *)parse_tree)->intoClause->userVarList != NIL) { + tag = "SELECT INTO"; + } else { + tag = "SELECT"; + } break; /* utility statements --- same whether raw or cooked */ diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 46110efef..c4adf5d14 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -38,6 +38,7 @@ * Backend version and inplace upgrade staffs *****************************************************************************/ +extern const uint32 SELECT_INTO_VAR_VERSION_NUM; extern const uint32 LARGE_SEQUENCE_VERSION_NUM; extern const uint32 GRAND_VERSION_NUM; extern const uint32 DOLPHIN_ENABLE_DROP_NUM; diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index de39801f9..fb5f078cd 100755 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -394,6 +394,7 @@ typedef enum NodeTag { T_UpdateStmt, T_MergeStmt, T_SelectStmt, + T_SelectIntoVarList, T_AlterTableStmt, T_AlterTableCmd, T_AlterDomainStmt, diff --git a/src/include/nodes/parsenodes_common.h b/src/include/nodes/parsenodes_common.h index ebaec67ef..e9a2f1641 100644 --- a/src/include/nodes/parsenodes_common.h +++ b/src/include/nodes/parsenodes_common.h @@ -688,6 +688,12 @@ typedef struct SelectStmt { /* Eventually add fields for CORRESPONDING spec here */ } SelectStmt; +typedef struct SelectIntoVarList { + NodeTag type; + SubLink *sublink; + List *userVarList; +} SelectIntoVarList; + /* ---------------------- * CREATE TABLE AS Statement (a/k/a SELECT INTO) * diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index bc5c8845f..20e3875ff 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -116,7 +116,8 @@ typedef struct IntoClause { char* tableSpaceName; /* table space to use, or NULL */ bool skipData; /* true for WITH NO DATA */ bool ivm; /* true for WITH IVM */ - char relkind; /* RELKIND_RELATION or RELKIND_MATVIEW */ + char relkind; /* RELKIND_RELATION or RELKIND_MATVIEW */ + List* userVarList; /* user define variables list */ #ifdef PGXC struct DistributeBy* distributeby; /* distribution to use, or NULL */ struct PGXCSubCluster* subcluster; /* subcluster node members */ diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h index 3716e3a98..290e8d05a 100644 --- a/src/include/optimizer/clauses.h +++ b/src/include/optimizer/clauses.h @@ -106,6 +106,8 @@ extern Node* eval_const_expressions_params(PlannerInfo* root, Node* node, ParamL extern Node *eval_const_expression_value(PlannerInfo* root, Node* node, ParamListInfo boundParams); +extern Node* simplify_subselect_expression(Node* node, ParamListInfo boundParams); + extern Node* estimate_expression_value(PlannerInfo* root, Node* node, EState* estate = NULL); extern Query* inline_set_returning_function(PlannerInfo* root, RangeTblEntry* rte); diff --git a/src/include/rewrite/rewriteHandler.h b/src/include/rewrite/rewriteHandler.h index 27dd29762..90f1781a3 100644 --- a/src/include/rewrite/rewriteHandler.h +++ b/src/include/rewrite/rewriteHandler.h @@ -31,5 +31,6 @@ extern List* QueryRewriteRefresh(Query *parsetree); #endif extern List* QueryRewritePrepareStmt(Query *parsetree); extern Node* QueryRewriteNonConstant(Node *node); +extern List* QueryRewriteSelectIntoVarList(Node *node); #endif /* REWRITEHANDLER_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index ba46d34f0..a756ccf75 100755 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -503,7 +503,8 @@ extern void init_set_params_htab(void); extern void init_set_user_params_htab(void); extern void make_set_message(void); extern int check_set_message_to_send(const VariableSetStmt* stmt, const char* queryString); -extern int check_set_user_message(const UserSetElem *elem); +extern void check_set_user_message(const UserSetElem *elem); +extern void check_variable_value_info(const char* var_name, const Expr* var_expr); #define TRANS_ENCRYPT_SAMPLE_RNDM "1234567890ABCDEF" #define TRANS_ENCRYPT_SAMPLE_STRING "TRANS_ENCRY_SAMPLE_STRING" diff --git a/src/test/regress/input/select_into_user_defined_variables.source b/src/test/regress/input/select_into_user_defined_variables.source new file mode 100644 index 000000000..6dd46b49a --- /dev/null +++ b/src/test/regress/input/select_into_user_defined_variables.source @@ -0,0 +1,202 @@ +-- error +select 10 into @aa; + +\! @abs_bindir@/gs_guc reload -Z datanode -D @abs_srcdir@/tmp_check/datanode1 -c "enable_set_variable_b_format=on" >/dev/null 2>&1 +\! sleep 1 + +-- error +select 10 into @aa; +create database test dbcompatibility 'b'; +\c test +show enable_set_variable_b_format; + +drop table if exists t; +create table t(i int, t text, b bool, f float, bi bit(3), vbi bit varying(5)); +insert into t(i, t, b, f, bi, vbi) +values(1, 'aaa', true, 1.11, B'101', B'00'), + (2, 'bbb', false, 2.22, B'100', B'10'), + (3, null, true, 3.33, B'101', B'00'), + (4, 'ddd', null, 4.44, B'100', B'10'), + (5, 'eee', false, null, B'101', B'00'), + (6, 'fff', true, 6.66, null, B'00'), + (7, 'ggg', false, 7.77, B'100', null), + (null, 'hhh', true, 8.88, B'101', B'10'); +select * from t; + +--three places +select 10,20 into @aa,@bb; +select @aa,@bb; +select 20,10 into @aa,@bb for update; +select @aa,@bb; +select 10,20 for update into @aa,@bb; +select @aa,@bb; + +--error +select 10,20 into @aa into @bb; --more than one into_clause +select * from t where i in (select 1 into @aa); --not allow here +select * from t limit 1 into @aa; --too many columns +select * from t into @aa,@bb,@cc,@dd,@ee,@ff; --too many rows + +--values change +select * from t where i=1 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=2 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=3 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=4 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=5 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=6 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=7 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i isnull into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; +select * from t where i=100 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + +--procedure stmt 1 +create or replace procedure my_pro() +as +begin +select 1 into @my_var; +end; +/ +call my_pro(); +select @my_var; + +--procedure stmt 2 +create or replace procedure my_pro(in p_in int) +as +begin +select p_in into @my_var; +end; +/ +call my_pro(2); +select @my_var; + +--procedure stmt 3 +create or replace procedure my_pro(p_in int, out p_out int) +as +begin +select p_in into p_out; +end; +/ +call my_pro(3, @my_var::int); +select @my_var; + +--procedure stmt 4 +create or replace procedure my_pro(inout p_inout int) +as +begin +select p_inout+1 into p_inout; +end; +/ +call my_pro(@my_var::int); +select @my_var; + +--procedure stmt 5 +create or replace procedure inner_pro(inout a int, b int, inout c int) +as +begin +select a*b,b*c into a,c; +end +/ +create or replace procedure outer_pro(d int) +as +begin +select 3,5 into @x,@y; +drop table if exists tb1; +create table tb1(a int, b int); +insert into tb1 values(@x, @y); +call inner_pro(@x::int, d, @y::int); +insert into tb1 values(@x, @y); +end; +/ +call outer_pro(10); +select * from tb1; + +--prepare +select 'select 1024 into @bb;' into @aa; +prepare stmt as @aa; +execute stmt; +select @bb; + +prepare stmt1 as select :p,:q into @aa,@bb; +execute stmt1(1,2); +select @aa,@bb; + +prepare stmt2 as select * from t where i in (:p,:q); +execute stmt2(@aa,@bb); + +execute stmt1('abcde', 'qwer'); +select @aa,@bb; + +prepare stmt3 as select lengthb(:p); +execute stmt3(@aa); + +--triggers +drop table if exists t1; +create table t1(a int); +create or replace function tri_func1() returns trigger as +$$ +begin + select @num + NEW.a into @num; + return NEW; +end +$$ LANGUAGE PLPGSQL; + +create trigger tri_insert_before before insert on t1 for each row execute procedure tri_func1(); +select 0 into @num; +select @num; +insert into t1 values(100); +select @num; +insert into t1 values(200); +select @num; + +drop trigger tri_insert_before on t1; + +create trigger tri_update_before before update on t1 for each row execute procedure tri_func1(); +select 0 into @num; +select @num; +update t1 set a = 1000 where a = 100; +select @num; +update t1 set a = 2000 where a = 200; +select @num; + +create or replace function tri_func2() returns trigger as +$$ +declare +begin + set @num = @num + OLD.a; + return OLD; +end +$$ LANGUAGE PLPGSQL; + +create trigger tri_update_after after update on t1 for each row execute procedure tri_func2(); +select 0 into @num; +select @num; +update t1 set a = 100 where a = 1000; +select @num; +update t1 set a = 200 where a = 2000; +select @num; + +drop trigger tri_update_after on t1; + +create trigger tri_delete_after after delete on t1 for each row execute procedure tri_func2(); +select 0 into @num; +select @num; +delete t1 where a = 100; +select @num; +delete t1 where a = 200; +select @num; + +drop trigger tri_delete_after on t1; + +\c regression +drop database if exists test; +\! @abs_bindir@/gs_guc reload -Z datanode -D @abs_srcdir@/tmp_check/datanode1 -c "enable_set_variable_b_format=off" >/dev/null 2>&1 +\! sleep 1 +show enable_set_variable_b_format; diff --git a/src/test/regress/output/select_into_user_defined_variables.source b/src/test/regress/output/select_into_user_defined_variables.source new file mode 100644 index 000000000..dcee89956 --- /dev/null +++ b/src/test/regress/output/select_into_user_defined_variables.source @@ -0,0 +1,400 @@ +-- error +select 10 into @aa; +ERROR: syntax error at or near "into @" +LINE 1: select 10 into @aa; + ^ +\! @abs_bindir@/gs_guc reload -Z datanode -D @abs_srcdir@/tmp_check/datanode1 -c "enable_set_variable_b_format=on" >/dev/null 2>&1 +\! sleep 1 +-- error +select 10 into @aa; +ERROR: syntax error at or near "into @" +LINE 1: select 10 into @aa; + ^ +create database test dbcompatibility 'b'; +\c test +show enable_set_variable_b_format; + enable_set_variable_b_format +------------------------------ + on +(1 row) + +drop table if exists t; +NOTICE: table "t" does not exist, skipping +create table t(i int, t text, b bool, f float, bi bit(3), vbi bit varying(5)); +insert into t(i, t, b, f, bi, vbi) +values(1, 'aaa', true, 1.11, B'101', B'00'), + (2, 'bbb', false, 2.22, B'100', B'10'), + (3, null, true, 3.33, B'101', B'00'), + (4, 'ddd', null, 4.44, B'100', B'10'), + (5, 'eee', false, null, B'101', B'00'), + (6, 'fff', true, 6.66, null, B'00'), + (7, 'ggg', false, 7.77, B'100', null), + (null, 'hhh', true, 8.88, B'101', B'10'); +select * from t; + i | t | b | f | bi | vbi +---+-----+---+------+-----+----- + 1 | aaa | t | 1.11 | 101 | 00 + 2 | bbb | f | 2.22 | 100 | 10 + 3 | | t | 3.33 | 101 | 00 + 4 | ddd | | 4.44 | 100 | 10 + 5 | eee | f | | 101 | 00 + 6 | fff | t | 6.66 | | 00 + 7 | ggg | f | 7.77 | 100 | + | hhh | t | 8.88 | 101 | 10 +(8 rows) + +--three places +select 10,20 into @aa,@bb; +select @aa,@bb; + @aa | @bb +-----+----- + 10 | 20 +(1 row) + +select 20,10 into @aa,@bb for update; +select @aa,@bb; + @aa | @bb +-----+----- + 20 | 10 +(1 row) + +select 10,20 for update into @aa,@bb; +select @aa,@bb; + @aa | @bb +-----+----- + 10 | 20 +(1 row) + +--error +select 10,20 into @aa into @bb; --more than one into_clause +ERROR: select statement can contain only one into_clause +select * from t where i in (select 1 into @aa); --not allow here +ERROR: SELECT ... INTO is not allowed here +select * from t limit 1 into @aa; --too many columns +ERROR: number of variables must equal the number of columns +select * from t into @aa,@bb,@cc,@dd,@ee,@ff; --too many rows +ERROR: select result consisted of more than one row +--values change +select * from t where i=1 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 1 | aaa | 1 | 1.11 | 101 | 00 +(1 row) + +select * from t where i=2 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 2 | bbb | 0 | 2.22 | 100 | 10 +(1 row) + +select * from t where i=3 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 3 | | 1 | 3.33 | 101 | 00 +(1 row) + +select * from t where i=4 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 4 | ddd | | 4.44 | 100 | 10 +(1 row) + +select * from t where i=5 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+-----+-----+----- + 5 | eee | 0 | | 101 | 00 +(1 row) + +select * from t where i=6 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 6 | fff | 1 | 6.66 | | 00 +(1 row) + +select * from t where i=7 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + 7 | ggg | 0 | 7.77 | 100 | +(1 row) + +select * from t where i isnull into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+------+-----+----- + | hhh | 1 | 8.88 | 101 | 10 +(1 row) + +select * from t where i=100 into @aa,@bb,@cc,@dd,@ee,@ff; +select @aa,@bb,@cc,@dd,@ee,@ff; + @aa | @bb | @cc | @dd | @ee | @ff +-----+-----+-----+-----+-----+----- + | | | | | +(1 row) + +--procedure stmt 1 +create or replace procedure my_pro() +as +begin +select 1 into @my_var; +end; +/ +call my_pro(); + my_pro +-------- + +(1 row) + +select @my_var; + @my_var +--------- + 1 +(1 row) + +--procedure stmt 2 +create or replace procedure my_pro(in p_in int) +as +begin +select p_in into @my_var; +end; +/ +call my_pro(2); + my_pro +-------- + +(1 row) + +select @my_var; + @my_var +--------- + 2 +(1 row) + +--procedure stmt 3 +create or replace procedure my_pro(p_in int, out p_out int) +as +begin +select p_in into p_out; +end; +/ +call my_pro(3, @my_var::int); +select @my_var; + @my_var +--------- + 3 +(1 row) + +--procedure stmt 4 +create or replace procedure my_pro(inout p_inout int) +as +begin +select p_inout+1 into p_inout; +end; +/ +call my_pro(@my_var::int); +select @my_var; + @my_var +--------- + 4 +(1 row) + +--procedure stmt 5 +create or replace procedure inner_pro(inout a int, b int, inout c int) +as +begin +select a*b,b*c into a,c; +end +/ +create or replace procedure outer_pro(d int) +as +begin +select 3,5 into @x,@y; +drop table if exists tb1; +create table tb1(a int, b int); +insert into tb1 values(@x, @y); +call inner_pro(@x::int, d, @y::int); +insert into tb1 values(@x, @y); +end; +/ +call outer_pro(10); +NOTICE: table "tb1" does not exist, skipping +CONTEXT: SQL statement "drop table if exists tb1" +PL/pgSQL function outer_pro(integer) line 4 at SQL statement + outer_pro +----------- + +(1 row) + +select * from tb1; + a | b +----+---- + 3 | 5 + 30 | 50 +(2 rows) + +--prepare +select 'select 1024 into @bb;' into @aa; +prepare stmt as @aa; +execute stmt; +select @bb; + @bb +------ + 1024 +(1 row) + +prepare stmt1 as select :p,:q into @aa,@bb; +execute stmt1(1,2); +select @aa,@bb; + @aa | @bb +-----+----- + 1 | 2 +(1 row) + +prepare stmt2 as select * from t where i in (:p,:q); +execute stmt2(@aa,@bb); + i | t | b | f | bi | vbi +---+-----+---+------+-----+----- + 1 | aaa | t | 1.11 | 101 | 00 + 2 | bbb | f | 2.22 | 100 | 10 +(2 rows) + +execute stmt1('abcde', 'qwer'); +select @aa,@bb; + @aa | @bb +-------+------ + abcde | qwer +(1 row) + +prepare stmt3 as select lengthb(:p); +execute stmt3(@aa); + lengthb +--------- + 5 +(1 row) + +--triggers +drop table if exists t1; +NOTICE: table "t1" does not exist, skipping +create table t1(a int); +create or replace function tri_func1() returns trigger as +$$ +begin + select @num + NEW.a into @num; + return NEW; +end +$$ LANGUAGE PLPGSQL; +create trigger tri_insert_before before insert on t1 for each row execute procedure tri_func1(); +select 0 into @num; +select @num; + @num +------ + 0 +(1 row) + +insert into t1 values(100); +select @num; + @num +------ + 100 +(1 row) + +insert into t1 values(200); +select @num; + @num +------ + 300 +(1 row) + +drop trigger tri_insert_before on t1; +create trigger tri_update_before before update on t1 for each row execute procedure tri_func1(); +select 0 into @num; +select @num; + @num +------ + 0 +(1 row) + +update t1 set a = 1000 where a = 100; +select @num; + @num +------ + 1000 +(1 row) + +update t1 set a = 2000 where a = 200; +select @num; + @num +------ + 3000 +(1 row) + +create or replace function tri_func2() returns trigger as +$$ +declare +begin + set @num = @num + OLD.a; + return OLD; +end +$$ LANGUAGE PLPGSQL; +create trigger tri_update_after after update on t1 for each row execute procedure tri_func2(); +select 0 into @num; +select @num; + @num +------ + 0 +(1 row) + +update t1 set a = 100 where a = 1000; +select @num; + @num +------ + 1100 +(1 row) + +update t1 set a = 200 where a = 2000; +select @num; + @num +------ + 3300 +(1 row) + +drop trigger tri_update_after on t1; +create trigger tri_delete_after after delete on t1 for each row execute procedure tri_func2(); +select 0 into @num; +select @num; + @num +------ + 0 +(1 row) + +delete t1 where a = 100; +select @num; + @num +------ + 100 +(1 row) + +delete t1 where a = 200; +select @num; + @num +------ + 300 +(1 row) + +drop trigger tri_delete_after on t1; +\c regression +drop database if exists test; +\! @abs_bindir@/gs_guc reload -Z datanode -D @abs_srcdir@/tmp_check/datanode1 -c "enable_set_variable_b_format=off" >/dev/null 2>&1 +\! sleep 1 +show enable_set_variable_b_format; + enable_set_variable_b_format +------------------------------ + off +(1 row) + diff --git a/src/test/regress/parallel_schedule0 b/src/test/regress/parallel_schedule0 index 2b348de77..496d69cce 100644 --- a/src/test/regress/parallel_schedule0 +++ b/src/test/regress/parallel_schedule0 @@ -33,6 +33,7 @@ test: pg_proc_test #test user_defined_variable test: set_user_defined_variables_test +test: select_into_user_defined_variables test: set_system_variables_test