列引用的值在表达式执行阶段如何计算出来的?

例如:

create table t1 (c1 int, c2 int, c3 int);
insert into t1 values(1, 3, 2 between c1 and c2);

对于表达式2 between c1 and c2
通过学习表达式构建的逻辑,发现在ObStaticEngineExprCG::cg_expr_by_operator中对于列引用没有生成对应的ObExpr的执行回调函数。

ObExprGeneratorImpl::visit(ObColumnRefRawExpr &expr)中,会执行sql_expr_->add_expr_item(item, &expr),但由于此时sql_expr_指向子类对象为ObExprOperatorFetcher

所以之后会执行ObExprOperatorFetcher::add_expr_item方法。

    if (IS_EXPR_OP(item.get_item_type())) {
      if (NULL != op_) {
        ret = common::OB_ERR_UNEXPECTED;
        SQL_ENG_LOG(WARN, "only one expr operator expected", K(ret));
      } else {
        op_ = item.get_expr_operator();
      }
    } else {
      op_ = NULL;
    }

这个函数只会对操作类型的表达式提取item的expr_operator。而对于列引用设置op_=NULL。
此时回到cg_expr_by_operator,对于op_=NULL不做处理。

 else if (NULL == expr_op_fetcher.op_) {
      // do nothing, some raw do not generate expr operator. e.g: T_OP_ROW
    }

也就是此时对于列引用表达式对应得ObExpr,只会有基本信息(例如表达式类型,参数个数等等信息)。
最后查阅ObExprValue算子执行时的表达式执行逻辑。
最开始的ObExpr对应between and。

  } else if (NULL != eval_func_ && !eval_info->evaluated_) {
	// do nothing for const/column reference expr or already evaluated expr
    if (OB_UNLIKELY(need_stack_check_) && OB_FAIL(check_stack_overflow())) {
      SQL_LOG(WARN, "failed to check stack overflow", K(ret));
    } else {
      if (datum->ptr_ != frame + res_buf_off_) {
        datum->ptr_ = frame + res_buf_off_;
      }
      ret = eval_func_(*this, ctx, *datum);
      CHECK_STRING_LENGTH((*this), (*datum));
      if (OB_LIKELY(common::OB_SUCCESS == ret)) {
        eval_info->evaluated_ = true;
      } else {
        datum->set_null();
      }
    }
	}

其中eval_func_会执行

int calc_between_expr(const ObExpr &expr, ObEvalCtx &ctx, ObDatum &res_datum)
{
  // left <= val <= right
  int ret = OB_SUCCESS;
  ObDatum *val = NULL;
  ObDatum *left = NULL;
  ObDatum *right = NULL;
  if (OB_FAIL(expr.args_[0]->eval(ctx, val))) {
    LOG_WARN("eval arg 0 failed", K(ret));
  } else if (val->is_null()) {
    res_datum.set_null();
  } else if (OB_FAIL(expr.args_[1]->eval(ctx, left))) {
    LOG_WARN("eval arg 1 failed", K(ret));
  } else if (OB_FAIL(expr.args_[2]->eval(ctx, right))) {
    LOG_WARN("eval arg 2 failed", K(ret));
  } 
......

}

在这个函数中会先算出参数表达式的结果。
例如:对于2 between c1 and c2中的列引用c1,此时由于eval_func_为NULL(因为生成列引用ObExpr时没有设置),所以datum (也就是列引用对应的结果)指向(frame + datum_off_)

	char *frame = ctx.frames_[frame_idx_];
    OB_ASSERT(NULL != frame);
	datum = (ObDatum *)(frame + datum_off_);

frame是从ObEvalCtx &ctx中获取到的。
之后查阅了ObExprValue的open阶段,发现此时设置ctx,仅仅是分配内存,并没有列引用的填充流程。

所以最终的疑问是:列引用在表达式中是如何计算出来的?


