Repository: pingcap/tidb
File: pkg/planner/core/operator/logicalop/logical_datasource.go (Go)

Code before:
		} else {
			cols := dataSource.Table.Meta().Columns
			if len(cols) > 0 {
				col := cols[0]
				resultColumnInfo = col
				resultColumn = &expression.Column{
					RetType:  col.FieldType.Clone(),
					UniqueID: dataSource.SCtx().GetSessionVars().AllocPlanColumnID(),
					ID:       col.ID,
					OrigName: fmt.Sprintf("%v.%v.%v", dataSource.DBName, dataSource.TableInfo.Name, col.Name),
				}
			}
		}
	} else {
		if dataSource.HandleCols != nil {
			resultColumn = dataSource.HandleCols.GetCol(0)
			resultColumnInfo = resultColumn.ToInfo()
		} else if dataSource.Table.Meta().PKIsHandle {
			// dataSource.HandleCols = nil doesn't mean datasource doesn't have a intPk handle.
			// since datasource.HandleCols will be cleared in the first columnPruner.
			resultColumn = dataSource.UnMutableHandleCols.GetCol(0)
			resultColumnInfo = resultColumn.ToInfo()
		} else {
			resultColumn = dataSource.NewExtraHandleSchemaCol()
			resultColumnInfo = model.NewExtraHandleColInfo()
		}
	}
	return resultColumn, resultColumnInfo
}

// IsSingleScan checks whether all the needed columns and conditions can be covered by the index.
func (ds *DataSource) IsSingleScan(indexColumns []*expression.Column, idxColLens []int) bool {
	if !ds.SCtx().GetSessionVars().OptPrefixIndexSingleScan || ds.ColsRequiringFullLen == nil {
		// ds.ColsRequiringFullLen is set at (*DataSource).PruneColumns. In some cases we don't reach (*DataSource).PruneColumns
		// and ds.ColsRequiringFullLen is nil, so we fall back to ds.isIndexCoveringColumns(ds.schema.Columns, indexColumns, idxColLens).
		return ds.IsIndexCoveringColumns(ds.Schema().Columns, indexColumns, idxColLens)
	}
	if !ds.IsIndexCoveringColumns(ds.ColsRequiringFullLen, indexColumns, idxColLens) {
		return false
	}
	for _, cond := range ds.AllConds {
		if !ds.IsIndexCoveringCondition(cond, indexColumns, idxColLens) {
			return false
		}
	}
	return true
}

// IsIndexCoveringColumns checks whether all the needed columns can be covered by the index.
func (ds *DataSource) IsIndexCoveringColumns(columns, indexColumns []*expression.Column, idxColLens []int) bool {
	for _, col := range columns {

Review comment (suggestion):
Potential nil pointer issue: If a cluster table has no columns in its metadata (dataSource.Table.Meta().Columns is empty), both resultColumn and resultColumnInfo will remain nil. This could cause issues when they are appended to ds.Columns and ds.Schema() at lines 215-216 in the calling code. While cluster tables should always have columns, the code should be defensive. Consider returning an error or falling back to a safe default when len(cols) == 0.
```suggestion
				}
			} else {
				// Defensive fallback: in the unexpected case where a cluster table reports
				// no metadata columns, synthesize a handle-like column to avoid nil pointer
				// dereferences in callers that assume a non-nil result.
				resultColumn = dataSource.NewExtraHandleSchemaCol()
				resultColumnInfo = model.NewExtraHandleColInfo()
```

Code after:
		OrigName: fmt.Sprintf("%v.%v.%v", ds.DBName, ds.TableInfo.Name, model.ExtraCommitTSName),
	}
}

func preferKeyColumnFromTable(dataSource *DataSource, originColumns []*expression.Column,
	originSchemaColumns []*model.ColumnInfo) (*expression.Column, *model.ColumnInfo) {
	var resultColumnInfo *model.ColumnInfo
	var resultColumn *expression.Column
	if dataSource.Table.Type().IsClusterTable() {
		// For cluster tables, ExtraHandleID is not valid as they are memory tables.
		// Use the first column from originColumns if available, otherwise fall back
		// to the first column from table metadata.
		if len(originColumns) > 0 {
			resultColumnInfo = originSchemaColumns[0]
			resultColumn = originColumns[0]
		} else {
			cols := dataSource.Table.Meta().Columns
			if len(cols) > 0 {
				col := cols[0]
				resultColumnInfo = col
				resultColumn = &expression.Column{
					RetType:  col.FieldType.Clone(),
					UniqueID: dataSource.SCtx().GetSessionVars().AllocPlanColumnID(),
					ID:       col.ID,
					OrigName: fmt.Sprintf("%v.%v.%v", dataSource.DBName, dataSource.TableInfo.Name, col.Name),
				}
			} else {
				// All cluster tables must have at least one column in their metadata.
				// If this is ever reached, it indicates a bug in table registration.
				logutil.BgLogger().Error("cluster table has no metadata columns",
					zap.String("db", dataSource.DBName.L),
					zap.String("table", dataSource.TableInfo.Name.L))
				resultColumn = dataSource.NewExtraHandleSchemaCol()
				resultColumnInfo = model.NewExtraHandleColInfo()
			}
		}
	} else {
		if dataSource.HandleCols != nil {
			resultColumn = dataSource.HandleCols.GetCol(0)
			resultColumnInfo = resultColumn.ToInfo()
		} else if dataSource.Table.Meta().PKIsHandle {
			// dataSource.HandleCols = nil doesn't mean datasource doesn't have a intPk handle.
			// since datasource.HandleCols will be cleared in the first columnPruner.
			resultColumn = dataSource.UnMutableHandleCols.GetCol(0)
			resultColumnInfo = resultColumn.ToInfo()
		} else {
			resultColumn = dataSource.NewExtraHandleSchemaCol()
			resultColumnInfo = model.NewExtraHandleColInfo()
		}
	}
	return resultColumn, resultColumnInfo