466 lines
20 KiB
C++
466 lines
20 KiB
C++
/**
|
|
* Copyright (c) 2021 OceanBase
|
|
* OceanBase CE is licensed under Mulan PubL v2.
|
|
* You can use this software according to the terms and conditions of the Mulan PubL v2.
|
|
* You may obtain a copy of Mulan PubL v2 at:
|
|
* http://license.coscl.org.cn/MulanPubL-2.0
|
|
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
|
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
|
* See the Mulan PubL v2 for more details.
|
|
*/
|
|
|
|
#define USING_LOG_PREFIX SQL_RESV
|
|
|
|
#include "ob_call_procedure_resolver.h"
|
|
#include "ob_call_procedure_stmt.h"
|
|
#include "sql/resolver/ob_resolver_utils.h"
|
|
#include "sql/resolver/expr/ob_raw_expr_util.h"
|
|
#include "sql/resolver/dml/ob_select_resolver.h"
|
|
#include "pl/ob_pl_stmt.h"
|
|
#include "pl/ob_pl_package.h"
|
|
#include "observer/ob_req_time_service.h"
|
|
#include "pl/ob_pl_compile.h"
|
|
|
|
namespace oceanbase
|
|
{
|
|
using namespace common;
|
|
using namespace share::schema;
|
|
namespace sql
|
|
{
|
|
int ObCallProcedureResolver::check_param_expr_legal(ObRawExpr *param)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
if (OB_NOT_NULL(param)) {
|
|
if (T_REF_QUERY == param->get_expr_type()) {
|
|
ret = OB_NOT_SUPPORTED;
|
|
LOG_USER_ERROR(OB_NOT_SUPPORTED, "subqueries or stored function calls here");
|
|
} else if (T_FUN_SYS_PL_SEQ_NEXT_VALUE == param->get_expr_type()) {
|
|
ret = OB_NOT_SUPPORTED;
|
|
LOG_USER_ERROR(OB_NOT_SUPPORTED, "ORA-06576 : not a valid function or procedure name");
|
|
} /* else if (T_OP_GET_PACKAGE_VAR == param->get_expr_type()) {
|
|
ret = OB_NOT_SUPPORTED;
|
|
LOG_USER_ERROR(OB_NOT_SUPPORTED, "PLS-221: not procedure or not defined!");
|
|
} */
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < param->get_param_count(); ++i) {
|
|
OZ (check_param_expr_legal(param->get_param_expr(i)));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
int ObCallProcedureResolver::resolve_cparams(const ParseNode *params_node,
|
|
const ObRoutineInfo *routine_info,
|
|
ObCallProcedureStmt *stmt)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
CK (OB_NOT_NULL(routine_info));
|
|
CK (OB_NOT_NULL(stmt));
|
|
ObArray<ObRawExpr*> params;
|
|
// Step 1: 初始化参数列表
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < routine_info->get_param_count(); ++i) {
|
|
OZ (params.push_back(NULL));
|
|
}
|
|
// Step 2: 从ParamsNode中解析参数
|
|
if (OB_SUCC(ret) && OB_NOT_NULL(params_node)) {
|
|
bool has_assign_param = false;
|
|
if (T_SP_CPARAM_LIST != params_node->type_) {
|
|
ret = OB_INVALID_ARGUMENT;
|
|
LOG_WARN("invalid params list node", K(ret), K(params_node->type_));
|
|
}
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < params_node->num_child_; ++i) {
|
|
if (OB_ISNULL(params_node->children_[i])) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("param node is NULL", K(i), K(ret));
|
|
} else if (T_SP_CPARAM == params_node->children_[i]->type_) {
|
|
has_assign_param = true;
|
|
if (OB_FAIL(resolve_cparam_with_assign(params_node->children_[i], routine_info, params))) {
|
|
LOG_WARN("failed to resolve cparam with assign", K(ret));
|
|
}
|
|
} else if (has_assign_param) {
|
|
ret = OB_ERR_SP_WRONG_ARG_NUM;
|
|
LOG_WARN("can not set param without assign after param with assign", K(ret));
|
|
} else if (OB_FAIL(resolve_cparam_without_assign(params_node->children_[i], i, params))) {
|
|
LOG_WARN("failed to resolve cparam without assign", K(ret), K(i));
|
|
}
|
|
}
|
|
}
|
|
// Step 3: 处理空缺参数, 如果有默认值填充默认值, 否则报错
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < params.count(); ++i) {
|
|
ObConstRawExpr *default_expr = NULL;
|
|
if (OB_ISNULL(params.at(i))) { // 空缺参数
|
|
params_.is_default_param_ = true;
|
|
ObRoutineParam *routine_param = routine_info->get_routine_params().at(i);
|
|
CK (OB_NOT_NULL(routine_param));
|
|
if (OB_SUCC(ret) && routine_param->get_default_value().empty()) {
|
|
ret = OB_ERR_SP_WRONG_ARG_NUM;
|
|
LOG_WARN("routine param dese not has default value", K(ret));
|
|
}
|
|
CK (OB_NOT_NULL(params_.expr_factory_));
|
|
OZ (ObRawExprUtils::build_const_int_expr(
|
|
*(params_.expr_factory_), ObIntType, 0, default_expr));
|
|
CK (OB_NOT_NULL(default_expr));
|
|
OZ(default_expr->add_flag(IS_PL_MOCK_DEFAULT_EXPR));
|
|
OX (params.at(i) = default_expr);
|
|
}
|
|
}
|
|
// Step 4: 将参数数组设置到stmt中
|
|
OZ (stmt->add_params(params));
|
|
if (OB_SUCC(ret)) { // 判断所有参数没有复杂表达式参数
|
|
bool v = true;
|
|
for (int64_t i = 0; v && OB_SUCC(ret) && i < params.count(); i ++) {
|
|
if (OB_ISNULL(params.at(i))) {
|
|
ret = OB_INVALID_ARGUMENT;
|
|
LOG_WARN("invalid argument", K(ret));
|
|
} else if (params.at(i)->is_const_raw_expr()) {
|
|
const ObConstRawExpr *const_expr = static_cast<const ObConstRawExpr *>(params.at(i));
|
|
if (T_QUESTIONMARK != const_expr->get_expr_type()) {
|
|
v = false;
|
|
}
|
|
} else {
|
|
v = false;
|
|
}
|
|
} // for end
|
|
stmt->set_can_direct_use_param(v);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ObCallProcedureResolver::resolve_cparam_without_assign(const ParseNode *param_node,
|
|
const int64_t position,
|
|
ObIArray<ObRawExpr*> ¶ms)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
CK (OB_NOT_NULL(param_node));
|
|
ObRawExpr *param = NULL;
|
|
if (OB_FAIL(ret)) {
|
|
} else if (position < 0 || position >= params.count()) {
|
|
ret = OB_ERR_SP_WRONG_ARG_NUM;
|
|
LOG_WARN("wrong argument number", K(ret), K(position), K(params.count()));
|
|
} else if (OB_NOT_NULL(params.at(position))) {
|
|
ret = OB_ERR_SP_DUP_PARAM;
|
|
LOG_WARN("dup params", K(ret), K(position));
|
|
} else if (OB_FAIL(pl::ObPLResolver::resolve_raw_expr(*param_node, params_, param))) {
|
|
LOG_WARN("failed to resolve const expr", K(ret));
|
|
} else if (OB_ISNULL(param)) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("param expr is null", K(ret), K(param));
|
|
} else if (OB_FAIL(check_param_expr_legal(param))) {
|
|
LOG_WARN("failed to check param expr legal", K(ret), KPC(param));
|
|
} else if (T_OP_ROW == param->get_expr_type() && 1 != param->get_param_count()) {
|
|
ret = OB_ERR_INVALID_COLUMN_NUM;
|
|
LOG_USER_ERROR(OB_ERR_INVALID_COLUMN_NUM, static_cast<int64_t>(1));
|
|
LOG_WARN("op_row input param count is not 1", K(param->get_param_count()), K(ret));
|
|
} else {
|
|
params.at(position) = param;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ObCallProcedureResolver::resolve_cparam_with_assign(const ParseNode *param_node,
|
|
const ObRoutineInfo* routine_info,
|
|
ObIArray<ObRawExpr*> ¶ms)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
CK (OB_NOT_NULL(param_node));
|
|
CK (OB_NOT_NULL(routine_info));
|
|
CK (OB_LIKELY(2 == param_node->num_child_));
|
|
CK (OB_NOT_NULL(param_node->children_[0]));
|
|
CK (OB_NOT_NULL(param_node->children_[1]));
|
|
if (OB_SUCC(ret)) {
|
|
const ParseNode *name_node = NULL;
|
|
if (T_OBJ_ACCESS_REF == param_node->children_[0]->type_) {
|
|
CK (OB_LIKELY(2 == param_node->children_[0]->num_child_));
|
|
CK (OB_NOT_NULL(param_node->children_[0]->children_[0]));
|
|
CK (OB_ISNULL(param_node->children_[0]->children_[1]));
|
|
CK (OB_LIKELY(T_IDENT == param_node->children_[0]->children_[0]->type_));
|
|
name_node = param_node->children_[0]->children_[0];
|
|
} else if (T_IDENT == param_node->children_[0]->type_) {
|
|
name_node = param_node->children_[0];
|
|
} else if (T_COLUMN_REF == param_node->children_[0]->type_ &&
|
|
3 == param_node->children_[0]->num_child_ &&
|
|
NULL == param_node->children_[0]->children_[0] &&
|
|
NULL == param_node->children_[0]->children_[1] &&
|
|
T_IDENT == param_node->children_[0]->children_[2]->type_) {
|
|
name_node = param_node->children_[0]->children_[2];
|
|
} else {
|
|
ret = OB_ERR_CALL_WRONG_ARG;
|
|
LOG_WARN("PLS-00306: wrong number or types of arguments in call", K(ret));
|
|
}
|
|
|
|
if (OB_SUCC(ret)) {
|
|
ObString name = ObString(static_cast<int32_t>(name_node->str_len_), name_node->str_value_);
|
|
int64_t position = -1;
|
|
if (OB_FAIL(routine_info->find_param_by_name(name, position))) {
|
|
LOG_WARN("failed to find param name in proc info", K(ret), K(name), K(*routine_info));
|
|
} else if (OB_UNLIKELY(-1 == position)) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("invalid postition value", K(ret), K(position));
|
|
} else if (OB_FAIL(resolve_cparam_without_assign(param_node->children_[1], position, params))) {
|
|
LOG_WARN("failed to resolve cparam without assign", K(ret));
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ObCallProcedureResolver::resolve_param_exprs(const ParseNode *params_node,
|
|
ObIArray<ObRawExpr*> &expr_params)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
CK (OB_NOT_NULL(params_node));
|
|
CK (T_SP_CPARAM_LIST == params_node->type_);
|
|
CK (OB_NOT_NULL(params_.session_info_));
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < params_node->num_child_; ++i) {
|
|
ObRawExpr* raw_expr = NULL;
|
|
CK (OB_NOT_NULL(params_node->children_[i]));
|
|
if (OB_SUCC(ret) && params_.is_execute_call_stmt_) {
|
|
ObArray<ObQualifiedName> columns;
|
|
ObArray<ObVarInfo> sys_vars;
|
|
ObArray<ObAggFunRawExpr*> aggr_exprs;
|
|
ObArray<ObWinFunRawExpr*> win_exprs;
|
|
ObArray<ObSubQueryInfo> sub_query_info;
|
|
ObArray<ObUDFInfo> udf_info;
|
|
ObArray<ObOpRawExpr*> op_exprs;
|
|
if (OB_FAIL(ObRawExprUtils::build_raw_expr(*params_.expr_factory_,
|
|
*params_.session_info_,
|
|
params_.schema_checker_,
|
|
params_.secondary_namespace_,
|
|
T_PL_SCOPE,
|
|
NULL/*ObStmt*/,
|
|
params_.param_list_,
|
|
NULL/*external_param_info*/,
|
|
*params_node->children_[i],
|
|
raw_expr,
|
|
columns,
|
|
sys_vars,
|
|
aggr_exprs,
|
|
win_exprs,
|
|
sub_query_info,
|
|
udf_info,
|
|
op_exprs,
|
|
true,
|
|
static_cast<TgTimingEvent>(params_.tg_timing_event_)))) {
|
|
LOG_WARN("failed to build raw expr", K(ret));
|
|
}
|
|
} else {
|
|
OZ (pl::ObPLResolver::resolve_raw_expr(*params_node->children_[i], params_, raw_expr));
|
|
}
|
|
CK (OB_NOT_NULL(raw_expr));
|
|
OZ (check_param_expr_legal(raw_expr));
|
|
OZ (expr_params.push_back(raw_expr));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int ObCallProcedureResolver::resolve(const ParseNode &parse_tree)
|
|
{
|
|
int ret = OB_SUCCESS;
|
|
ObCallProcedureStmt *stmt = NULL;
|
|
ParseNode *name_node = parse_tree.children_[0];
|
|
ParseNode *params_node = parse_tree.children_[1];
|
|
ObString db_name;
|
|
ObString package_name;
|
|
ObString sp_name;
|
|
const ObRoutineInfo *proc_info = NULL;
|
|
|
|
if (OB_ISNULL(schema_checker_) || OB_ISNULL(session_info_)) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("argument is NULL", K(schema_checker_), K(session_info_), K(ret));
|
|
} else if (OB_UNLIKELY(T_SP_CALL_STMT != parse_tree.type_ || OB_ISNULL(name_node))) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("the children of parse tree is NULL", K(parse_tree.type_), K(name_node), K(ret));
|
|
} else if (OB_ISNULL(stmt = create_stmt<ObCallProcedureStmt>())) {
|
|
ret = OB_ALLOCATE_MEMORY_FAILED;
|
|
LOG_ERROR("create call stmt failed", K(ret));
|
|
} else {
|
|
stmt_ = stmt;
|
|
}
|
|
// 解析过程名称
|
|
if (OB_SUCC(ret)) {
|
|
if (T_SP_ACCESS_NAME != name_node->type_) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("Invalid procedure name node", K(name_node->type_), K(ret));
|
|
} else {
|
|
if (OB_FAIL(ObResolverUtils::resolve_sp_access_name(*schema_checker_,
|
|
session_info_->get_effective_tenant_id(),
|
|
session_info_->get_database_name(),
|
|
*name_node,
|
|
db_name, package_name, sp_name))) {
|
|
LOG_WARN("resolve sp name failed", K(ret));
|
|
} else if (db_name.empty() && session_info_->get_database_name().empty()) {
|
|
ret = OB_ERR_NO_DB_SELECTED;
|
|
LOG_WARN("no database selected", K(ret), K(db_name));
|
|
} else {
|
|
if (!db_name.empty()) {
|
|
OX (stmt->set_db_name(db_name));
|
|
} else {
|
|
OX (stmt->set_db_name(session_info_->get_database_name()));
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
ObSEArray<ObRawExpr*, 16> expr_params;
|
|
// 获取routine schem info
|
|
if (OB_SUCC(ret)) {
|
|
if (OB_NOT_NULL(params_node)
|
|
&& OB_FAIL(resolve_param_exprs(params_node, expr_params))) {
|
|
LOG_WARN("failed to resolve param exprs", K(ret));
|
|
} else if (OB_FAIL(ObResolverUtils::get_routine(params_,
|
|
(*session_info_).get_effective_tenant_id(),
|
|
(*session_info_).get_database_name(),
|
|
db_name,
|
|
package_name,
|
|
sp_name,
|
|
ROUTINE_PROCEDURE_TYPE,
|
|
expr_params,
|
|
proc_info))) {
|
|
LOG_WARN("failed to get routine info", K(ret), K(db_name), K(package_name), K(sp_name));
|
|
} else if (OB_ISNULL(proc_info)) {
|
|
ret = OB_ERR_UNEXPECTED;
|
|
LOG_WARN("proc info is null", K(ret), K(db_name), K(package_name), K(sp_name), K(proc_info));
|
|
} else if (proc_info->has_accessible_by_clause()) {
|
|
ret = OB_ERR_MISMATCH_SUBPROGRAM;
|
|
LOG_WARN("PLS-00263: mismatch between string on a subprogram specification and body",
|
|
K(ret), KPC(proc_info));
|
|
}
|
|
if (OB_SUCC(ret) && proc_info->is_udt_routine() && !proc_info->is_udt_static_routine()) {
|
|
ret = OB_ERR_CALL_WRONG_ARG;
|
|
LOG_USER_ERROR(OB_ERR_CALL_WRONG_ARG, proc_info->get_routine_name().length(),
|
|
proc_info->get_routine_name().ptr());
|
|
}
|
|
if (OB_SUCC(ret) && proc_info->is_udt_routine()) {
|
|
stmt->set_is_udt_routine(true);
|
|
}
|
|
if (OB_SUCC(ret)) {
|
|
ObSchemaObjVersion obj_version;
|
|
obj_version.object_id_ = proc_info->get_routine_id();
|
|
obj_version.object_type_ = DEPENDENCY_PROCEDURE;
|
|
obj_version.version_ = proc_info->get_schema_version();
|
|
OZ (stmt->add_global_dependency_table(obj_version));
|
|
}
|
|
}
|
|
// 解析参数列表
|
|
if (OB_SUCC(ret) && params_.is_execute_call_stmt_) {
|
|
OZ (stmt->add_params(expr_params));
|
|
OX (stmt->set_can_direct_use_param(true));
|
|
} else {
|
|
OZ (resolve_cparams(params_node, proc_info, stmt));
|
|
}
|
|
|
|
if (OB_SUCC(ret)) {
|
|
if (OB_INVALID_ID == proc_info->get_package_id()) {
|
|
//standalone procedure
|
|
stmt->set_package_id(proc_info->get_package_id());
|
|
stmt->set_routine_id(proc_info->get_routine_id());
|
|
} else {
|
|
//package procedure
|
|
stmt->set_package_id(proc_info->get_package_id());
|
|
stmt->set_routine_id(proc_info->get_subprogram_id());
|
|
}
|
|
|
|
for (int64_t i = 0; OB_SUCC(ret) && i < proc_info->get_param_count(); ++i) {
|
|
const ObRoutineParam *param_info = proc_info->get_routine_params().at(i);
|
|
const ObRawExpr *param_expr = stmt->get_params().at(i);
|
|
pl::ObPLDataType pl_type;
|
|
CK (OB_NOT_NULL(param_info));
|
|
CK (OB_NOT_NULL(param_expr));
|
|
|
|
if (OB_SUCC(ret)) {
|
|
CK (OB_NOT_NULL(schema_checker_->get_schema_mgr()));
|
|
CK (OB_NOT_NULL(params_.sql_proxy_));
|
|
CK (OB_NOT_NULL(params_.allocator_));
|
|
CK (OB_NOT_NULL(session_info_));
|
|
OZ (pl::ObPLDataType::transform_from_iparam(param_info,
|
|
*(schema_checker_->get_schema_mgr()),
|
|
*(session_info_),
|
|
*(params_.allocator_),
|
|
*(params_.sql_proxy_),
|
|
pl_type));
|
|
if (OB_FAIL(ret)) {
|
|
} else if (params_.is_prepare_protocol_
|
|
&& params_.is_prepare_stage_
|
|
&& param_expr->get_expr_type() != T_QUESTIONMARK) {
|
|
// do nothing ...
|
|
} else if (!param_info->is_extern_type()) {
|
|
} else {
|
|
if (OB_SUCC(ret)) {
|
|
//not support complex param not in prepare, except default value
|
|
if (pl_type.is_user_type()) {
|
|
if (!params_.is_prepare_protocol_
|
|
&& !param_expr->has_flag(IS_PL_MOCK_DEFAULT_EXPR)) {
|
|
ret = OB_ERR_CALL_WRONG_ARG;
|
|
LOG_WARN("PLS-00306: wrong number or types of arguments in call stmt", K(ret));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (OB_SUCC(ret)) {
|
|
if (param_info->is_out_sp_param() || param_info->is_inout_sp_param()) {
|
|
const ObRawExpr* param = stmt->get_params().at(i);
|
|
if (lib::is_mysql_mode()
|
|
&& param->get_expr_type() != T_OP_GET_USER_VAR
|
|
&& param->get_expr_type() != T_OP_GET_SYS_VAR) {
|
|
ret = OB_ER_SP_NOT_VAR_ARG;
|
|
LOG_USER_ERROR(OB_ER_SP_NOT_VAR_ARG, static_cast<int32_t>(i), static_cast<int32_t>(sp_name.length()), sp_name.ptr());
|
|
LOG_WARN("OUT or INOUT argument for routine is not a variable", K(param->get_expr_type()), K(ret));
|
|
} else if (param_info->is_sys_refcursor_type()
|
|
|| (param_info->is_pkg_type() && pl_type.is_cursor_type())) {
|
|
OZ (stmt->add_out_param(i,
|
|
param_info->get_mode(),
|
|
param_info->get_param_name(),
|
|
pl_type,
|
|
ObString("SYS_REFCURSOR"),
|
|
ObString("")));
|
|
} else if (param_info->is_complex_type()) { // UDT
|
|
if (param_info->get_type_owner() == session_info_->get_database_id()) {
|
|
CK (!session_info_->get_database_name().empty());
|
|
OZ (stmt->add_out_param(i,
|
|
param_info->get_mode(),
|
|
param_info->get_param_name(),
|
|
pl_type,
|
|
param_info->get_type_name(),
|
|
session_info_->get_database_name()));
|
|
} else {
|
|
const ObDatabaseSchema *db_schema = NULL;
|
|
CK (OB_NOT_NULL(schema_checker_));
|
|
CK (OB_NOT_NULL(schema_checker_->get_schema_mgr()));
|
|
OZ (schema_checker_->get_schema_mgr()->get_database_schema(param_info->get_tenant_id(),
|
|
param_info->get_type_owner(), db_schema), param_info->get_type_owner());
|
|
if (OB_SUCC(ret) && OB_ISNULL(db_schema)) {
|
|
ret = OB_ERR_BAD_DATABASE;
|
|
LOG_WARN("failed to get type owner", K(param_info->get_type_owner()));
|
|
}
|
|
OZ (stmt->add_out_param(i,
|
|
param_info->get_mode(),
|
|
param_info->get_param_name(),
|
|
pl_type,
|
|
param_info->get_type_name(),
|
|
OB_SYS_TENANT_ID == db_schema->get_tenant_id()
|
|
? ObString("SYS") : db_schema->get_database_name_str()), i);
|
|
}
|
|
} else if (pl_type.is_user_type()) {
|
|
// 通过Call语句执行PL且参数是复杂类型的情况, 仅在PS模式支持, 通过客户端无法构造复杂数据类型;
|
|
// PS模式仅支持UDT作为出参, 这里将其他模式的复杂类型出参禁掉;
|
|
ret = OB_NOT_SUPPORTED;
|
|
LOG_WARN("not supported other type as out parameter except udt", K(ret), K(pl_type.is_user_type()));
|
|
LOG_USER_ERROR(OB_NOT_SUPPORTED, "other complex type as out parameter except user define type");
|
|
} else {
|
|
OZ (stmt->add_out_param(i,
|
|
param_info->get_mode(),
|
|
param_info->get_param_name(),
|
|
pl_type,
|
|
param_info->get_type_name(),
|
|
ObString("")));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
}
|
|
}
|