OceanBase(admin@test)>explain insert into t1 values(1, 3, 4 between c1 and c2);
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                                                                                        |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ==================================================                                                                                                                |
| |ID|OPERATOR          |NAME|EST.ROWS|EST.TIME(us)|                                                                                                                |
| --------------------------------------------------                                                                                                                |
| |0 |DISTRIBUTED INSERT|    |1       |13          |                                                                                                                |
| |1 |└─EXPRESSION      |    |1       |1           |                                                                                                                |
| ==================================================                                                                                                                |
| Outputs & filters:                                                                                                                                                |
| -------------------------------------                                                                                                                             |
|   0 - output(nil), filter(nil)                                                                                                                                    |
|       columns([{t1: ({t1: (t1.__pk_increment, t1.c1, t1.c2, t1.c3)})}]),                                                                                          |
|       column_values([T_HIDDEN_PK], [column_conv(INT,PS:(11,0),NULL,__values.c1)], [column_conv(INT,PS:(11,0),NULL,__values.c2)], [column_conv(INT,PS:(11,         |
|       0),NULL,__values.c3)])                                                                                                                                      |
|   1 - output([__values.c1], [__values.c2], [__values.c3]), filter(nil)                                                                                            |
|       values({column_conv(INT,PS:(11,0),NULL,cast(1, INT(-1, 0))), column_conv(INT,PS:(11,0),NULL,cast(3, INT(-1, 0))), (T_OP_BTW, 4, __values.c1, __values.c2)}) |

从计划上看这个是常量表达式,应该提前就算好的

1 个赞

感谢回复。
补充如下:
我举得的这个例子可能有点特殊。
翻阅resolver层的解析流程后发现ob对于以上的insert语句中values子句的表达式会调用以下成员函数进行处理。

//insert into test values(1, c1 + 2);
//本函数用来解决c1的取值过程。先查找到C1对应的expr 1,然后将expr 1 变成column_conv(1) ;
//将 c1+2==> column_conv(1) + 2;
int ObInsertResolver::replace_column_ref(ObArray<ObRawExpr*> *value_row,
                                         ObRawExpr *&expr,
                                         bool in_generated_column /*default false*/)

相当于在resolver层就将c1,c2 的值作为常量填充到了表达式之中。所以之后也就不会走列引用的处理逻辑了。

再回过来探讨列引用的值是在什么时候获取的呢?
重新举一个例子:

create table t1 (c1 datetime, c2 int, c3 varchar(10));
insert into t1 values(current_date, 1, 'day1'),(current_date, 2, 'day2'),(current_date, 3, 'day3');
select str_to_date(c1, %Y%m%d) from t1;

通过explain可以看到最后执行的算子为TABLE FULL SCAN,对应源码中就是ObTableScanOp算子:

obclient [test]> explain  select date_format(c1, '%Y-%m-%d %h:%i:%s') from t1;
+---------------------------------------------------------------------------------+
| Query Plan                                                                      |
+---------------------------------------------------------------------------------+
| ===============================================                                 |
| |ID|OPERATOR       |NAME|EST.ROWS|EST.TIME(us)|                                 |
| -----------------------------------------------                                 |
| |0 |TABLE FULL SCAN|t1  |3       |3           |                                 |
| ===============================================                                 |
| Outputs & filters:                                                              |
| -------------------------------------                                           |
|   0 - output([date_format(t1.c1, '%Y-%m-%d %h:%i:%s')]), filter(nil), rowset=16 |
|       access([t1.c1]), partitions(p0)                                           |
|       is_index_back=false, is_global_index=false,                               |
|       range_key([t1.__pk_increment]), range(MIN ; MAX)always true               |
+---------------------------------------------------------------------------------+
11 rows in set (0.054 sec)

对于这条select语句查看执行引擎中它的部分调用栈:

#0  oceanbase::sql::ObExprDateFormat::calc_date_format(oceanbase::sql::ObExpr const&, oceanbase::sql::ObEvalCtx&, oceanbase::common::ObDatum&) (expr=..., 
    ctx=..., expr_datum=...) at ./src/sql/engine/expr/ob_expr_date_format.cpp:72
