Repository: pingcap/tidb
File: pkg/planner/core/expression_rewriter.go (Go)

Code before:
		// The stack order is: col1, col2, ... colN, against
		stackLen := len(er.ctxStack)
		if stackLen < numCols+1 {
			er.err = errors.Errorf("Unexpected stack length for MatchAgainst: %d", stackLen)
			return retNode, false
		}
		against := er.ctxStack[stackLen-1]
		cols := er.ctxStack[stackLen-numCols-1 : stackLen-1]

		args := make([]expression.Expression, 0, 1+numCols)
		args = append(args, against)
		args = append(args, cols...)

		er.ctxStackPop(numCols + 1)
		fn, err := er.newFunction(ast.FTSMysqlMatchAgainst, &v.Type, args...)
		if err != nil {
			er.err = err
			return retNode, false
		}
		sf, ok := fn.(*expression.ScalarFunction)
		if !ok {
			er.err = errors.Errorf("unexpected expression type for %s: %T", ast.FTSMysqlMatchAgainst, fn)
			return retNode, false
		}
		if v.Modifier != ast.FulltextSearchModifierBooleanMode {
			er.err = errors.Errorf("Currently TiDB only supports BOOLEAN MODE in MATCH AGAINST")
		}
		if err := expression.SetFTSMysqlMatchAgainstModifier(sf, v.Modifier); err != nil {
			er.err = err
			return retNode, false
		}
		er.ctxStackAppend(fn, types.EmptyName)
		er.planCtx.builder.ctx.SetHasFTSFunc()
	default:
		er.err = errors.Errorf("UnknownType: %T", v)
		return retNode, false
	}

	if er.err != nil {
		return retNode, false
	}
	return originInNode, true
}

// newFunctionWithInit chooses which expression.NewFunctionImpl() will be used.
func (er *expressionRewriter) newFunctionWithInit(funcName string, retType *types.FieldType, init expression.ScalarFunctionCallBack, args ...expression.Expression) (ret expression.Expression, err error) {
	if init != nil {
		ret, err = expression.NewFunctionWithInit(er.sctx, funcName, retType, init, args...)
	} else if er.disableFoldCounter > 0 {
		ret, err = expression.NewFunctionBase(er.sctx, funcName, retType, args...)
	} else if er.tryFoldCounter > 0 {

Review comment (suggestion):
The error handling logic here is problematic. When the modifier is not BOOLEAN MODE (line 1725-1727), the error is set but the function continues to execute and may call SetFTSMysqlMatchAgainstModifier with an invalid modifier. This should return immediately after setting the error to avoid executing subsequent logic with an invalid state.
```suggestion
			er.err = errors.Errorf("Currently TiDB only supports BOOLEAN MODE in MATCH AGAINST")
			return retNode, false
```

Code after:
		// The stack order is: col1, col2, ... colN, against
		stackLen := len(er.ctxStack)
		if stackLen < numCols+1 {
			er.err = errors.Errorf("Unexpected stack length for MatchAgainst: %d", stackLen)
			return retNode, false
		}
		against := er.ctxStack[stackLen-1]
		cols := er.ctxStack[stackLen-numCols-1 : stackLen-1]

		args := make([]expression.Expression, 0, 1+numCols)
		args = append(args, against)
		args = append(args, cols...)

		er.ctxStackPop(numCols + 1)
		fn, err := er.newFunction(ast.FTSMysqlMatchAgainst, &v.Type, args...)
		if err != nil {
			er.err = err
			return retNode, false
		}
		sf, ok := fn.(*expression.ScalarFunction)
		if !ok {
			er.err = errors.Errorf("unexpected expression type for %s: %T", ast.FTSMysqlMatchAgainst, fn)
			return retNode, false
		}
		if v.Modifier != ast.FulltextSearchModifierBooleanMode {
			er.err = errors.Errorf("Currently TiDB only supports BOOLEAN MODE in MATCH AGAINST")
			return retNode, false
		}
		if err := expression.SetFTSMysqlMatchAgainstModifier(sf, v.Modifier); err != nil {
			er.err = err
			return retNode, false
		}
		newF, err := expression.RewriteMySQLMatchAgainst(er.sctx, sf)
		if err != nil {
			er.err = err
			return retNode, false
		}
		er.ctxStackAppend(newF, types.EmptyName)
		er.planCtx.builder.ctx.SetHasFTSFunc()
	default:
		er.err = errors.Errorf("UnknownType: %T", v)
		return retNode, false
	}

	if er.err != nil {
		return retNode, false
	}
	return originInNode, true
}

// newFunctionWithInit chooses which expression.NewFunctionImpl() will be used.