code: func (p *DataSource) convert2PhysicalPlan(prop *requiredProperty) (*physicalPlanInfo, error) {
  	info, err := p.getPlanInfo(prop)
  	if err != nil {
  		return nil, errors.Trace(err)
  	}
  	if info != nil {
  		return info, nil
  	}
  	info, err = p.tryToConvert2DummyScan(prop)
  	if info != nil || err != nil {
  		return info, errors.Trace(err)
  	}
  
  	indices, includeTableScan := availableIndices(p.indexHints, p.tableInfo)
  	if includeTableScan {
  		info, err = p.convert2TableScan(prop)
  		if err != nil {
  			return nil, errors.Trace(err)
  		}
  	}
  	if !includeTableScan || p.need2ConsiderIndex(prop) {
  		for _, index := range indices {
  			indexInfo, err := p.convert2IndexScan(prop, index)
  			if err != nil {
  				return nil, errors.Trace(err)
  			}
  			if info == nil || indexInfo.cost < info.cost {
  				info = indexInfo
  			}
  		}
  	}
  	return info, errors.Trace(p.storePlanInfo(prop, info))
  }
docs: // convert2PhysicalPlan implements the LogicalPlan convert2PhysicalPlan interface.
  // If there is no index that matches the required property, the returned physicalPlanInfo
  // will be table scan and has the cost of MaxInt64. But this can be ignored because the parent will call
  // convert2PhysicalPlan again with an empty *requiredProperty, so the plan with the lowest
  // cost will be chosen.