/** * 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_PC #include "ob_prepare_stmt_struct.h" #include "lib/utility/utility.h" #include "lib/utility/ob_print_utils.h" #include "sql/plan_cache/ob_ps_sql_utils.h" #include "sql/plan_cache/ob_ps_cache.h" #include "sql/resolver/cmd/ob_call_procedure_stmt.h" #include "sql/parser/parse_node.h" namespace oceanbase { using namespace common; namespace sql { int ObPsSqlKey::deep_copy(const ObPsSqlKey &other, common::ObIAllocator &allocator) { int ret = OB_SUCCESS; flag_ = other.flag_; db_id_ = other.db_id_; inc_id_ = other.inc_id_; if (OB_FAIL(ObPsSqlUtils::deep_copy_str(allocator, other.ps_sql_, ps_sql_))) { LOG_WARN("deep copy str failed", K(other), K(ret)); } return ret; } ObPsSqlKey &ObPsSqlKey::operator=(const ObPsSqlKey &other) { if (this != &other) { flag_ = other.flag_; db_id_ = other.db_id_; inc_id_ = other.inc_id_; ps_sql_ = other.ps_sql_; } return *this; } bool ObPsSqlKey::operator==(const ObPsSqlKey &other) const { return flag_ == other.flag_ && db_id_ == other.db_id_ && inc_id_ == other.inc_id_ && ps_sql_.compare(other.ps_sql_) == 0; } int64_t ObPsSqlKey::hash() const { uint64_t hash_val = 0; hash_val = murmurhash(&flag_, sizeof(uint32_t), hash_val); hash_val = murmurhash(&db_id_, sizeof(uint64_t), hash_val); hash_val = murmurhash(&inc_id_, sizeof(uint64_t), hash_val); ps_sql_.hash(hash_val, hash_val); return hash_val; } ObPsStmtItem::ObPsStmtItem() : ref_count_(1), stmt_id_(OB_INVALID_STMT_ID), is_expired_evicted_(false), allocator_(NULL), external_allocator_(NULL) { } ObPsStmtItem::ObPsStmtItem(const ObPsStmtId ps_stmt_id) : ref_count_(1), stmt_id_(ps_stmt_id), is_expired_evicted_(false), allocator_(NULL), external_allocator_(NULL) { } ObPsStmtItem::ObPsStmtItem(ObIAllocator *inner_allocator, ObIAllocator *external_allocator) : ref_count_(1), stmt_id_(OB_INVALID_STMT_ID), is_expired_evicted_(false), allocator_(inner_allocator), external_allocator_(external_allocator) {} int ObPsStmtItem::deep_copy(const ObPsStmtItem &other) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else { stmt_id_ = other.stmt_id_; if (OB_FAIL(ps_key_.deep_copy(other.get_sql_key(), *allocator_))) { LOG_WARN("deep copy ps key failed", K(other), K(ret)); } } return ret; } ObPsStmtItem &ObPsStmtItem::operator=(const ObPsStmtItem &other) { if (this != &other) { ref_count_ = other.ref_count_; ps_key_ = other.get_sql_key(); stmt_id_ = other.get_ps_stmt_id(); is_expired_evicted_ = other.is_expired_evicted_; } return *this; } bool ObPsStmtItem::is_valid() const { bool bret = false; if (!ps_key_.ps_sql_.empty() && OB_INVALID_STMT_ID != stmt_id_) { bret = true; } return bret; } int ObPsStmtItem::get_convert_size(int64_t &cv_size) const { cv_size = sizeof(ObPsStmtItem); cv_size += ps_key_.ps_sql_.length() + 1; return OB_SUCCESS; } bool ObPsStmtItem::check_erase_inc_ref_count() { bool need_erase = false; bool cas_ret = false; do { need_erase = false; int64_t cur_ref_cnt = ATOMIC_LOAD(&ref_count_); int64_t next_ref_cnt = cur_ref_cnt + 1; if (0 == cur_ref_cnt) { need_erase = true; break; } cas_ret = ATOMIC_BCAS(&ref_count_, cur_ref_cnt, next_ref_cnt); LOG_TRACE("ps item check erase inc ref count after cas", K(cur_ref_cnt), K(next_ref_cnt), K(cas_ret)); } while (!cas_ret); LOG_TRACE("ps item inc ref count", K(*this), K(need_erase)); return need_erase; } void ObPsStmtItem::dec_ref_count_check_erase() { int ret = OB_SUCCESS; LOG_TRACE("ps item dec ref count", K(*this)); int64_t ref_count = ATOMIC_SAF(&ref_count_, 1); if (ref_count > 0) { LOG_TRACE("ps item dec ref count", K(ref_count)); } else if (0 == ref_count) { LOG_INFO("free ps item", K(ref_count), K(*this)); ObPsStmtItem *ps_item = this; ObIAllocator *allocator = NULL; if (OB_ISNULL(allocator = get_external_allocator())) { ret = OB_INVALID_ARGUMENT; LOG_WARN("invalid argument", K(allocator), K(ret)); } else { ps_item->~ObPsStmtItem(); allocator->free(ps_item); } } else if (ref_count < 0) { BACKTRACE(ERROR, true, "ObPsStmtItem %p ref count < 0, ref_count = %ld", this, ref_count); } } int ObPsSqlMeta::add_param_field(const ObField &field) { int ret = OB_SUCCESS; ObField tmp_field; if (OB_FAIL(tmp_field.full_deep_copy(field, allocator_))) { LOG_WARN("deep copy field failed", K(ret)); } else if (OB_FAIL(param_fields_.push_back(tmp_field))) { LOG_WARN("push back field param failed", K(ret)); } else { LOG_DEBUG("succ to push back param field", K(field)); } return ret; } int ObPsSqlMeta::add_column_field(const ObField &field) { int ret = OB_SUCCESS; ObField tmp_field; if (OB_FAIL(tmp_field.full_deep_copy(field, allocator_))) { LOG_WARN("deep copy field failed", K(ret)); } else if (OB_FAIL(column_fields_.push_back(tmp_field))) { LOG_WARN("push back field param failed", K(ret)); } else { LOG_DEBUG("succ to push back column field", K(field)); } return ret; } int ObPsSqlMeta::reverse_fileds(int64_t param_size, int64_t column_size) { int64_t ret = OB_SUCCESS; if (OB_FAIL(param_fields_.init(param_size))) { LOG_WARN("fail to init param fields", K(ret), K(param_size)); } else if (OB_FAIL(column_fields_.init(column_size))) { LOG_WARN("fail to init column fields", K(ret), K(column_size)); } return ret; } int ObPsSqlMeta::deep_copy(const ObPsSqlMeta &sql_meta) { int ret = OB_SUCCESS; const int64_t column_size = sql_meta.get_column_size(); const int64_t param_size = sql_meta.get_param_size(); if (OB_FAIL(reverse_fileds(param_size, column_size))) { LOG_WARN("reserve_fix_size failed", K(column_size), K(param_size)); } else { for (int64_t i = 0; OB_SUCC(ret) && i < param_size; ++i) { if (OB_FAIL(add_param_field(sql_meta.get_param_fields().at(i)))) { LOG_WARN("add column field failed", K(ret)); } } for (int64_t i = 0; OB_SUCC(ret) && i < column_size; ++i) { if (OB_FAIL(add_column_field(sql_meta.get_column_fields().at(i)))) { LOG_WARN("add column field failed", K(ret)); } } } return ret; } int ObPsSqlMeta::get_convert_size(int64_t &cv_size) const { int ret = OB_SUCCESS; cv_size = 0; int64_t convert_size = sizeof(ObPsSqlMeta); for (int i = 0 ; OB_SUCC(ret) && i < column_fields_.count(); ++i) { convert_size += column_fields_.at(i).get_convert_size(); } for (int i = 0; OB_SUCC(ret) && i < param_fields_.count(); ++i) { convert_size += param_fields_.at(i).get_convert_size(); } if (OB_SUCC(ret)) { cv_size = convert_size; } return ret; } ObPsStmtInfo::ObPsStmtInfo(ObIAllocator *inner_allocator) : stmt_type_(stmt::T_NONE), ps_stmt_checksum_(0), ps_sql_meta_(inner_allocator), ref_count_(1), question_mark_count_(0), can_direct_use_param_(false), item_and_info_size_(0), last_closed_timestamp_(0), dep_objs_(NULL), dep_objs_cnt_(0), ps_item_(NULL), tenant_version_(OB_INVALID_VERSION), is_expired_(false), is_expired_evicted_(false), allocator_(inner_allocator), external_allocator_(inner_allocator), num_of_returning_into_(-1), no_param_sql_(), is_sensitive_sql_(false), raw_sql_(), raw_params_(inner_allocator), raw_params_idx_(inner_allocator), literal_stmt_type_(stmt::T_NONE) { } ObPsStmtInfo::ObPsStmtInfo(ObIAllocator *inner_allocator, ObIAllocator *external_allocator) : stmt_type_(stmt::T_NONE), ps_stmt_checksum_(0), ps_sql_meta_(inner_allocator), ref_count_(1), question_mark_count_(0), can_direct_use_param_(false), item_and_info_size_(0), last_closed_timestamp_(0), dep_objs_(NULL), dep_objs_cnt_(0), ps_item_(NULL), tenant_version_(OB_INVALID_VERSION), is_expired_(false), is_expired_evicted_(false), allocator_(inner_allocator), external_allocator_(external_allocator), num_of_returning_into_(-1), no_param_sql_(), is_sensitive_sql_(false), raw_sql_(), raw_params_(inner_allocator), raw_params_idx_(inner_allocator), literal_stmt_type_(stmt::T_NONE) { } bool ObPsStmtInfo::is_valid() const { return !ps_key_.ps_sql_.empty(); } int ObPsStmtInfo::assign_no_param_sql(const common::ObString &no_param_sql) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else if (OB_FAIL(ObPsSqlUtils::deep_copy_str(*allocator_, no_param_sql, no_param_sql_))) { LOG_WARN("deep copy no param sql failed", K(no_param_sql), K(ret)); } return ret; } int ObPsStmtInfo::assign_raw_sql(const common::ObString &raw_sql) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else if (OB_FAIL(ObPsSqlUtils::deep_copy_str(*allocator_, raw_sql, raw_sql_))) { LOG_WARN("deep copy raw sql failed", K(raw_sql), K(ret)); } return ret; } int ObPsStmtInfo::add_fixed_raw_param(const ObPCParam &node) { int ret = OB_SUCCESS; char *buf = NULL; ObPCParam *param = NULL; int64_t alloc_size = sizeof(ObPCParam) + sizeof(ParseNode); if (OB_ISNULL(buf = static_cast(allocator_->alloc(alloc_size)))) { ret = OB_ALLOCATE_MEMORY_FAILED; LOG_WARN("fail to alloc memory", K(ret)); } else { MEMSET(buf, 0, alloc_size); param = reinterpret_cast(buf); buf += sizeof(ObPCParam); param->node_ = reinterpret_cast(buf); if (OB_FAIL(deep_copy_parse_node(allocator_, node.node_, param->node_))) { LOG_WARN("fail to deep copy parse node", K(ret)); } else if (FALSE_IT(param->flag_ = node.flag_)) { } else { if (NEG_PARAM == param->flag_) { int64_t tmp_pos = 0; if ('-' != param->node_->str_value_[tmp_pos]) { ret = OB_ERR_UNEXPECTED; LOG_WARN("expected neg sign here", K(tmp_pos), K(ObString(param->node_->str_len_, param->node_->str_value_))); } else { tmp_pos += 1; for (; tmp_pos < param->node_->str_len_ && isspace(param->node_->str_value_[tmp_pos]); tmp_pos++); const_cast(param->node_->str_value_)[tmp_pos - 1] = '-'; param->node_->str_value_ += (tmp_pos - 1); param->node_->str_len_ -= (tmp_pos - 1); } } OZ (raw_params_.push_back(param)); } } return ret; } int ObPsStmtInfo::assign_fixed_raw_params(const common::ObIArray ¶m_idxs, const common::ObIArray &raw_params) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else if (OB_FAIL(raw_params_.reserve(param_idxs.count()))) { LOG_WARN("failed to reserve array", K(ret)); } else { for (int64_t i = 0; OB_SUCC(ret) && i < param_idxs.count(); ++i) { ObPCParam *tmp_param = NULL; const int64_t idx = param_idxs.at(i); if (idx >= raw_params.count()) { ret = OB_ERR_UNEXPECTED; LOG_WARN("invalid idx", K(ret), K(idx), K(raw_params.count())); } else if (OB_ISNULL(tmp_param = raw_params.at(idx)) || OB_ISNULL(tmp_param->node_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("node is null", K(idx), K(tmp_param)); } else if (OB_FAIL(add_fixed_raw_param(*tmp_param))) { LOG_WARN("fail to add fixed raw param", K(ret)); } } } if (OB_SUCC(ret)) { if (OB_FAIL(raw_params_idx_.assign(param_idxs))) { LOG_WARN("failed to assign raw params idx", K(ret)); } } return ret; } int ObPsStmtInfo::deep_copy_fixed_raw_params(const common::ObIArray ¶m_idxs, const common::ObIArray &raw_params) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else if (OB_FAIL(raw_params_.reserve(raw_params.count()))) { LOG_WARN("failed to reserve array", K(ret)); } else { for (int64_t i = 0; OB_SUCC(ret) && i < raw_params.count(); ++i) { if (OB_FAIL(add_fixed_raw_param(*raw_params.at(i)))) { LOG_WARN("fail to add fixed raw param", K(ret), K(i)); } } } if (OB_SUCC(ret)) { if (OB_FAIL(raw_params_idx_.assign(param_idxs))) { LOG_WARN("failed to assign raw params idx", K(ret)); } } return ret; } int ObPsStmtInfo::deep_copy(const ObPsStmtInfo &other) { int ret = OB_SUCCESS; if (OB_ISNULL(allocator_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("allocator is invalid", K(ret)); } else { stmt_type_ = other.stmt_type_; ps_stmt_checksum_ = other.ps_stmt_checksum_; question_mark_count_ = other.question_mark_count_; num_of_returning_into_ = other.num_of_returning_into_; is_sensitive_sql_ = other.is_sensitive_sql_; can_direct_use_param_ = other.can_direct_use_param(); item_and_info_size_ = other.item_and_info_size_; ps_item_ = other.ps_item_; tenant_version_ = other.tenant_version_; is_expired_ = other.is_expired_; is_expired_evicted_ = other.is_expired_evicted_; literal_stmt_type_ = other.literal_stmt_type_; if (other.get_dep_objs_cnt() > 0) { dep_objs_cnt_ = other.get_dep_objs_cnt(); if (NULL == (dep_objs_ = reinterpret_cast (allocator_->alloc(dep_objs_cnt_ * sizeof(ObSchemaObjVersion))))) { ret = OB_ALLOCATE_MEMORY_FAILED; LOG_WARN("fail to alloc memory", K(ret), K(dep_objs_cnt_)); } else { MEMCPY(dep_objs_, other.get_dep_objs(), dep_objs_cnt_*sizeof(ObSchemaObjVersion)); } } if (OB_FAIL(ret)) { // do nothing } else if (OB_FAIL(ps_key_.deep_copy(other.get_sql_key(), *allocator_))) { LOG_WARN("failed to deep copy ps key", K(ret), K(other)); } else if (OB_FAIL(ObPsSqlUtils::deep_copy_str(*allocator_, other.get_no_param_sql(), no_param_sql_))) { LOG_WARN("deep copy str failed", K(other), K(ret)); } else if (OB_FAIL(ObPsSqlUtils::deep_copy_str(*allocator_, other.get_raw_sql(), raw_sql_))) { LOG_WARN("deep copy str failed", K(other), K(ret)); } else if (OB_FAIL(deep_copy_fixed_raw_params(other.raw_params_idx_, other.raw_params_))) { LOG_WARN("deep copy fixed raw params failed", K(other), K(ret)); } else if (OB_FAIL(ps_sql_meta_.deep_copy(other.get_ps_sql_meta()))) { LOG_WARN("deep copy ps sql meta faield", K(ret)); } } return ret; } int ObPsStmtInfo::add_column_field(const ObField &field) { int ret = OB_SUCCESS; if (OB_FAIL(ps_sql_meta_.add_column_field(field))) { LOG_WARN("add column field failed", K(ret)); } return ret; } int ObPsStmtInfo::add_param_field(const ObField ¶m) { int ret = OB_SUCCESS; if (OB_FAIL(ps_sql_meta_.add_param_field(param))) { LOG_WARN("add param field failed", K(ret)); } return ret; } int ObPsStmtInfo::get_convert_size(int64_t &cv_size) const { int ret = OB_SUCCESS; cv_size = 0; int64_t convert_size = sizeof(ObPsStmtInfo); convert_size += ps_key_.ps_sql_.length() + 1; convert_size += no_param_sql_.length() + 1; convert_size += raw_sql_.length() + 1; int64_t meta_convert_size = 0; int64_t raw_params_size = 0; if (OB_FAIL(ps_sql_meta_.get_convert_size(meta_convert_size))) { LOG_WARN("get sql meta convert size failed", K(ret)); } for (int i = 0; OB_SUCC(ret) && i < raw_params_.count(); ++i) { const ObPCParam *param = raw_params_.at(i); if (OB_ISNULL(param)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("is null", K(ret)); } else if (OB_FAIL(get_deep_copy_size(param->node_, &raw_params_size))) { LOG_WARN("failed to get deeps copy size", K(ret)); } else { raw_params_size += sizeof(ObPCParam); } } if (OB_SUCC(ret)) { raw_params_size += (raw_params_.count() * sizeof(ObPCParam*)); raw_params_size += (raw_params_idx_.count() * sizeof(int64_t)); convert_size += meta_convert_size; convert_size += raw_params_size; convert_size += dep_objs_cnt_ * sizeof(share::schema::ObSchemaObjVersion); cv_size = convert_size; } return ret; } /* 对于ps cache并发场景: A线程接收到close请求,B线程接收到prepare_command请求, 且在接收请求前ref_cnt=1 ps cache中, ps_id->stmt_info这个map: 1. A线程在close stmt时, 会先将引用计数减1, 如果发现为0, 则将stmt_info从map 上摘除, 并释放stmt_info资源 2. B线程在获取stmt_info时(prepare), 会先判定stmt_info是否为0, 如果为0则表示 该stmt_info即将被删除, 则直接退出并进行获取重试; 不为0则将引用计数加1; 3. 上面的两个操作, 操作a. 减引用计数,并判断是否删除, 操作b. 判断是否已删除, 以及加引用计数, 这两个操作是需要互斥执行的, 否则会出现下图的并发问题: A线程(close_command) B线程(prepare_command) | read_atomic (ref_cnt<=0判断为false) read_atomic (ref_cnt-1 = 0, is_erase=true) read_atomic_end | | | (ref_cnt+1=1) read_atomic_end | stmt_info_map_.erase free(stmt_info_) 此时stmt_info_内存已释放, 当再访问时可能core, 或者继续进行execute时, 会报-4201(NOT_EXIST) 4. 从面的过程可以看到, 其实操作a和操作b是在hash_map中read_atomic中执行的, 但read_atomic这个接口加的是读锁,并不互斥,上面两个操作的互斥性需要另外保障, 因此加入了如下处理方式, 使用这两个接口, 确保操作a和操作b的原子性: 1) check_erase_inc_ref_count: 使用cas确保操作b在判断完是否erase和加引用 计数过程是中间没有修改引用计数的, 如果修改了, 则重新进行操作b的原子处理; 2) dec_ref_count_check_erase: 原子减引用计数, 并返回减引用计数后结果, 判断是否已erase */ bool ObPsStmtInfo::check_erase_inc_ref_count() { bool need_erase = false; bool cas_ret = false; do { need_erase = false; int64_t cur_ref_cnt = ATOMIC_LOAD(&ref_count_); int64_t next_ref_cnt = cur_ref_cnt + 1; if (0 == cur_ref_cnt) { need_erase = true; break; } cas_ret = ATOMIC_BCAS(&ref_count_, cur_ref_cnt, next_ref_cnt); LOG_TRACE("ps info check erase inc ref count after cas", K(cur_ref_cnt), K(next_ref_cnt), K(cas_ret)); } while (!cas_ret); LOG_TRACE("ps info inc ref count", K(*this), K(need_erase)); return need_erase; } bool ObPsStmtInfo::dec_ref_count_check_erase() { bool need_erase = false; LOG_TRACE("ps info dec ref count", K(*this)); int64_t ref_count = ATOMIC_SAF(&ref_count_, 1); if (ref_count > 0) { if (ref_count == 1) { last_closed_timestamp_ = common::ObTimeUtility::current_time(); } LOG_TRACE("ps info dec ref count", K(ref_count), K(*this)); } else if (0 == ref_count) { need_erase = true; LOG_INFO("free ps info", K(ref_count), K(*this), K(need_erase)); } else if (ref_count < 0) { BACKTRACE_RET(ERROR, OB_ERR_UNEXPECTED, true, "ObPsStmtInfo %p ref count < 0, ref_count = %ld", this, ref_count); } return need_erase; } int64_t ObPsStmtInfo::to_string(char *buf, const int64_t buf_len) const { int64_t pos = 0; int ret = OB_SUCCESS; J_OBJ_START(); J_KV(K_(ps_key), K_(ref_count), K_(can_direct_use_param), K_(question_mark_count), K_(last_closed_timestamp), K_(tenant_version), K_(no_param_sql), K_(num_of_returning_into)); J_COMMA(); J_NAME("columns"); J_COLON(); J_ARRAY_START(); for (int64_t i = 0 ; OB_SUCC(ret) && i < ps_sql_meta_.get_column_fields().count(); ++i) { J_KV("column", ps_sql_meta_.get_column_fields().at(i)); } J_ARRAY_END(); J_COMMA(); J_NAME("params"); J_COLON(); J_ARRAY_START(); for (int64_t i = 0 ; OB_SUCC(ret) && i < ps_sql_meta_.get_param_fields().count(); ++i) { J_KV("params", ps_sql_meta_.get_param_fields().at(i)); } J_ARRAY_END(); J_OBJ_END(); return pos; } ObPsStmtInfoGuard::~ObPsStmtInfoGuard() { int ret = OB_SUCCESS; if (NULL != ps_cache_) { if (NULL != stmt_info_) { if (OB_FAIL(ps_cache_->deref_stmt_info(stmt_id_))) { LOG_WARN("deref stmt item faield", K(ret), K(*stmt_info_), K_(stmt_id)); } LOG_TRACE("destroy PsStmtInfo Guard", K_(stmt_id)); } else { LOG_WARN("stmt info is null", K(stmt_id_)); } } else { LOG_WARN("ps_cache is null", K(stmt_id_), K(stmt_info_)); } } int ObPsStmtInfoGuard::get_ps_sql(ObString &ps_sql) { int ret = OB_SUCCESS; if (OB_ISNULL(stmt_info_)) { ret = OB_ERR_UNEXPECTED; LOG_WARN("stmt info is null", K(ret)); } else { ps_sql = stmt_info_->get_ps_sql(); } return ret; } int TypeInfo::deep_copy(common::ObIAllocator *allocator, const TypeInfo* other) { int ret = OB_SUCCESS; if (this != other) { CK (OB_NOT_NULL(allocator)); CK (OB_NOT_NULL(other)); OZ (ob_write_string(*allocator, other->relation_name_, relation_name_)); OZ (ob_write_string(*allocator, other->package_name_, package_name_)); OZ (ob_write_string(*allocator, other->type_name_, type_name_)); OX (elem_type_ = other->elem_type_); OX (is_elem_type_ = other->is_elem_type_); OX (is_basic_type_ = other->is_basic_type_); } return ret; } } // end of sql } // end of oceanbase