#1  0x000055b0490664e3 in oceanbase::sql::expr_default_eval_batch_func(oceanbase::sql::ObExpr const&, oceanbase::sql::ObEvalCtx&, oceanbase::sql::ObBitVectorImpl<unsigned long> const&, oceanbase::sql::EvalBound const&) (expr=..., ctx=..., skip=..., bound=...) at ./src/sql/engine/expr/ob_expr.cpp:1211
#2  0x000055b049066850 in oceanbase::sql::expr_default_eval_batch_func(oceanbase::sql::ObExpr const&, oceanbase::sql::ObEvalCtx&, oceanbase::sql::ObBitVectorImpl<unsigned long> const&, long) (expr=..., ctx=..., skip=..., size=3) at ./src/sql/engine/expr/ob_expr.cpp:1231
#3  0x000055b049066007 in oceanbase::sql::expr_default_eval_vector_func(oceanbase::sql::ObExpr const&, oceanbase::sql::ObEvalCtx&, oceanbase::sql::ObBitVectorImpl<unsigned long> const&, oceanbase::sql::EvalBound const&) (expr=..., ctx=..., skip=..., bound=...) at ./src/sql/engine/expr/ob_expr.cpp:1244
#4  0x000055b03dc9c9ca in oceanbase::sql::ObExpr::eval_vector(oceanbase::sql::ObEvalCtx&, oceanbase::sql::ObBitVectorImpl<unsigned long> const&, oceanbase::sql::EvalBound const&) const (this=0x7fcac188e138, ctx=..., skip=..., bound=...) at ./src/sql/engine/expr/ob_expr.cpp:1169
#5  0x000055b03dc6004e in eval_vector (this=0x7fcac188e138, ctx=..., skip=..., size=3, all_rows_active=true) at ./src/sql/engine/expr/ob_expr.h:1337
#6  eval_vector (this=0x7fcac188e138, ctx=..., brs=...) at ./src/sql/engine/expr/ob_expr.h:1329
#7  oceanbase::sql::ObOperator::get_next_batch(long, oceanbase::sql::ObBatchRows const*&) (this=0x7fcaa5e98bf0, max_row_cnt=9223372036854775807, 
    batch_rows=@0x7fcac67c5d98: 0x7fcaa5e98de8) at ./src/sql/engine/ob_operator.cpp:1328
#8  0x000055b03dc5ea32 in oceanbase::sql::ObBatchRowIter::get_next_row() (this=0x7fcac67c5d90) at ./src/sql/engine/ob_operator.cpp:1709
#9  0x000055b03dc5ca02 in oceanbase::sql::ObExecuteResult::get_next_row(oceanbase::sql::ObExecContext&, oceanbase::common::ObNewRow const*&) (
    this=0x7fcac67c5d58, ctx=..., row=@0x7fcac67c51d8: 0x7fcac67c5d70) at ./src/sql/executor/ob_execute_result.cpp:70
#10 0x000055b03dc5bd86 in inner_get_next_row (this=0x7fcac67c58b0, row=@0x7fcac67c51d8: 0x7fcac67c5d70) at ./src/sql/ob_result_set.cpp:408
#11 oceanbase::sql::ObResultSet::get_next_row(oceanbase::common::ObNewRow const*&) (this=0x7fcac67c58b0, row=@0x7fcac67c51d8: 0x7fcac67c5d70)
    at ./src/sql/ob_result_set.cpp:396

而在ObOperator::get_next_batch的中首先会执行Operator对应的实际算子类型为ObTableScanOpinner_get_next_batch

int ObOperator::get_next_batch(const int64_t max_row_cnt, const ObBatchRows *&batch_rows)
{
  ......

  while (OB_SUCC(ret) && !brs_.end_) {
        if (OB_FAIL(inner_get_next_batch(max_row_cnt))) {
          LOG_WARN("get next batch failed", K(ret),  K_(eval_ctx), "id", spec_.get_id(), "op_name", op_name());
        } else {
          LOG_DEBUG("inner get next batch", "id", spec_.get_id(), "op_name", op_name(), K(brs_));
        }
  ......
}

ObTableScanOp::inner_get_next_batch(const int64_t max_row_cnt)它的调用逻辑为:

ObTableScanOp::inner_get_next_batch(const int64_t max_row_cnt) -> 
ObTableScanOp::inner_get_next_batch_for_tsc(const int64_t max_row_cnt) ->
ObTableScanOp::get_next_batch_with_das(int64_t &count, int64_t capacity)

而在算子的open阶段可能已经拿到了数据,之后会填充到Operator的EvalCtx中。

int ObDASScanOp::open_op()
{
  ......
  int ret = OB_SUCCESS;
  ObITabletScan &tsc_service = get_tsc_service();
  //Retry may be called many times.
  //Only for DASScanOp now, we add a retry alloc to avoid
  //memory expansion.
  if (in_part_retry_) {
    init_retry_alloc();
  }
  reset_access_datums_ptr();
  if (OB_FAIL(init_scan_param())) {
    LOG_WARN("init scan param failed", K(ret));
  } else if (OB_FAIL(tsc_service.table_scan(scan_param_, result_))) {
  ......
}

最后回到表达式的执行date_format的时候对于列引用直接获取就行。目前推测大概是这么一个流程。
但是对于(1) ObDASScanOp如何拿取数据,(2) 如何填充到EvalCtx中还没有看到。期待补充。