Repository: pingcap/tidb
Pull request #55634 (2024-08-24 03:26:40)

*: move annotated attributes of AST node to resolve.Context
<!--

Thank you for contributing to TiDB!

PR Title Format:
1. pkg [, pkg2, pkg3]: what's changed
2. *: what's changed

-->

### What problem does this PR solve?
<!--

Please create an issue first to describe the problem.

There MUST be one line starting with "Issue Number:  " and
linking the relevant issues via the "close" or "ref".

For more info, check https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue.

-->

Issue Number: ref #54436

Problem Summary:

### What changed and how does it work?
move annotated attributes of AST node to a separate resolve.Context, to avoid`parser` pkg depends on semantic structs like `TableInfo`/`DBInfo`

This PR is one of the refactor PR to move `model` pkg out from parser to ddl：

- We plan to replace the un-typed arguments Args []interface{} of Job to a series of structs depends on job type, and want to add them at DDL side as they are specific to DDL and better be maintained together, but we cannot do it now as it introduced dependency cycle. Structs like Job/BackfillMeta can be moved, but TableInfo cannot as AST depends on it. I hope we can move them all together.
- Parser is a standalone go module for parsing SQL of MySQL dialect, generates AST and provides an interface to visit it. That's all that's needed if Parser is imported by other repos alone. And I guess that's one of the reasons that we make Parser independent of TiDB. TableInfo/Job/BackfillMeta and others are catalog and runtime structures specific to TiDB. They should stay in the TiDB realm.
  - AST structures are coupled with semantic catalog structures, such as ast.TableName depends on TableInfo.

### Check List

Tests <!-- At least one of them must be included. -->

- [x] Unit test
- [ ] Integration test
- [x] Manual test (add detailed scripts or steps below)

do a TPCC performance test, the first line `8.4.0-split` is this PR, the second is nightly build
![image](https://github.com/user-attachments/assets/2e0f864d-d67b-4b9a-84b1-c30618726ed9)
![image](https://github.com/user-attachments/assets/04ebabe4-7ca7-42a9-9353-7a789bb20b86)
sysbench
![image](https://github.com/user-attachments/assets/9269f26e-185d-43ba-9cb7-f7c292b89268)
![image](https://github.com/user-attachments/assets/0e461bca-94ed-4c7d-93ad-75b6df685b9d)



- [ ] No need to test
  > - [ ] I checked and no code files have been changed.
  > <!-- Or your custom  "No need to test" reasons -->

Side effects

- [ ] Performance regression: Consumes more CPU
- [ ] Performance regression: Consumes more Memory
- [ ] Breaking backward compatibility

Documentation

- [ ] Affects user behaviors
- [ ] Contains syntax changes
- [ ] Contains variable changes
- [ ] Contains experimental features
- [ ] Changes MySQL compatibility

### Release note

<!-- compatibility change, improvement, bugfix, and new feature need a release note -->

Please refer to [Release Notes Language Style Guide](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/release-notes-style-guide.html) to write a quality release note.

```release-note
None
```


ti-chi-bot[bot]: Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with `/test all`
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter) and additionally assign [hawkingrei](https://github.com/hawkingrei), [tangenta](https://github.com/tangenta), [windtalker](https://github.com/windtalker) for approval(**Please ensuring that each of them provides their approval before proceeding**). For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)**
- **[pkg/ddl/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/ddl/OWNERS)**
- **[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)**
- **[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)**
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- **[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter","hawkingrei","tangenta","windtalker"]} -->
D3Hunter: /test all

tiprow[bot]: Hi @D3Hunter. Thanks for your PR.

PRs from untrusted users cannot be marked as trusted with `/ok-to-test` in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using `/test all`.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=pingcap%2Ftidb).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308027578):

>/test all
>


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16465/display/redirect) | true | `/test unit-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pingcap/tidb/pull/55634?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) Report
Attention: Patch coverage is `39.77695%` with `162 lines` in your changes missing coverage. Please review.
> Project coverage is 72.8943%. Comparing base [(`599977b`)](https://app.codecov.io/gh/pingcap/tidb/commit/599977b1d6ab2b2f75984a01a3d714465046d375?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) to head [(`defbfb7`)](https://app.codecov.io/gh/pingcap/tidb/commit/defbfb7560b88e90dcfcbce2930541f6ae653aef?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap).
> Report is 2 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@               Coverage Diff                @@
##             master     #55634        +/-   ##
================================================
+ Coverage   72.8188%   72.8943%   +0.0755%     
================================================
  Files          1581       1582         +1     
  Lines        442751     442841        +90     
================================================
+ Hits         322406     322806       +400     
+ Misses       100462     100096       -366     
- Partials      19883      19939        +56     
```

| [Flag](https://app.codecov.io/gh/pingcap/tidb/pull/55634/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | Coverage Δ | |
|---|---|---|
| [integration](https://app.codecov.io/gh/pingcap/tidb/pull/55634/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | `14.9137% <39.7769%> (?)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Components](https://app.codecov.io/gh/pingcap/tidb/pull/55634/components?src=pr&el=components&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | Coverage Δ | |
|---|---|---|
| [dumpling](https://app.codecov.io/gh/pingcap/tidb/pull/55634/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | `52.9567% <ø> (ø)` | |
| [parser](https://app.codecov.io/gh/pingcap/tidb/pull/55634/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | `∅ <ø> (∅)` | |
| [br](https://app.codecov.io/gh/pingcap/tidb/pull/55634/components?src=pr&el=component&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pingcap) | `46.0792% <ø> (+0.1799%)` | :arrow_up: |

</details>
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16465/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15825/display/redirect) | true | `/test mysql-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16465/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15825/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev_2 | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16298/display/redirect) | true | `/test check-dev2`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16465/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15825/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev_2 | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16298/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/check_dev | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15700/display/redirect) | true | `/test check-dev`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter) and additionally assign [hawkingrei](https://github.com/hawkingrei), [windtalker](https://github.com/windtalker) for approval(**Please ensuring that each of them provides their approval before proceeding**). For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)**
- **[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)**
- **[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)**
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- **[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter","hawkingrei","windtalker"]} -->
D3Hunter: /test all
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308440303):

>/test all


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16465/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/check_dev_2 | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16298/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/check_dev | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15700/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/mysql-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15829/display/redirect) | true | `/test mysql-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev_2 | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16298/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/check_dev | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15700/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/mysql-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15829/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/unit-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16469/display/redirect) | true | `/test unit-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev_2 | defbfb7560b88e90dcfcbce2930541f6ae653aef | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16298/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/mysql-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15829/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/unit-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16469/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/check_dev | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15704/display/redirect) | true | `/test check-dev`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/mysql-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15829/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/unit-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16469/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/check_dev | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15704/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16302/display/redirect) | true | `/test check-dev2`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

D3Hunter: /test all
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308460673):

>/test all


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/mysql-test | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15829/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15704/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16302/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16470/display/redirect) | true | `/test unit-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15704/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16302/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16470/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15830/display/redirect) | true | `/test mysql-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev_2 | 250cf7a68d8f81aa9f9d5980c7c11ac5bd2af2a6 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16302/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16470/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15830/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15705/display/redirect) | true | `/test check-dev`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16470/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15830/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15705/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16303/display/redirect) | true | `/test check-dev2`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

D3Hunter: /test all
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308473153):

>/test all


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/mysql-test | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15830/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15705/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16303/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15705/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16303/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15831/display/redirect) | true | `/test mysql-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/check_dev_2 | 678756c084d22654babffed27010070f5e6558cf | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16303/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15831/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15706/display/redirect) | true | `/test check-dev`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/mysql-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_mysql_test/15831/display/redirect) | true | `/test mysql-test`
idc-jenkins-ci-tidb/check_dev | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15706/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16304/display/redirect) | true | `/test check-dev2`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

D3Hunter: /test all
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308497179):

>/test all


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/check_dev_2 | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16304/display/redirect) | true | `/test check-dev2`
idc-jenkins-ci-tidb/check_dev | 2e3627201894445212077b4ec7e16856d8a8ef66 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15707/display/redirect) | true | `/test check-dev`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: @D3Hunter: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | 82720ab26e505969aebc37925828650179140198 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16471/display/redirect) | true | `/test unit-test`
idc-jenkins-ci-tidb/check_dev | 2e3627201894445212077b4ec7e16856d8a8ef66 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check/15707/display/redirect) | true | `/test check-dev`
idc-jenkins-ci-tidb/check_dev_2 | 2e3627201894445212077b4ec7e16856d8a8ef66 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_check2/16305/display/redirect) | true | `/test check-dev2`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-linked-issue` label, please provide the linked issue number on one line in the PR body, for example: `Issue Number: close #123` or `Issue Number: ref #456`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#referring-to-an-issue) section in the development guide.</sub>

<hr>

**Notice**: To remove the `do-not-merge/needs-tests-checked` label, please finished the tests then check the finished items in description.

For example:

> Tests <!-- At least one of them must be included. -->
>
> - [x] Unit test
> - [ ] Integration test
> - [ ] Manual test (add detailed scripts or steps below)
> - [ ] No code

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#writing-tests) section in the development guide.</sub>


<!--Checker Notification Identifier-->

D3Hunter: /test all
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2308654129):

>/test all


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>


<!--Checker Notification Identifier-->

ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>


<!--Checker Notification Identifier-->

ti-chi-bot[bot]: 
[FORMAT CHECKER NOTIFICATION]

**Notice**: To remove the `do-not-merge/invalid-title` label, please follow title format, for example `pkg [, pkg2, pkg3]: what is changed` or `*: what is changed`.

<sub>:open_book: For more info, you can check the ["Contribute Code"](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/contribute-code.html#title-format) section in the development guide.</sub>


<!--Checker Notification Identifier-->

ti-chi-bot[bot]: @D3Hunter: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
idc-jenkins-ci-tidb/unit-test | cfca5c2df2d2ec921d9a56fac0c9158da6c383f5 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/ghpr_unit_test/16510/display/redirect) | true | `/test unit-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
D3Hunter: /retest
tiprow[bot]: @D3Hunter: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2309666599):

>/retest


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260186914" title="Approved">hawkingrei</a>*
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter) and additionally assign [windtalker](https://github.com/windtalker) for approval(**Please ensuring that each of them provides their approval before proceeding**). For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)~~ [hawkingrei]
- **[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)**
- **[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)**
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- ~~[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)~~ [hawkingrei]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter","windtalker"]} -->
ti-chi-bot[bot]: [LGTM Timeline notifier]
---
Timeline:

- `2024-08-26 09:04:34.845791128 +0000 UTC m=+774669.980241249`: :ballot_box_with_check: agreed by [hawkingrei](https://github.com/hawkingrei).
ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260186914" title="Approved">hawkingrei</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260566100" title="Approved">lcwangchao</a>*
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter) and additionally assign [windtalker](https://github.com/windtalker) for approval(**Please ensuring that each of them provides their approval before proceeding**). For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)~~ [hawkingrei,lcwangchao]
- **[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)**
- **[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)**
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- ~~[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)~~ [hawkingrei]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter","windtalker"]} -->
ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260186914" title="Approved">hawkingrei</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2262042535" title="Approved">lance6716</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260566100" title="Approved">lcwangchao</a>*
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter) and additionally assign [windtalker](https://github.com/windtalker) for approval(**Please ensuring that each of them provides their approval before proceeding**). For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)~~ [hawkingrei,lance6716,lcwangchao]
- ~~[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)~~ [lance6716]
- **[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)**
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- ~~[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)~~ [hawkingrei]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter","windtalker"]} -->
lcwangchao: /test pull-lightning-integration-test
tiprow[bot]: @lcwangchao: Cannot trigger testing until a trusted user reviews the PR and leaves an `/ok-to-test` message.

<details>

In response to [this](https://github.com/pingcap/tidb/pull/55634#issuecomment-2314134994):

>/test pull-lightning-integration-test


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
ti-chi-bot[bot]: @D3Hunter: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-lightning-integration-test | cfca5c2df2d2ec921d9a56fac0c9158da6c383f5 | [link](https://do.pingcap.net/jenkins/job/pingcap/job/tidb/job/pull_lightning_integration_test/1193/display/redirect) | true | `/test pull-lightning-integration-test`

[Full PR test history](https://prow.tidb.net/pr-history?org=pingcap&repo=tidb&pr=55634). [Your PR dashboard](https://prow.tidb.net/pr?query=is%3Apr%20state%3Aopen%20author%3AD3Hunter).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260186914" title="Approved">hawkingrei</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2262042535" title="Approved">lance6716</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260566100" title="Approved">lcwangchao</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2268298943" title="Approved">zanmato1984</a>*
**Once this PR has been reviewed and has the lgtm label**, please ask for approval from [d3hunter](https://github.com/d3hunter), **ensuring that each of them provides their approval before proceeding**. For more information see [the Code Review Process](https://book.prow.tidb.net/#/workflows/pr).

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

<details open>
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)~~ [hawkingrei,lance6716,lcwangchao,zanmato1984]
- ~~[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)~~ [lance6716]
- ~~[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)~~ [zanmato1984]
- **[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)**
- ~~[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)~~ [hawkingrei]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["d3hunter"]} -->
ti-chi-bot[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/pingcap/tidb/pull/55634#issuecomment-2317309433" title="Approved">D3Hunter</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260186914" title="Approved">hawkingrei</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2262042535" title="Approved">lance6716</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2260566100" title="Approved">lcwangchao</a>*, *<a href="https://github.com/pingcap/tidb/pull/55634#pullrequestreview-2268298943" title="Approved">zanmato1984</a>*

The full list of commands accepted by this bot can be found [here](https://prow.tidb.net/command-help?repo=pingcap%2Ftidb).

The pull request process is described [here](https://book.prow.tidb.net/#/workflows/pr)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/pingcap/tidb/blob/master/OWNERS)~~ [D3Hunter,hawkingrei,lance6716,lcwangchao,zanmato1984]
- ~~[pkg/executor/importer/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/executor/importer/OWNERS)~~ [D3Hunter,lance6716]
- ~~[pkg/expression/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/expression/OWNERS)~~ [zanmato1984]
- ~~[pkg/parser/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/parser/OWNERS)~~ [D3Hunter]
- ~~[pkg/planner/OWNERS](https://github.com/pingcap/tidb/blob/master/pkg/planner/OWNERS)~~ [hawkingrei]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/pkg/executor/BUILD.bazel b/pkg/executor/BUILD.bazel
index 886c068208..6a3ef72b89 100644
--- a/pkg/executor/BUILD.bazel
+++ b/pkg/executor/BUILD.bazel
@@ -155,6 +155,7 @@ go_library(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/util",
         "//pkg/planner/util/coreusage",
         "//pkg/planner/util/fixcontrol",
@@ -412,6 +413,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/property",
         "//pkg/planner/util",
         "//pkg/planner/util/coretestsdk",
diff --git a/pkg/executor/adapter.go b/pkg/executor/adapter.go
index 2a452e4c21..d2122ce609 100644
--- a/pkg/executor/adapter.go
+++ b/pkg/executor/adapter.go
@@ -50,6 +50,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/plugin"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/sessionstates"
@@ -395,7 +396,8 @@ func (a *ExecStmt) IsReadOnly(vars *variable.SessionVars) bool {
 // It returns the current information schema version that 'a' is using.
 func (a *ExecStmt) RebuildPlan(ctx context.Context) (int64, error) {
 	ret := &plannercore.PreprocessorReturn{}
-	if err := plannercore.Preprocess(ctx, a.Ctx, a.StmtNode, plannercore.InTxnRetry, plannercore.InitTxnContextProvider, plannercore.WithPreprocessorReturn(ret)); err != nil {
+	nodeW := resolve.NewNodeW(a.StmtNode)
+	if err := plannercore.Preprocess(ctx, a.Ctx, nodeW, plannercore.InTxnRetry, plannercore.InitTxnContextProvider, plannercore.WithPreprocessorReturn(ret)); err != nil {
 		return 0, err
 	}
 
@@ -417,7 +419,7 @@ func (a *ExecStmt) RebuildPlan(ctx context.Context) (int64, error) {
 	if a.Ctx.GetSessionVars().GetReplicaRead().IsClosestRead() && replicaReadScope == kv.GlobalReplicaScope {
 		logutil.BgLogger().Warn(fmt.Sprintf("tidb can't read closest replicas due to it haven't %s label", placement.DCLabelKey))
 	}
-	p, names, err := planner.Optimize(ctx, a.Ctx, a.StmtNode, a.InfoSchema)
+	p, names, err := planner.Optimize(ctx, a.Ctx, nodeW, a.InfoSchema)
 	if err != nil {
 		return 0, err
 	}
diff --git a/pkg/executor/builder.go b/pkg/executor/builder.go
index 469682d1a4..054f521185 100644
--- a/pkg/executor/builder.go
+++ b/pkg/executor/builder.go
@@ -926,6 +926,7 @@ func (b *executorBuilder) buildSimple(v *plannercore.Simple) exec.Executor {
 	e := &SimpleExec{
 		BaseExecutor:    base,
 		Statement:       v.Statement,
+		ResolveCtx:      v.ResolveCtx,
 		IsFromRemote:    v.IsFromRemote,
 		is:              b.is,
 		staleTxnStartTS: v.StaleTxnStartTS,
@@ -1201,6 +1202,7 @@ func (b *executorBuilder) buildTrace(v *plannercore.Trace) exec.Executor {
 	t := &TraceExec{
 		BaseExecutor: exec.NewBaseExecutor(b.ctx, v.Schema(), v.ID()),
 		stmtNode:     v.StmtNode,
+		resolveCtx:   v.ResolveCtx,
 		builder:      b,
 		format:       v.Format,
 
diff --git a/pkg/executor/compiler.go b/pkg/executor/compiler.go
index 8771753e78..ebc756bc3a 100644
--- a/pkg/executor/compiler.go
+++ b/pkg/executor/compiler.go
@@ -25,6 +25,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/sessiontxn/staleread"
@@ -64,10 +65,11 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS
 
 	// Do preprocess and validate.
 	ret := &plannercore.PreprocessorReturn{}
+	nodeW := resolve.NewNodeW(stmtNode)
 	err = plannercore.Preprocess(
 		ctx,
 		c.Ctx,
-		stmtNode,
+		nodeW,
 		plannercore.WithPreprocessorReturn(ret),
 		plannercore.InitTxnContextProvider,
 	)
@@ -96,7 +98,7 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS
 		}
 	}
 	// Build the final physical plan.
-	finalPlan, names, err := planner.Optimize(ctx, c.Ctx, stmtNode, is)
+	finalPlan, names, err := planner.Optimize(ctx, c.Ctx, nodeW, is)
 	if err != nil {
 		return nil, err
 	}
@@ -106,9 +108,9 @@ func (c *Compiler) Compile(ctx context.Context, stmtNode ast.StmtNode) (_ *ExecS
 	})
 
 	if preparedObj != nil {
-		CountStmtNode(preparedObj.PreparedAst.Stmt, sessVars.InRestrictedSQL, stmtCtx.ResourceGroupName)
+		CountStmtNode(preparedObj.PreparedAst.Stmt, preparedObj.ResolveCtx, sessVars.InRestrictedSQL, stmtCtx.ResourceGroupName)
 	} else {
-		CountStmtNode(stmtNode, sessVars.InRestrictedSQL, stmtCtx.ResourceGroupName)
+		CountStmtNode(stmtNode, nodeW.GetResolveContext(), sessVars.InRestrictedSQL, stmtCtx.ResourceGroupName)
 	}
 	var lowerPriority bool
 	if c.Ctx.GetSessionVars().StmtCtx.Priority == mysql.NoPriority {
@@ -185,7 +187,7 @@ func isPhysicalPlanNeedLowerPriority(p base.PhysicalPlan) bool {
 }
 
 // CountStmtNode records the number of statements with the same type.
-func CountStmtNode(stmtNode ast.StmtNode, inRestrictedSQL bool, resourceGroup string) {
+func CountStmtNode(stmtNode ast.StmtNode, resolveCtx *resolve.Context, inRestrictedSQL bool, resourceGroup string) {
 	if inRestrictedSQL {
 		return
 	}
@@ -193,7 +195,7 @@ func CountStmtNode(stmtNode ast.StmtNode, inRestrictedSQL bool, resourceGroup st
 	typeLabel := ast.GetStmtLabel(stmtNode)
 
 	if config.GetGlobalConfig().Status.RecordQPSbyDB || config.GetGlobalConfig().Status.RecordDBLabel {
-		dbLabels := getStmtDbLabel(stmtNode)
+		dbLabels := getStmtDbLabel(stmtNode, resolveCtx)
 		switch {
 		case config.GetGlobalConfig().Status.RecordQPSbyDB:
 			for dbLabel := range dbLabels {
@@ -209,7 +211,7 @@ func CountStmtNode(stmtNode ast.StmtNode, inRestrictedSQL bool, resourceGroup st
 	}
 }
 
-func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
+func getStmtDbLabel(stmtNode ast.StmtNode, resolveCtx *resolve.Context) map[string]struct{} {
 	dbLabelSet := make(map[string]struct{})
 
 	switch x := stmtNode.(type) {
@@ -231,12 +233,12 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 	case *ast.InsertStmt:
 		var dbLabels []string
 		if x.Table != nil {
-			dbLabels = getDbFromResultNode(x.Table.TableRefs)
+			dbLabels = getDbFromResultNode(x.Table.TableRefs, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
 		}
-		dbLabels = getDbFromResultNode(x.Select)
+		dbLabels = getDbFromResultNode(x.Select, resolveCtx)
 		for _, db := range dbLabels {
 			dbLabelSet[db] = struct{}{}
 		}
@@ -289,25 +291,25 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			}
 		}
 	case *ast.SelectStmt:
-		dbLabels := getDbFromResultNode(x)
+		dbLabels := getDbFromResultNode(x, resolveCtx)
 		for _, db := range dbLabels {
 			dbLabelSet[db] = struct{}{}
 		}
 	case *ast.SetOprStmt:
-		dbLabels := getDbFromResultNode(x)
+		dbLabels := getDbFromResultNode(x, resolveCtx)
 		for _, db := range dbLabels {
 			dbLabelSet[db] = struct{}{}
 		}
 	case *ast.UpdateStmt:
 		if x.TableRefs != nil {
-			dbLabels := getDbFromResultNode(x.TableRefs.TableRefs)
+			dbLabels := getDbFromResultNode(x.TableRefs.TableRefs, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
 		}
 	case *ast.DeleteStmt:
 		if x.TableRefs != nil {
-			dbLabels := getDbFromResultNode(x.TableRefs.TableRefs)
+			dbLabels := getDbFromResultNode(x.TableRefs.TableRefs, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -403,7 +405,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -424,7 +426,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -448,7 +450,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -469,7 +471,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -493,7 +495,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -515,7 +517,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 			} else {
 				resNode = nil
 			}
-			dbLabels := getDbFromResultNode(resNode)
+			dbLabels := getDbFromResultNode(resNode, resolveCtx)
 			for _, db := range dbLabels {
 				dbLabelSet[db] = struct{}{}
 			}
@@ -530,7 +532,7 @@ func getStmtDbLabel(stmtNode ast.StmtNode) map[string]struct{} {
 	return dbLabelSet
 }
 
-func getDbFromResultNode(resultNode ast.ResultSetNode) []string { // may have duplicate db name
+func getDbFromResultNode(resultNode ast.ResultSetNode, resolveCtx *resolve.Context) []string { // may have duplicate db name
 	var dbLabels []string
 
 	if resultNode == nil {
@@ -539,25 +541,26 @@ func getDbFromResultNode(resultNode ast.ResultSetNode) []string { // may have du
 
 	switch x := resultNode.(type) {
 	case *ast.TableSource:
-		return getDbFromResultNode(x.Source)
+		return getDbFromResultNode(x.Source, resolveCtx)
 	case *ast.SelectStmt:
 		if x.From != nil {
-			return getDbFromResultNode(x.From.TableRefs)
+			return getDbFromResultNode(x.From.TableRefs, resolveCtx)
 		}
 	case *ast.TableName:
-		if x.DBInfo != nil {
-			dbLabels = append(dbLabels, x.DBInfo.Name.O)
+		xW := resolveCtx.GetTableName(x)
+		if xW != nil {
+			dbLabels = append(dbLabels, xW.DBInfo.Name.O)
 		}
 	case *ast.Join:
 		if x.Left != nil {
-			dbs := getDbFromResultNode(x.Left)
+			dbs := getDbFromResultNode(x.Left, resolveCtx)
 			if dbs != nil {
 				dbLabels = append(dbLabels, dbs...)
 			}
 		}
 
 		if x.Right != nil {
-			dbs := getDbFromResultNode(x.Right)
+			dbs := getDbFromResultNode(x.Right, resolveCtx)
 			if dbs != nil {
 				dbLabels = append(dbLabels, dbs...)
 			}
diff --git a/pkg/executor/ddl.go b/pkg/executor/ddl.go
index bf935d2f77..e29a866043 100644
--- a/pkg/executor/ddl.go
+++ b/pkg/executor/ddl.go
@@ -31,6 +31,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/sessiontxn/staleread"
@@ -302,7 +303,8 @@ func (e *DDLExec) createSessionTemporaryTable(s *ast.CreateTableStmt) error {
 
 func (e *DDLExec) executeCreateView(ctx context.Context, s *ast.CreateViewStmt) error {
 	ret := &core.PreprocessorReturn{}
-	err := core.Preprocess(ctx, e.Ctx(), s.Select, core.WithPreprocessorReturn(ret))
+	nodeW := resolve.NewNodeW(s.Select)
+	err := core.Preprocess(ctx, e.Ctx(), nodeW, core.WithPreprocessorReturn(ret))
 	if err != nil {
 		return errors.Trace(err)
 	}
diff --git a/pkg/executor/foreign_key.go b/pkg/executor/foreign_key.go
index d829595333..5cfba96ff2 100644
--- a/pkg/executor/foreign_key.go
+++ b/pkg/executor/foreign_key.go
@@ -29,6 +29,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
 	"github.com/pingcap/tidb/pkg/table"
@@ -757,11 +758,12 @@ func (fkc *FKCascadeExec) buildFKCascadePlan(ctx context.Context) (base.Plan, er
 		return nil, errors.Errorf("generate foreign key cascade ast failed, %v", fkc.tp)
 	}
 	sctx := fkc.b.ctx
-	err := plannercore.Preprocess(ctx, sctx, stmtNode)
+	nodeW := resolve.NewNodeW(stmtNode)
+	err := plannercore.Preprocess(ctx, sctx, nodeW)
 	if err != nil {
 		return nil, err
 	}
-	finalPlan, err := planner.OptimizeForForeignKeyCascade(ctx, sctx.GetPlanCtx(), stmtNode, fkc.b.is)
+	finalPlan, err := planner.OptimizeForForeignKeyCascade(ctx, sctx.GetPlanCtx(), nodeW, fkc.b.is)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/executor/importer/BUILD.bazel b/pkg/executor/importer/BUILD.bazel
index bfec2f2411..95572930a4 100644
--- a/pkg/executor/importer/BUILD.bazel
+++ b/pkg/executor/importer/BUILD.bazel
@@ -131,6 +131,7 @@ go_test(
         "//pkg/parser/mysql",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/util",
         "//pkg/session",
         "//pkg/sessionctx/variable",
diff --git a/pkg/executor/show.go b/pkg/executor/show.go
index 70c1e035ec..d1aa30bdc3 100644
--- a/pkg/executor/show.go
+++ b/pkg/executor/show.go
@@ -54,6 +54,7 @@ import (
 	field_types "github.com/pingcap/tidb/pkg/parser/types"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/plugin"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/privilege/privileges"
@@ -94,7 +95,7 @@ type ShowExec struct {
 
 	Tp                ast.ShowStmtType // Databases/Tables/Columns/....
 	DBName            model.CIStr
-	Table             *ast.TableName       // Used for showing columns.
+	Table             *resolve.TableNameW  // Used for showing columns.
 	Partition         model.CIStr          // Used for showing partition
 	Column            *ast.ColumnName      // Used for `desc table column`.
 	IndexName         model.CIStr          // Used for show table regions.
diff --git a/pkg/executor/simple.go b/pkg/executor/simple.go
index 737eedf269..48108a83bd 100644
--- a/pkg/executor/simple.go
+++ b/pkg/executor/simple.go
@@ -48,6 +48,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/plugin"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/sessionctx"
@@ -83,7 +84,8 @@ const notSpecified = -1
 type SimpleExec struct {
 	exec.BaseExecutor
 
-	Statement ast.StmtNode
+	Statement  ast.StmtNode
+	ResolveCtx *resolve.Context
 	// IsFromRemote indicates whether the statement IS FROM REMOTE TiDB instance in cluster,
 	//   and executing in coprocessor.
 	//   Used for `global kill`. See https://github.com/pingcap/tidb/blob/master/docs/design/2020-06-01-global-kill.md.
@@ -2752,20 +2754,23 @@ func (e *SimpleExec) executeDropStats(ctx context.Context, s *ast.DropStatsStmt)
 	var statsIDs []int64
 	// TODO: GLOBAL option will be deprecated. Also remove this condition when the syntax is removed
 	if s.IsGlobalStats {
-		statsIDs = []int64{s.Tables[0].TableInfo.ID}
+		tnW := e.ResolveCtx.GetTableName(s.Tables[0])
+		statsIDs = []int64{tnW.TableInfo.ID}
 	} else {
 		if len(s.PartitionNames) == 0 {
 			for _, table := range s.Tables {
-				partitionStatIDs, _, err := core.GetPhysicalIDsAndPartitionNames(table.TableInfo, nil)
+				tnW := e.ResolveCtx.GetTableName(table)
+				partitionStatIDs, _, err := core.GetPhysicalIDsAndPartitionNames(tnW.TableInfo, nil)
 				if err != nil {
 					return err
 				}
 				statsIDs = append(statsIDs, partitionStatIDs...)
-				statsIDs = append(statsIDs, table.TableInfo.ID)
+				statsIDs = append(statsIDs, tnW.TableInfo.ID)
 			}
 		} else {
 			// TODO: drop stats for specific partition is deprecated. Also remove this condition when the syntax is removed
-			if statsIDs, _, err = core.GetPhysicalIDsAndPartitionNames(s.Tables[0].TableInfo, s.PartitionNames); err != nil {
+			tnW := e.ResolveCtx.GetTableName(s.Tables[0])
+			if statsIDs, _, err = core.GetPhysicalIDsAndPartitionNames(tnW.TableInfo, s.PartitionNames); err != nil {
 				return err
 			}
 		}
diff --git a/pkg/executor/trace.go b/pkg/executor/trace.go
index 97feb89bfa..78a2507bf8 100644
--- a/pkg/executor/trace.go
+++ b/pkg/executor/trace.go
@@ -38,6 +38,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/types"
 	"github.com/pingcap/tidb/pkg/util/chunk"
@@ -58,7 +59,8 @@ type TraceExec struct {
 	// exhausted being true means there is no more result.
 	exhausted bool
 	// stmtNode is the real query ast tree and it is used for building real query's plan.
-	stmtNode ast.StmtNode
+	stmtNode   ast.StmtNode
+	resolveCtx *resolve.Context
 
 	builder *executorBuilder
 	format  string
@@ -109,7 +111,8 @@ func (e *TraceExec) nextOptimizerCEPlanTrace(ctx context.Context, se sessionctx.
 		stmtCtx.EnableOptimizerCETrace = origin
 	}()
 
-	_, _, err := core.OptimizeAstNode(ctx, se, e.stmtNode, se.GetInfoSchema().(infoschema.InfoSchema))
+	nodeW := resolve.NewNodeWWithCtx(e.stmtNode, e.resolveCtx)
+	_, _, err := core.OptimizeAstNode(ctx, se, nodeW, se.GetInfoSchema().(infoschema.InfoSchema))
 	if err != nil {
 		return err
 	}
@@ -137,7 +140,8 @@ func (e *TraceExec) nextOptimizerDebugPlanTrace(ctx context.Context, se sessionc
 		stmtCtx.EnableOptimizerDebugTrace = origin
 	}()
 
-	_, _, err := core.OptimizeAstNode(ctx, se, e.stmtNode, se.GetInfoSchema().(infoschema.InfoSchema))
+	nodeW := resolve.NewNodeWWithCtx(e.stmtNode, e.resolveCtx)
+	_, _, err := core.OptimizeAstNode(ctx, se, nodeW, se.GetInfoSchema().(infoschema.InfoSchema))
 	if err != nil {
 		return err
 	}
@@ -183,7 +187,8 @@ func (e *TraceExec) nextOptimizerPlanTrace(ctx context.Context, se sessionctx.Co
 	defer func() {
 		stmtCtx.EnableOptimizeTrace = origin
 	}()
-	_, _, err = core.OptimizeAstNode(ctx, se, e.stmtNode, se.GetInfoSchema().(infoschema.InfoSchema))
+	nodeW := resolve.NewNodeWWithCtx(e.stmtNode, e.resolveCtx)
+	_, _, err = core.OptimizeAstNode(ctx, se, nodeW, se.GetInfoSchema().(infoschema.InfoSchema))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/expression/BUILD.bazel b/pkg/expression/BUILD.bazel
index 35545cd480..f79d7d2182 100644
--- a/pkg/expression/BUILD.bazel
+++ b/pkg/expression/BUILD.bazel
@@ -213,6 +213,7 @@ go_test(
         "//pkg/parser/mysql",
         "//pkg/parser/terror",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx/stmtctx",
         "//pkg/sessionctx/variable",
diff --git a/pkg/parser/ast/dml.go b/pkg/parser/ast/dml.go
index 3a124060ef..a44316f863 100644
--- a/pkg/parser/ast/dml.go
+++ b/pkg/parser/ast/dml.go
@@ -277,9 +277,6 @@ type TableName struct {
 	Schema model.CIStr
 	Name   model.CIStr
 
-	DBInfo    *model.DBInfo
-	TableInfo *model.TableInfo
-
 	IndexHints     []*IndexHint
 	PartitionNames []model.CIStr
 	TableSample    *TableSample
diff --git a/pkg/parser/ast/expressions.go b/pkg/parser/ast/expressions.go
index 75e68e9b4c..7c341ef396 100644
--- a/pkg/parser/ast/expressions.go
+++ b/pkg/parser/ast/expressions.go
@@ -577,10 +577,6 @@ type ColumnNameExpr struct {
 
 	// Name is the referenced column name.
 	Name *ColumnName
-
-	// Refer is the result field the column name refers to.
-	// The value of Refer.Expr is used as the value of the expression.
-	Refer *ResultField
 }
 
 // Restore implements Node interface.
@@ -1042,8 +1038,6 @@ type PositionExpr struct {
 	N int
 	// P is the parameterized position.
 	P ExprNode
-	// Refer is the result field the position refers to.
-	Refer *ResultField
 }
 
 // Restore implements Node interface.
diff --git a/pkg/planner/BUILD.bazel b/pkg/planner/BUILD.bazel
index 0e45cbd795..7103c9c40f 100644
--- a/pkg/planner/BUILD.bazel
+++ b/pkg/planner/BUILD.bazel
@@ -17,6 +17,7 @@ go_library(
         "//pkg/planner/context",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/util/debugtrace",
         "//pkg/privilege",
         "//pkg/sessionctx",
diff --git a/pkg/planner/cardinality/BUILD.bazel b/pkg/planner/cardinality/BUILD.bazel
index 4446861b0e..f470a3dc21 100644
--- a/pkg/planner/cardinality/BUILD.bazel
+++ b/pkg/planner/cardinality/BUILD.bazel
@@ -73,6 +73,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx",
         "//pkg/sessionctx/stmtctx",
diff --git a/pkg/planner/cascades/BUILD.bazel b/pkg/planner/cascades/BUILD.bazel
index 1c5dc15b9a..85e75e02e0 100644
--- a/pkg/planner/cascades/BUILD.bazel
+++ b/pkg/planner/cascades/BUILD.bazel
@@ -59,6 +59,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/memo",
         "//pkg/planner/pattern",
         "//pkg/planner/property",
diff --git a/pkg/planner/core/BUILD.bazel b/pkg/planner/core/BUILD.bazel
index a8f0de8239..3cbfcb266b 100644
--- a/pkg/planner/core/BUILD.bazel
+++ b/pkg/planner/core/BUILD.bazel
@@ -131,6 +131,7 @@ go_library(
         "//pkg/planner/core/operator/baseimpl",
         "//pkg/planner/core/operator/logicalop",
         "//pkg/planner/core/operator/physicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/core/rule",
         "//pkg/planner/core/rule/util",
         "//pkg/planner/funcdep",
@@ -287,6 +288,7 @@ go_test(
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
         "//pkg/planner/core/operator/physicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/core/rule",
         "//pkg/planner/property",
         "//pkg/planner/util",
diff --git a/pkg/planner/core/common_plans.go b/pkg/planner/core/common_plans.go
index dacc7fe83b..436e42974f 100644
--- a/pkg/planner/core/common_plans.go
+++ b/pkg/planner/core/common_plans.go
@@ -29,6 +29,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/physicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/planner/util/costusage"
@@ -96,7 +97,7 @@ type CheckTable struct {
 type RecoverIndex struct {
 	baseSchemaProducer
 
-	Table     *ast.TableName
+	Table     *resolve.TableNameW
 	IndexName string
 }
 
@@ -104,7 +105,7 @@ type RecoverIndex struct {
 type CleanupIndex struct {
 	baseSchemaProducer
 
-	Table     *ast.TableName
+	Table     *resolve.TableNameW
 	IndexName string
 }
 
@@ -122,7 +123,7 @@ type CheckIndexRange struct {
 type ChecksumTable struct {
 	baseSchemaProducer
 
-	Tables []*ast.TableName
+	Tables []*resolve.TableNameW
 }
 
 // CancelDDLJobs represents a cancel DDL jobs plan.
@@ -300,6 +301,8 @@ type Simple struct {
 
 	// StaleTxnStartTS is the StartTS that is used to build a staleness transaction by 'START TRANSACTION READ ONLY' statement.
 	StaleTxnStartTS uint64
+
+	ResolveCtx *resolve.Context
 }
 
 // MemoryUsage return the memory usage of Simple
@@ -577,7 +580,7 @@ type LoadData struct {
 	OnDuplicate ast.OnDuplicateKeyHandlingType
 	Path        string
 	Format      *string
-	Table       *ast.TableName
+	Table       *resolve.TableNameW
 	Charset     *string
 	Columns     []*ast.ColumnName
 	FieldsInfo  *ast.FieldsClause
@@ -602,7 +605,7 @@ type LoadDataOpt struct {
 type ImportInto struct {
 	baseSchemaProducer
 
-	Table              *ast.TableName
+	Table              *resolve.TableNameW
 	ColumnAssignments  []*ast.Assignment
 	ColumnsAndUserVars []*ast.ColumnNameOrUserVar
 	Path               string
diff --git a/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go
index 1f6922fc47..c6cf39519f 100644
--- a/pkg/planner/core/logical_plan_builder.go
+++ b/pkg/planner/core/logical_plan_builder.go
@@ -45,6 +45,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	core_metrics "github.com/pingcap/tidb/pkg/planner/core/metrics"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
@@ -2922,11 +2923,17 @@ func (g *gbyResolver) Leave(inNode ast.Node) (ast.Node, bool) {
 	return inNode, true
 }
 
-func tblInfoFromCol(from ast.ResultSetNode, name *types.FieldName) *model.TableInfo {
-	tableList := ExtractTableList(from, true)
+func (b *PlanBuilder) tblInfoFromCol(from ast.ResultSetNode, name *types.FieldName) *model.TableInfo {
+	nodeW := resolve.NewNodeWWithCtx(from, b.resolveCtx)
+	tableList := ExtractTableList(nodeW, true)
 	for _, field := range tableList {
 		if field.Name.L == name.TblName.L {
-			return field.TableInfo
+			tnW := b.resolveCtx.GetTableName(field)
+			// when the Select is inside a view, it's not pre-processed, tnW is nil.
+			if tnW != nil {
+				return tnW.TableInfo
+			}
+			return nil
 		}
 	}
 	return nil
@@ -2982,7 +2989,7 @@ func buildWhereFuncDepend(p base.LogicalPlan, where ast.ExprNode) (map[*types.Fi
 	return colDependMap, nil
 }
 
-func buildJoinFuncDepend(p base.LogicalPlan, from ast.ResultSetNode) (map[*types.FieldName]*types.FieldName, error) {
+func (b *PlanBuilder) buildJoinFuncDepend(p base.LogicalPlan, from ast.ResultSetNode) (map[*types.FieldName]*types.FieldName, error) {
 	switch x := from.(type) {
 	case *ast.Join:
 		if x.On == nil {
@@ -2998,7 +3005,7 @@ func buildJoinFuncDepend(p base.LogicalPlan, from ast.ResultSetNode) (map[*types
 			if lCol == nil || rCol == nil {
 				continue
 			}
-			lTbl := tblInfoFromCol(x.Left, lCol)
+			lTbl := b.tblInfoFromCol(x.Left, lCol)
 			if lTbl == nil {
 				lCol, rCol = rCol, lCol
 			}
@@ -3211,7 +3218,7 @@ func extractSingeValueColNamesFromWhere(p base.LogicalPlan, where ast.ExprNode,
 	}
 }
 
-func (*PlanBuilder) checkOnlyFullGroupByWithGroupClause(p base.LogicalPlan, sel *ast.SelectStmt) error {
+func (b *PlanBuilder) checkOnlyFullGroupByWithGroupClause(p base.LogicalPlan, sel *ast.SelectStmt) error {
 	gbyOrSingleValueColNames := make(map[*types.FieldName]struct{}, len(sel.Fields.Fields))
 	gbyExprs := make([]ast.ExprNode, 0, len(sel.Fields.Fields))
 	for _, byItem := range sel.GroupBy.Items {
@@ -3258,13 +3265,13 @@ func (*PlanBuilder) checkOnlyFullGroupByWithGroupClause(p base.LogicalPlan, sel
 	if err != nil {
 		return err
 	}
-	joinDepends, err := buildJoinFuncDepend(p, sel.From.TableRefs)
+	joinDepends, err := b.buildJoinFuncDepend(p, sel.From.TableRefs)
 	if err != nil {
 		return err
 	}
 	tblMap := make(map[*model.TableInfo]struct{}, len(notInGbyOrSingleValueColNames))
 	for name, errExprLoc := range notInGbyOrSingleValueColNames {
-		tblInfo := tblInfoFromCol(sel.From.TableRefs, name)
+		tblInfo := b.tblInfoFromCol(sel.From.TableRefs, name)
 		if tblInfo == nil {
 			continue
 		}
@@ -3289,7 +3296,7 @@ func (*PlanBuilder) checkOnlyFullGroupByWithGroupClause(p base.LogicalPlan, sel
 	return nil
 }
 
-func (*PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p base.LogicalPlan, sel *ast.SelectStmt) error {
+func (b *PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p base.LogicalPlan, sel *ast.SelectStmt) error {
 	resolver := colResolverForOnlyFullGroupBy{
 		firstOrderByAggColIdx: -1,
 	}
@@ -3324,7 +3331,7 @@ func (*PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p base.LogicalPlan, s
 		return err
 	}
 
-	joinDepends, err := buildJoinFuncDepend(p, sel.From.TableRefs)
+	joinDepends, err := b.buildJoinFuncDepend(p, sel.From.TableRefs)
 	if err != nil {
 		return err
 	}
@@ -3338,7 +3345,7 @@ func (*PlanBuilder) checkOnlyFullGroupByWithOutGroupClause(p base.LogicalPlan, s
 		if _, ok := singleValueColNames[fieldName]; ok {
 			continue
 		}
-		tblInfo := tblInfoFromCol(sel.From.TableRefs, fieldName)
+		tblInfo := b.tblInfoFromCol(sel.From.TableRefs, fieldName)
 		if tblInfo == nil {
 			continue
 		}
@@ -3809,10 +3816,11 @@ func (b *PlanBuilder) buildSelect(ctx context.Context, sel *ast.SelectStmt) (p b
 	if l != nil && l.LockType != ast.SelectLockNone {
 		for _, tName := range l.Tables {
 			// CTE has no *model.HintedTable, we need to skip it.
-			if tName.TableInfo == nil {
+			tNameW := b.resolveCtx.GetTableName(tName)
+			if tNameW == nil {
 				continue
 			}
-			b.ctx.GetSessionVars().StmtCtx.LockTableIDs[tName.TableInfo.ID] = struct{}{}
+			b.ctx.GetSessionVars().StmtCtx.LockTableIDs[tNameW.TableInfo.ID] = struct{}{}
 		}
 		p, err = b.buildSelectLock(p, l)
 		if err != nil {
@@ -4952,7 +4960,8 @@ func (b *PlanBuilder) BuildDataSourceFromView(ctx context.Context, dbName model.
 		b.hintProcessor = originHintProcessor
 		b.ctx.GetSessionVars().PlannerSelectBlockAsName.Store(originPlannerSelectBlockAsName)
 	}()
-	selectLogicalPlan, err := b.Build(ctx, selectNode)
+	nodeW := resolve.NewNodeWWithCtx(selectNode, b.resolveCtx)
+	selectLogicalPlan, err := b.Build(ctx, nodeW)
 	if err != nil {
 		logutil.BgLogger().Error("build plan for view failed", zap.Error(err))
 		if terror.ErrorNotEqual(err, plannererrors.ErrViewRecursive) &&
@@ -5271,7 +5280,8 @@ func (b *PlanBuilder) buildUpdate(ctx context.Context, update *ast.UpdateStmt) (
 		return nil, err
 	}
 
-	tableList := ExtractTableList(update.TableRefs.TableRefs, false)
+	nodeW := resolve.NewNodeWWithCtx(update.TableRefs.TableRefs, b.resolveCtx)
+	tableList := ExtractTableList(nodeW, false)
 	for _, t := range tableList {
 		dbName := t.Schema.L
 		if dbName == "" {
@@ -5323,7 +5333,9 @@ func (b *PlanBuilder) buildUpdate(ctx context.Context, update *ast.UpdateStmt) (
 	proj.SetChildren(p)
 	p = proj
 
-	utlr := &updatableTableListResolver{}
+	utlr := &updatableTableListResolver{
+		resolveCtx: b.resolveCtx,
+	}
 	update.Accept(utlr)
 	orderedList, np, allAssignmentsAreConstant, err := b.buildUpdateLists(ctx, utlr.updatableTableList, update.List, p)
 	if err != nil {
@@ -5441,8 +5453,8 @@ func CheckUpdateList(assignFlags []int, updt *Update, newTblID2Table map[int64]t
 
 // If tl is CTE, its HintedTable will be nil.
 // Only used in build plan from AST after preprocess.
-func isCTE(tl *ast.TableName) bool {
-	return tl.TableInfo == nil
+func isCTE(tlW *resolve.TableNameW) bool {
+	return tlW == nil
 }
 
 func (b *PlanBuilder) buildUpdateLists(ctx context.Context, tableList []*ast.TableName, list []*ast.Assignment, p base.LogicalPlan) (newList []*expression.Assignment, po base.LogicalPlan, allAssignmentsAreConstant bool, e error) {
@@ -5470,8 +5482,9 @@ func (b *PlanBuilder) buildUpdateLists(ctx context.Context, tableList []*ast.Tab
 		name := p.OutputNames()[idx]
 		foundListItem := false
 		for _, tl := range tableList {
+			tlW := b.resolveCtx.GetTableName(tl)
 			if (tl.Schema.L == "" || tl.Schema.L == name.DBName.L) && (tl.Name.L == name.TblName.L) {
-				if isCTE(tl) || tl.TableInfo.IsView() || tl.TableInfo.IsSequence() {
+				if isCTE(tlW) || tlW.TableInfo.IsView() || tlW.TableInfo.IsSequence() {
 					return nil, nil, false, plannererrors.ErrNonUpdatableTable.GenWithStackByArgs(name.TblName.O, "UPDATE")
 				}
 				foundListItem = true
@@ -5494,20 +5507,21 @@ func (b *PlanBuilder) buildUpdateLists(ctx context.Context, tableList []*ast.Tab
 	// And, fill virtualAssignments here; that's for generated columns.
 	virtualAssignments := make([]*ast.Assignment, 0)
 	for _, tn := range tableList {
-		if isCTE(tn) || tn.TableInfo.IsView() || tn.TableInfo.IsSequence() {
+		tnW := b.resolveCtx.GetTableName(tn)
+		if isCTE(tnW) || tnW.TableInfo.IsView() || tnW.TableInfo.IsSequence() {
 			continue
 		}
 
-		tableInfo := tn.TableInfo
+		tableInfo := tnW.TableInfo
 		tableVal, found := b.is.TableByID(ctx, tableInfo.ID)
 		if !found {
-			return nil, nil, false, infoschema.ErrTableNotExists.FastGenByArgs(tn.DBInfo.Name.O, tableInfo.Name.O)
+			return nil, nil, false, infoschema.ErrTableNotExists.FastGenByArgs(tnW.DBInfo.Name.O, tableInfo.Name.O)
 		}
 		for i, colInfo := range tableVal.Cols() {
 			if !colInfo.IsGenerated() {
 				continue
 			}
-			columnFullName := fmt.Sprintf("%s.%s.%s", tn.DBInfo.Name.L, tn.Name.L, colInfo.Name.L)
+			columnFullName := fmt.Sprintf("%s.%s.%s", tnW.DBInfo.Name.L, tn.Name.L, colInfo.Name.L)
 			isDefault, ok := modifyColumns[columnFullName]
 			if ok && colInfo.Hidden {
 				return nil, nil, false, plannererrors.ErrUnknownColumn.GenWithStackByArgs(colInfo.Name, clauseMsg[fieldList])
@@ -5528,10 +5542,11 @@ func (b *PlanBuilder) buildUpdateLists(ctx context.Context, tableList []*ast.Tab
 	newList = make([]*expression.Assignment, 0, p.Schema().Len())
 	tblDbMap := make(map[string]string, len(tableList))
 	for _, tbl := range tableList {
-		if isCTE(tbl) {
+		tblW := b.resolveCtx.GetTableName(tbl)
+		if isCTE(tblW) {
 			continue
 		}
-		tblDbMap[tbl.Name.L] = tbl.DBInfo.Name.L
+		tblDbMap[tbl.Name.L] = tblW.DBInfo.Name.L
 	}
 
 	allAssignments := append(list, virtualAssignments...)
@@ -5730,6 +5745,7 @@ func (b *PlanBuilder) buildDelete(ctx context.Context, ds *ast.DeleteStmt) (base
 	}.Init(b.ctx)
 
 	del.names = p.OutputNames()
+	localResolveCtx := resolve.NewContext()
 	// Collect visitInfo.
 	if ds.Tables != nil {
 		// Delete a, b from a, b, c, d... add a and b.
@@ -5760,30 +5776,37 @@ func (b *PlanBuilder) buildDelete(ctx context.Context, ds *ast.DeleteStmt) (base
 				return nil, plannererrors.ErrNonUpdatableTable.GenWithStackByArgs(tn.Name.O, "DELETE")
 			}
 			tb := tbInfoList[name]
-			tn.DBInfo = tb.DBInfo
-			tn.TableInfo = tb.TableInfo
-			if tn.TableInfo.IsView() {
+			tnW := b.resolveCtx.GetTableName(tb)
+			localResolveCtx.AddTableName(&resolve.TableNameW{
+				TableName: tn,
+				TableInfo: tnW.TableInfo,
+				DBInfo:    tnW.DBInfo,
+			})
+			tableInfo := tnW.TableInfo
+			if tableInfo.IsView() {
 				return nil, errors.Errorf("delete view %s is not supported now", tn.Name.O)
 			}
-			if tn.TableInfo.IsSequence() {
+			if tableInfo.IsSequence() {
 				return nil, errors.Errorf("delete sequence %s is not supported now", tn.Name.O)
 			}
 			if sessionVars.User != nil {
 				authErr = plannererrors.ErrTableaccessDenied.FastGenByArgs("DELETE", sessionVars.User.AuthUsername, sessionVars.User.AuthHostname, tb.Name.L)
 			}
-			b.visitInfo = appendVisitInfo(b.visitInfo, mysql.DeletePriv, tb.DBInfo.Name.L, tb.Name.L, "", authErr)
+			b.visitInfo = appendVisitInfo(b.visitInfo, mysql.DeletePriv, tnW.DBInfo.Name.L, tb.Name.L, "", authErr)
 		}
 	} else {
 		// Delete from a, b, c, d.
-		tableList := ExtractTableList(ds.TableRefs.TableRefs, false)
+		nodeW := resolve.NewNodeWWithCtx(ds.TableRefs.TableRefs, b.resolveCtx)
+		tableList := ExtractTableList(nodeW, false)
 		for _, v := range tableList {
-			if isCTE(v) {
+			tblW := b.resolveCtx.GetTableName(v)
+			if isCTE(tblW) {
 				return nil, plannererrors.ErrNonUpdatableTable.GenWithStackByArgs(v.Name.O, "DELETE")
 			}
-			if v.TableInfo.IsView() {
+			if tblW.TableInfo.IsView() {
 				return nil, errors.Errorf("delete view %s is not supported now", v.Name.O)
 			}
-			if v.TableInfo.IsSequence() {
+			if tblW.TableInfo.IsSequence() {
 				return nil, errors.Errorf("delete sequence %s is not supported now", v.Name.O)
 			}
 			dbName := v.Schema.L
@@ -5806,9 +5829,10 @@ func (b *PlanBuilder) buildDelete(ctx context.Context, ds *ast.DeleteStmt) (base
 		// Table ID may not be unique for deleting multiple tables, for statements like
 		// `delete from t as t1, t as t2`, the same table has two alias, we have to identify a table
 		// by its alias instead of ID.
-		tblID2TableName := make(map[int64][]*ast.TableName, len(ds.Tables.Tables))
+		tblID2TableName := make(map[int64][]*resolve.TableNameW, len(ds.Tables.Tables))
 		for _, tn := range ds.Tables.Tables {
-			tblID2TableName[tn.TableInfo.ID] = append(tblID2TableName[tn.TableInfo.ID], tn)
+			tnW := localResolveCtx.GetTableName(tn)
+			tblID2TableName[tnW.TableInfo.ID] = append(tblID2TableName[tnW.TableInfo.ID], tnW)
 		}
 		tblID2Handle = del.cleanTblID2HandleMap(tblID2TableName, tblID2Handle, del.names)
 	}
@@ -5845,7 +5869,7 @@ func resolveIndicesForTblID2Handle(tblID2Handle map[int64][]util.HandleCols, sch
 }
 
 func (p *Delete) cleanTblID2HandleMap(
-	tablesToDelete map[int64][]*ast.TableName,
+	tablesToDelete map[int64][]*resolve.TableNameW,
 	tblID2Handle map[int64][]util.HandleCols,
 	outputNames []*types.FieldName,
 ) map[int64][]util.HandleCols {
@@ -5878,7 +5902,7 @@ func (p *Delete) cleanTblID2HandleMap(
 }
 
 // matchingDeletingTable checks whether this column is from the table which is in the deleting list.
-func (*Delete) matchingDeletingTable(names []*ast.TableName, name *types.FieldName) bool {
+func (*Delete) matchingDeletingTable(names []*resolve.TableNameW, name *types.FieldName) bool {
 	for _, n := range names {
 		if (name.DBName.L == "" || name.DBName.L == n.DBInfo.Name.L) && name.TblName.L == n.Name.L {
 			return true
@@ -6606,6 +6630,7 @@ func buildWindowSpecs(specs []ast.WindowSpec) (map[string]*ast.WindowSpec, error
 
 type updatableTableListResolver struct {
 	updatableTableList []*ast.TableName
+	resolveCtx         *resolve.Context
 }
 
 func (*updatableTableListResolver) Enter(inNode ast.Node) (ast.Node, bool) {
@@ -6624,6 +6649,13 @@ func (u *updatableTableListResolver) Leave(inNode ast.Node) (ast.Node, bool) {
 				newTableName.Name = v.AsName
 				newTableName.Schema = model.NewCIStr("")
 				u.updatableTableList = append(u.updatableTableList, &newTableName)
+				if tnW := u.resolveCtx.GetTableName(s); tnW != nil {
+					u.resolveCtx.AddTableName(&resolve.TableNameW{
+						TableName: &newTableName,
+						DBInfo:    tnW.DBInfo,
+						TableInfo: tnW.TableInfo,
+					})
+				}
 			} else {
 				u.updatableTableList = append(u.updatableTableList, s)
 			}
@@ -6635,15 +6667,16 @@ func (u *updatableTableListResolver) Leave(inNode ast.Node) (ast.Node, bool) {
 // ExtractTableList is a wrapper for tableListExtractor and removes duplicate TableName
 // If asName is true, extract AsName prior to OrigName.
 // Privilege check should use OrigName, while expression may use AsName.
-func ExtractTableList(node ast.Node, asName bool) []*ast.TableName {
-	if node == nil {
+func ExtractTableList(node *resolve.NodeW, asName bool) []*ast.TableName {
+	if node.Node == nil {
 		return []*ast.TableName{}
 	}
 	e := &tableListExtractor{
 		asName:     asName,
 		tableNames: []*ast.TableName{},
+		resolveCtx: node.GetResolveContext(),
 	}
-	node.Accept(e)
+	node.Node.Accept(e)
 	tableNames := e.tableNames
 	m := make(map[string]map[string]*ast.TableName) // k1: schemaName, k2: tableName, v: ast.TableName
 	for _, x := range tableNames {
@@ -6669,16 +6702,18 @@ func ExtractTableList(node ast.Node, asName bool) []*ast.TableName {
 type tableListExtractor struct {
 	asName     bool
 	tableNames []*ast.TableName
+	resolveCtx *resolve.Context
 }
 
 func (e *tableListExtractor) Enter(n ast.Node) (_ ast.Node, skipChildren bool) {
-	innerExtract := func(inner ast.Node) []*ast.TableName {
+	innerExtract := func(inner ast.Node, resolveCtx *resolve.Context) []*ast.TableName {
 		if inner == nil {
 			return nil
 		}
 		innerExtractor := &tableListExtractor{
 			asName:     e.asName,
 			tableNames: []*ast.TableName{},
+			resolveCtx: resolveCtx,
 		}
 		inner.Accept(innerExtractor)
 		return innerExtractor.tableNames
@@ -6694,12 +6729,19 @@ func (e *tableListExtractor) Enter(n ast.Node) (_ ast.Node, skipChildren bool) {
 				newTableName.Name = x.AsName
 				newTableName.Schema = model.NewCIStr("")
 				e.tableNames = append(e.tableNames, &newTableName)
+				if tnW := e.resolveCtx.GetTableName(s); tnW != nil {
+					e.resolveCtx.AddTableName(&resolve.TableNameW{
+						TableName: &newTableName,
+						DBInfo:    tnW.DBInfo,
+						TableInfo: tnW.TableInfo,
+					})
+				}
 			} else {
 				e.tableNames = append(e.tableNames, s)
 			}
 		} else if s, ok := x.Source.(*ast.SelectStmt); ok {
 			if s.From != nil {
-				innerList := innerExtract(s.From.TableRefs)
+				innerList := innerExtract(s.From.TableRefs, e.resolveCtx)
 				if len(innerList) > 0 {
 					innerTableName := innerList[0]
 					if x.AsName.L != "" && e.asName {
@@ -6707,7 +6749,15 @@ func (e *tableListExtractor) Enter(n ast.Node) (_ ast.Node, skipChildren bool) {
 						newTableName.Name = x.AsName
 						newTableName.Schema = model.NewCIStr("")
 						innerTableName = &newTableName
+						if tnW := e.resolveCtx.GetTableName(innerList[0]); tnW != nil {
+							e.resolveCtx.AddTableName(&resolve.TableNameW{
+								TableName: &newTableName,
+								DBInfo:    tnW.DBInfo,
+								TableInfo: tnW.TableInfo,
+							})
+						}
 					}
+					// why the first inner table is used to represent the table source???
 					e.tableNames = append(e.tableNames, innerTableName)
 				}
 			}
@@ -6767,7 +6817,7 @@ func (e *tableListExtractor) Enter(n ast.Node) (_ ast.Node, skipChildren bool) {
 		e.tableNames = append(e.tableNames, &ast.TableName{Schema: model.NewCIStr(x.DBName)})
 	case *ast.ExecuteStmt:
 		if v, ok := x.PrepStmt.(*PlanCacheStmt); ok {
-			e.tableNames = append(e.tableNames, innerExtract(v.PreparedAst.Stmt)...)
+			e.tableNames = append(e.tableNames, innerExtract(v.PreparedAst.Stmt, v.ResolveCtx)...)
 		}
 	}
 	return n, false
diff --git a/pkg/planner/core/operator/logicalop/BUILD.bazel b/pkg/planner/core/operator/logicalop/BUILD.bazel
index 78296e4973..29089a6e6e 100644
--- a/pkg/planner/core/operator/logicalop/BUILD.bazel
+++ b/pkg/planner/core/operator/logicalop/BUILD.bazel
@@ -45,6 +45,7 @@ go_library(
         "//pkg/planner/core/constraint",
         "//pkg/planner/core/cost",
         "//pkg/planner/core/operator/baseimpl",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/core/rule/util",
         "//pkg/planner/funcdep",
         "//pkg/planner/property",
diff --git a/pkg/planner/core/operator/logicalop/logical_show.go b/pkg/planner/core/operator/logicalop/logical_show.go
index 33df6e1930..bf400ee91a 100644
--- a/pkg/planner/core/operator/logicalop/logical_show.go
+++ b/pkg/planner/core/operator/logicalop/logical_show.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/auth"
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util/optimizetrace"
 	"github.com/pingcap/tidb/pkg/planner/util/utilfuncp"
@@ -41,9 +42,9 @@ type LogicalShow struct {
 type ShowContents struct {
 	Tp                ast.ShowStmtType // Databases/Tables/Columns/....
 	DBName            string
-	Table             *ast.TableName  // Used for showing columns.
-	Partition         model.CIStr     // Use for showing partition
-	Column            *ast.ColumnName // Used for `desc table column`.
+	Table             *resolve.TableNameW // Used for showing columns.
+	Partition         model.CIStr         // Use for showing partition
+	Column            *ast.ColumnName     // Used for `desc table column`.
 	IndexName         model.CIStr
 	ResourceGroupName string               // Used for showing resource group
 	Flag              int                  // Some flag parsed from sql, such as FULL.
diff --git a/pkg/planner/core/optimizer.go b/pkg/planner/core/optimizer.go
index 83763ebb6f..7fbab273e5 100644
--- a/pkg/planner/core/optimizer.go
+++ b/pkg/planner/core/optimizer.go
@@ -40,6 +40,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/physicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util/debugtrace"
@@ -60,7 +61,7 @@ import (
 )
 
 // OptimizeAstNode optimizes the query to a physical plan directly.
-var OptimizeAstNode func(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (base.Plan, types.NameSlice, error)
+var OptimizeAstNode func(ctx context.Context, sctx sessionctx.Context, node *resolve.NodeW, is infoschema.InfoSchema) (base.Plan, types.NameSlice, error)
 
 // AllowCartesianProduct means whether tidb allows cartesian join without equal conditions.
 var AllowCartesianProduct = atomic.NewBool(true)
@@ -106,7 +107,7 @@ var optRuleList = []base.LogicalOptRule{
 var optInteractionRuleList = map[base.LogicalOptRule]base.LogicalOptRule{}
 
 // BuildLogicalPlanForTest builds a logical plan for testing purpose from ast.Node.
-func BuildLogicalPlanForTest(ctx context.Context, sctx sessionctx.Context, node ast.Node, infoSchema infoschema.InfoSchema) (base.Plan, error) {
+func BuildLogicalPlanForTest(ctx context.Context, sctx sessionctx.Context, node *resolve.NodeW, infoSchema infoschema.InfoSchema) (base.Plan, error) {
 	sctx.GetSessionVars().PlanID.Store(0)
 	sctx.GetSessionVars().PlanColumnID.Store(0)
 	builder, _ := NewPlanBuilder().Init(sctx.GetPlanCtx(), infoSchema, utilhint.NewQBHintHandler(nil))
diff --git a/pkg/planner/core/pb_to_plan.go b/pkg/planner/core/pb_to_plan.go
index f77db446cb..94002d9228 100644
--- a/pkg/planner/core/pb_to_plan.go
+++ b/pkg/planner/core/pb_to_plan.go
@@ -26,6 +26,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/types"
@@ -267,7 +268,7 @@ func (*PBPlanBuilder) pbToKill(e *tipb.Executor) (base.PhysicalPlan, error) {
 		ConnectionID: e.Kill.ConnID,
 		Query:        e.Kill.Query,
 	}
-	simple := Simple{Statement: node, IsFromRemote: true}
+	simple := Simple{Statement: node, IsFromRemote: true, ResolveCtx: resolve.NewContext()}
 	return &PhysicalSimpleWrapper{Inner: simple}, nil
 }
 
diff --git a/pkg/planner/core/plan_cache.go b/pkg/planner/core/plan_cache.go
index d988f48913..3dfbf94cb4 100644
--- a/pkg/planner/core/plan_cache.go
+++ b/pkg/planner/core/plan_cache.go
@@ -25,6 +25,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	core_metrics "github.com/pingcap/tidb/pkg/planner/core/metrics"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/util/debugtrace"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/sessionctx"
@@ -148,10 +149,12 @@ func planCachePreprocess(ctx context.Context, sctx sessionctx.Context, isNonPrep
 		// We should reset the tableRefs in the prepared update statements, otherwise, the ast nodes still hold the old
 		// tableRefs columnInfo which will cause chaos in logic of trying point get plan. (should ban non-public column)
 		ret := &PreprocessorReturn{InfoSchema: is}
-		err := Preprocess(ctx, sctx, stmtAst.Stmt, InPrepare, WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmtAst.Stmt)
+		err := Preprocess(ctx, sctx, nodeW, InPrepare, WithPreprocessorReturn(ret))
 		if err != nil {
 			return plannererrors.ErrSchemaChanged.GenWithStack("Schema change caused error: %s", err.Error())
 		}
+		stmt.ResolveCtx = nodeW.GetResolveContext()
 		stmt.SchemaVersion = is.SchemaMetaVersion()
 	}
 
@@ -290,7 +293,8 @@ func generateNewPlan(ctx context.Context, sctx sessionctx.Context, isNonPrepared
 
 	core_metrics.GetPlanCacheMissCounter(isNonPrepared).Inc()
 	sctx.GetSessionVars().StmtCtx.InPreparedPlanBuilding = true
-	p, names, err := OptimizeAstNode(ctx, sctx, stmtAst.Stmt, is)
+	nodeW := resolve.NewNodeWWithCtx(stmtAst.Stmt, stmt.ResolveCtx)
+	p, names, err := OptimizeAstNode(ctx, sctx, nodeW, is)
 	sctx.GetSessionVars().StmtCtx.InPreparedPlanBuilding = false
 	if err != nil {
 		return nil, nil, err
diff --git a/pkg/planner/core/plan_cache_utils.go b/pkg/planner/core/plan_cache_utils.go
index 1048821f6d..0ff67672e2 100644
--- a/pkg/planner/core/plan_cache_utils.go
+++ b/pkg/planner/core/plan_cache_utils.go
@@ -35,6 +35,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/planner/util/fixcontrol"
@@ -109,7 +110,8 @@ func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context,
 	}
 
 	ret := &PreprocessorReturn{InfoSchema: is} // is can be nil, and
-	err := Preprocess(ctx, sctx, paramStmt, InPrepare, WithPreprocessorReturn(ret))
+	nodeW := resolve.NewNodeW(paramStmt)
+	err := Preprocess(ctx, sctx, nodeW, InPrepare, WithPreprocessorReturn(ret))
 	if err != nil {
 		return nil, nil, 0, err
 	}
@@ -170,7 +172,7 @@ func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context,
 
 	var p base.Plan
 	destBuilder, _ := NewPlanBuilder().Init(sctx.GetPlanCtx(), ret.InfoSchema, hint.NewQBHintHandler(nil))
-	p, err = destBuilder.Build(ctx, paramStmt)
+	p, err = destBuilder.Build(ctx, nodeW)
 	if err != nil {
 		return nil, nil, 0, err
 	}
@@ -204,6 +206,7 @@ func GeneratePlanCacheStmtWithAST(ctx context.Context, sctx sessionctx.Context,
 
 	preparedObj := &PlanCacheStmt{
 		PreparedAst:         prepared,
+		ResolveCtx:          nodeW.GetResolveContext(),
 		StmtDB:              vars.CurrentDB,
 		StmtText:            paramSQL,
 		VisitInfos:          destBuilder.GetVisitInfo(),
@@ -528,6 +531,7 @@ type PointGetExecutorCache struct {
 // PlanCacheStmt store prepared ast from PrepareExec and other related fields
 type PlanCacheStmt struct {
 	PreparedAst *ast.Prepared
+	ResolveCtx  *resolve.Context
 	StmtDB      string // which DB the statement will be processed over
 	VisitInfos  []visitInfo
 	Params      []ast.ParamMarkerExpr
diff --git a/pkg/planner/core/planbuilder.go b/pkg/planner/core/planbuilder.go
index 8a519f1a5d..0a61305eba 100644
--- a/pkg/planner/core/planbuilder.go
+++ b/pkg/planner/core/planbuilder.go
@@ -42,6 +42,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
@@ -291,6 +292,8 @@ type PlanBuilder struct {
 
 	// allowBuildCastArray indicates whether allow cast(... as ... array).
 	allowBuildCastArray bool
+	// resolveCtx is set when calling Build, it's only effective in the current Build call.
+	resolveCtx *resolve.Context
 }
 
 type handleColHelper struct {
@@ -490,9 +493,12 @@ func (b *PlanBuilder) ResetForReuse() *PlanBuilder {
 }
 
 // Build builds the ast node to a Plan.
-func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (base.Plan, error) {
+func (b *PlanBuilder) Build(ctx context.Context, node *resolve.NodeW) (base.Plan, error) {
+	// Build might be called recursively, right now they all share the same resolve
+	// context, so it's ok to override it.
+	b.resolveCtx = node.GetResolveContext()
 	b.optFlag |= rule.FlagPruneColumns
-	switch x := node.(type) {
+	switch x := node.Node.(type) {
 	case *ast.AdminStmt:
 		return b.buildAdmin(ctx, x)
 	case *ast.DeallocateStmt:
@@ -550,7 +556,7 @@ func (b *PlanBuilder) Build(ctx context.Context, node ast.Node) (base.Plan, erro
 		*ast.GrantRoleStmt, *ast.RevokeRoleStmt, *ast.SetRoleStmt, *ast.SetDefaultRoleStmt, *ast.ShutdownStmt,
 		*ast.RenameUserStmt, *ast.NonTransactionalDMLStmt, *ast.SetSessionStatesStmt, *ast.SetResourceGroupStmt,
 		*ast.ImportIntoActionStmt, *ast.CalibrateResourceStmt, *ast.AddQueryWatchStmt, *ast.DropQueryWatchStmt:
-		return b.buildSimple(ctx, node.(ast.StmtNode))
+		return b.buildSimple(ctx, node.Node.(ast.StmtNode))
 	case ast.DDLNode:
 		return b.buildDDL(ctx, x)
 	case *ast.CreateBindingStmt:
@@ -1482,15 +1488,21 @@ func (b *PlanBuilder) buildAdmin(ctx context.Context, as *ast.AdminStmt) (base.P
 			return ret, err
 		}
 	case ast.AdminRecoverIndex:
-		p := &RecoverIndex{Table: as.Tables[0], IndexName: as.Index}
+		tnW := b.resolveCtx.GetTableName(as.Tables[0])
+		p := &RecoverIndex{Table: tnW, IndexName: as.Index}
 		p.setSchemaAndNames(buildRecoverIndexFields())
 		ret = p
 	case ast.AdminCleanupIndex:
-		p := &CleanupIndex{Table: as.Tables[0], IndexName: as.Index}
+		tnW := b.resolveCtx.GetTableName(as.Tables[0])
+		p := &CleanupIndex{Table: tnW, IndexName: as.Index}
 		p.setSchemaAndNames(buildCleanupIndexFields())
 		ret = p
 	case ast.AdminChecksumTable:
-		p := &ChecksumTable{Tables: as.Tables}
+		tnWs := make([]*resolve.TableNameW, 0, len(as.Tables))
+		for _, tn := range as.Tables {
+			tnWs = append(tnWs, b.resolveCtx.GetTableName(tn))
+		}
+		p := &ChecksumTable{Tables: tnWs}
 		p.setSchemaAndNames(buildChecksumTableSchema())
 		ret = p
 	case ast.AdminShowNextRowID:
@@ -1569,11 +1581,11 @@ func (b *PlanBuilder) buildAdmin(ctx context.Context, as *ast.AdminStmt) (base.P
 	case ast.AdminReloadBindings:
 		return &SQLBindPlan{SQLBindOp: OpReloadBindings}, nil
 	case ast.AdminReloadStatistics:
-		return &Simple{Statement: as}, nil
+		return &Simple{Statement: as, ResolveCtx: b.resolveCtx}, nil
 	case ast.AdminFlushPlanCache:
-		return &Simple{Statement: as}, nil
+		return &Simple{Statement: as, ResolveCtx: b.resolveCtx}, nil
 	case ast.AdminSetBDRRole, ast.AdminUnsetBDRRole:
-		ret = &Simple{Statement: as}
+		ret = &Simple{Statement: as, ResolveCtx: b.resolveCtx}
 	case ast.AdminShowBDRRole:
 		p := &AdminShowBDRRole{}
 		p.setSchemaAndNames(buildAdminShowBDRRoleFields())
@@ -1819,10 +1831,11 @@ func (b *PlanBuilder) buildPhysicalIndexLookUpReaders(ctx context.Context, dbNam
 
 func (b *PlanBuilder) buildAdminCheckTable(ctx context.Context, as *ast.AdminStmt) (*CheckTable, error) {
 	tblName := as.Tables[0]
-	tableInfo := as.Tables[0].TableInfo
+	tnW := b.resolveCtx.GetTableName(tblName)
+	tableInfo := tnW.TableInfo
 	tbl, ok := b.is.TableByID(ctx, tableInfo.ID)
 	if !ok {
-		return nil, infoschema.ErrTableNotExists.FastGenByArgs(tblName.DBInfo.Name.O, tableInfo.Name.O)
+		return nil, infoschema.ErrTableNotExists.FastGenByArgs(tnW.DBInfo.Name.O, tableInfo.Name.O)
 	}
 	p := &CheckTable{
 		DBName: tblName.Schema.O,
@@ -1868,8 +1881,9 @@ func (b *PlanBuilder) buildCheckIndexSchema(tn *ast.TableName, indexName string)
 	schema := expression.NewSchema()
 	var names types.NameSlice
 	indexName = strings.ToLower(indexName)
-	indicesInfo := tn.TableInfo.Indices
-	cols := tn.TableInfo.Cols()
+	tnW := b.resolveCtx.GetTableName(tn)
+	indicesInfo := tnW.TableInfo.Indices
+	cols := tnW.TableInfo.Cols()
 	for _, idxInfo := range indicesInfo {
 		if idxInfo.Name.L != indexName {
 			continue
@@ -1904,8 +1918,9 @@ func (b *PlanBuilder) buildCheckIndexSchema(tn *ast.TableName, indexName string)
 }
 
 // getColsInfo returns the info of index columns, normal columns and primary key.
-func getColsInfo(tn *ast.TableName) (indicesInfo []*model.IndexInfo, colsInfo []*model.ColumnInfo) {
-	tbl := tn.TableInfo
+func (b *PlanBuilder) getColsInfo(tn *ast.TableName) (indicesInfo []*model.IndexInfo, colsInfo []*model.ColumnInfo) {
+	tnW := b.resolveCtx.GetTableName(tn)
+	tbl := tnW.TableInfo
 	for _, col := range tbl.Columns {
 		// The virtual column will not store any data in TiKV, so it should be ignored when collect statistics
 		if col.IsVirtualGenerated() {
@@ -1916,7 +1931,7 @@ func getColsInfo(tn *ast.TableName) (indicesInfo []*model.IndexInfo, colsInfo []
 		}
 		colsInfo = append(colsInfo, col)
 	}
-	for _, idx := range tn.TableInfo.Indices {
+	for _, idx := range tnW.TableInfo.Indices {
 		if idx.State == model.StatePublic {
 			indicesInfo = append(indicesInfo, idx)
 		}
@@ -2020,7 +2035,7 @@ type calcOnceMap struct {
 }
 
 // getMustAnalyzedColumns puts the columns whose statistics must be collected into `cols` if `cols` has not been calculated.
-func (b *PlanBuilder) getMustAnalyzedColumns(tbl *ast.TableName, cols *calcOnceMap) (map[int64]struct{}, error) {
+func (b *PlanBuilder) getMustAnalyzedColumns(tbl *resolve.TableNameW, cols *calcOnceMap) (map[int64]struct{}, error) {
 	if cols.calculated {
 		return cols.data, nil
 	}
@@ -2081,7 +2096,7 @@ func (b *PlanBuilder) getMustAnalyzedColumns(tbl *ast.TableName, cols *calcOnceM
 }
 
 // getPredicateColumns gets the columns used in predicates.
-func (b *PlanBuilder) getPredicateColumns(tbl *ast.TableName, cols *calcOnceMap) (map[int64]struct{}, error) {
+func (b *PlanBuilder) getPredicateColumns(tbl *resolve.TableNameW, cols *calcOnceMap) (map[int64]struct{}, error) {
 	// Already calculated in the previous call.
 	if cols.calculated {
 		return cols.data, nil
@@ -2111,7 +2126,7 @@ func (b *PlanBuilder) getPredicateColumns(tbl *ast.TableName, cols *calcOnceMap)
 	return cols.data, nil
 }
 
-func getAnalyzeColumnList(specifiedColumns []model.CIStr, tbl *ast.TableName) ([]*model.ColumnInfo, error) {
+func getAnalyzeColumnList(specifiedColumns []model.CIStr, tbl *resolve.TableNameW) ([]*model.ColumnInfo, error) {
 	colList := make([]*model.ColumnInfo, 0, len(specifiedColumns))
 	for _, colName := range specifiedColumns {
 		colInfo := model.FindColumnInfo(tbl.TableInfo.Columns, colName.L)
@@ -2127,7 +2142,7 @@ func getAnalyzeColumnList(specifiedColumns []model.CIStr, tbl *ast.TableName) ([
 // The first return value is the columns which need to be analyzed and the second return value is the columns which need to
 // be record in mysql.analyze_options(only for the case of analyze table t columns c1, .., cn).
 func (b *PlanBuilder) getFullAnalyzeColumnsInfo(
-	tbl *ast.TableName,
+	tbl *resolve.TableNameW,
 	columnChoice model.ColumnChoice,
 	specifiedCols []*model.ColumnInfo,
 	predicateCols, mustAnalyzedCols *calcOnceMap,
@@ -2199,7 +2214,7 @@ func (b *PlanBuilder) getFullAnalyzeColumnsInfo(
 }
 
 func (b *PlanBuilder) getColumnsBasedOnPredicateColumns(
-	tbl *ast.TableName,
+	tbl *resolve.TableNameW,
 	predicateCols, mustAnalyzedCols *calcOnceMap,
 	rewriteAllStatsNeeded bool,
 ) ([]*model.ColumnInfo, error) {
@@ -2320,7 +2335,7 @@ func getModifiedIndexesInfoForAnalyze(
 }
 
 // filterSkipColumnTypes filters out columns whose types are in the skipTypes list.
-func (b *PlanBuilder) filterSkipColumnTypes(origin []*model.ColumnInfo, tbl *ast.TableName, mustAnalyzedCols *calcOnceMap) (result []*model.ColumnInfo) {
+func (b *PlanBuilder) filterSkipColumnTypes(origin []*model.ColumnInfo, tbl *resolve.TableNameW, mustAnalyzedCols *calcOnceMap) (result []*model.ColumnInfo) {
 	// If the session is in restricted SQL mode, it uses @@global.tidb_analyze_skip_column_types to get the skipTypes list.
 	skipTypes := b.ctx.GetSessionVars().AnalyzeSkipColumnTypes
 	if b.ctx.GetSessionVars().InRestrictedSQL {
@@ -2358,7 +2373,7 @@ func (b *PlanBuilder) buildAnalyzeFullSamplingTask(
 	analyzePlan *Analyze,
 	physicalIDs []int64,
 	partitionNames []string,
-	tbl *ast.TableName,
+	tbl *resolve.TableNameW,
 	version int,
 	persistOpts bool,
 ) error {
@@ -2450,7 +2465,7 @@ func (b *PlanBuilder) buildAnalyzeFullSamplingTask(
 
 func (b *PlanBuilder) genV2AnalyzeOptions(
 	persist bool,
-	tbl *ast.TableName,
+	tbl *resolve.TableNameW,
 	isAnalyzeTable bool,
 	physicalIDs []int64,
 	astOpts map[ast.AnalyzeOptionType]uint64,
@@ -2637,21 +2652,22 @@ func (b *PlanBuilder) buildAnalyzeTable(as *ast.AnalyzeTableStmt, opts map[ast.A
 
 	// Construct tasks for each table.
 	for _, tbl := range as.TableNames {
-		if tbl.TableInfo.IsView() {
+		tnW := b.resolveCtx.GetTableName(tbl)
+		if tnW.TableInfo.IsView() {
 			return nil, errors.Errorf("analyze view %s is not supported now", tbl.Name.O)
 		}
-		if tbl.TableInfo.IsSequence() {
+		if tnW.TableInfo.IsSequence() {
 			return nil, errors.Errorf("analyze sequence %s is not supported now", tbl.Name.O)
 		}
 
-		idxInfo, colInfo := getColsInfo(tbl)
-		physicalIDs, partitionNames, err := GetPhysicalIDsAndPartitionNames(tbl.TableInfo, as.PartitionNames)
+		idxInfo, colInfo := b.getColsInfo(tbl)
+		physicalIDs, partitionNames, err := GetPhysicalIDsAndPartitionNames(tnW.TableInfo, as.PartitionNames)
 		if err != nil {
 			return nil, err
 		}
 		var commonHandleInfo *model.IndexInfo
 		if version == statistics.Version2 {
-			err = b.buildAnalyzeFullSamplingTask(as, p, physicalIDs, partitionNames, tbl, version, usePersistedOptions)
+			err = b.buildAnalyzeFullSamplingTask(as, p, physicalIDs, partitionNames, tnW, version, usePersistedOptions)
 			if err != nil {
 				return nil, err
 			}
@@ -2668,7 +2684,7 @@ func (b *PlanBuilder) buildAnalyzeTable(as *ast.AnalyzeTableStmt, opts map[ast.A
 		for _, idx := range idxInfo {
 			// For prefix common handle. We don't use analyze mixed to handle it with columns. Because the full value
 			// is read by coprocessor, the prefix index would get wrong stats in this case.
-			if idx.Primary && tbl.TableInfo.IsCommonHandle && !idx.HasPrefixIndex() {
+			if idx.Primary && tnW.TableInfo.IsCommonHandle && !idx.HasPrefixIndex() {
 				commonHandleInfo = idx
 				continue
 			}
@@ -2676,19 +2692,19 @@ func (b *PlanBuilder) buildAnalyzeTable(as *ast.AnalyzeTableStmt, opts map[ast.A
 				b.ctx.GetSessionVars().StmtCtx.AppendWarning(errors.NewNoStackErrorf("analyzing multi-valued indexes is not supported, skip %s", idx.Name.L))
 				continue
 			}
-			p.IdxTasks = append(p.IdxTasks, generateIndexTasks(idx, as, tbl.TableInfo, partitionNames, physicalIDs, version)...)
+			p.IdxTasks = append(p.IdxTasks, generateIndexTasks(idx, as, tnW.TableInfo, partitionNames, physicalIDs, version)...)
 		}
-		handleCols := BuildHandleColsForAnalyze(b.ctx, tbl.TableInfo, true, nil)
+		handleCols := BuildHandleColsForAnalyze(b.ctx, tnW.TableInfo, true, nil)
 		if len(colInfo) > 0 || handleCols != nil {
 			for i, id := range physicalIDs {
-				if id == tbl.TableInfo.ID {
+				if id == tnW.TableInfo.ID {
 					id = -1
 				}
 				info := AnalyzeInfo{
 					DBName:        tbl.Schema.O,
 					TableName:     tbl.Name.O,
 					PartitionName: partitionNames[i],
-					TableID:       statistics.AnalyzeTableID{TableID: tbl.TableInfo.ID, PartitionID: id},
+					TableID:       statistics.AnalyzeTableID{TableID: tnW.TableInfo.ID, PartitionID: id},
 					StatsVersion:  version,
 				}
 				p.ColTasks = append(p.ColTasks, AnalyzeColumnsTask{
@@ -2696,7 +2712,7 @@ func (b *PlanBuilder) buildAnalyzeTable(as *ast.AnalyzeTableStmt, opts map[ast.A
 					CommonHandleInfo: commonHandleInfo,
 					ColsInfo:         colInfo,
 					AnalyzeInfo:      info,
-					TblInfo:          tbl.TableInfo,
+					TblInfo:          tnW.TableInfo,
 				})
 			}
 		}
@@ -2711,7 +2727,8 @@ func (b *PlanBuilder) buildAnalyzeIndex(as *ast.AnalyzeTableStmt, opts map[ast.A
 	if statsHandle == nil {
 		return nil, errors.Errorf("statistics hasn't been initialized, please try again later")
 	}
-	tblInfo := as.TableNames[0].TableInfo
+	tnW := b.resolveCtx.GetTableName(as.TableNames[0])
+	tblInfo := tnW.TableInfo
 	physicalIDs, names, err := GetPhysicalIDsAndPartitionNames(tblInfo, as.PartitionNames)
 	if err != nil {
 		return nil, err
@@ -2763,7 +2780,8 @@ func (b *PlanBuilder) buildAnalyzeAllIndex(as *ast.AnalyzeTableStmt, opts map[as
 	if statsHandle == nil {
 		return nil, errors.Errorf("statistics hasn't been initialized, please try again later")
 	}
-	tblInfo := as.TableNames[0].TableInfo
+	tnW := b.resolveCtx.GetTableName(as.TableNames[0])
+	tblInfo := tnW.TableInfo
 	physicalIDs, names, err := GetPhysicalIDsAndPartitionNames(tblInfo, as.PartitionNames)
 	if err != nil {
 		return nil, err
@@ -3274,12 +3292,13 @@ func splitWhere(where ast.ExprNode) []ast.ExprNode {
 }
 
 func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (base.Plan, error) {
+	tnW := b.resolveCtx.GetTableName(show.Table)
 	p := logicalop.LogicalShow{
 		ShowContents: logicalop.ShowContents{
 			Tp:                    show.Tp,
 			CountWarningsOrErrors: show.CountWarningsOrErrors,
 			DBName:                show.DBName,
-			Table:                 show.Table,
+			Table:                 tnW,
 			Partition:             show.Partition,
 			Column:                show.Column,
 			IndexName:             show.IndexName,
@@ -3442,7 +3461,7 @@ func (b *PlanBuilder) buildShow(ctx context.Context, show *ast.ShowStmt) (base.P
 }
 
 func (b *PlanBuilder) buildSimple(ctx context.Context, node ast.StmtNode) (base.Plan, error) {
-	p := &Simple{Statement: node}
+	p := &Simple{Statement: node, ResolveCtx: b.resolveCtx}
 
 	switch raw := node.(type) {
 	case *ast.FlushStmt:
@@ -3773,7 +3792,8 @@ func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) (
 	if !ok {
 		return nil, infoschema.ErrTableNotExists.FastGenByArgs()
 	}
-	tableInfo := tn.TableInfo
+	tnW := b.resolveCtx.GetTableName(tn)
+	tableInfo := tnW.TableInfo
 	if tableInfo.IsView() {
 		err := errors.Errorf("insert into view %s is not supported now", tableInfo.Name.O)
 		if insert.IsReplace {
@@ -3829,7 +3849,7 @@ func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) (
 		authErr = plannererrors.ErrTableaccessDenied.FastGenByArgs("INSERT", user.AuthUsername, user.AuthHostname, tableInfo.Name.L)
 	}
 
-	b.visitInfo = appendVisitInfo(b.visitInfo, mysql.InsertPriv, tn.DBInfo.Name.L,
+	b.visitInfo = appendVisitInfo(b.visitInfo, mysql.InsertPriv, tnW.DBInfo.Name.L,
 		tableInfo.Name.L, "", authErr)
 
 	// `REPLACE INTO` requires both INSERT + DELETE privilege
@@ -3845,7 +3865,7 @@ func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) (
 			cmd := strings.ToUpper(mysql.Priv2Str[extraPriv])
 			authErr = plannererrors.ErrTableaccessDenied.FastGenByArgs(cmd, user.AuthUsername, user.AuthHostname, tableInfo.Name.L)
 		}
-		b.visitInfo = appendVisitInfo(b.visitInfo, extraPriv, tn.DBInfo.Name.L, tableInfo.Name.L, "", authErr)
+		b.visitInfo = appendVisitInfo(b.visitInfo, extraPriv, tnW.DBInfo.Name.L, tableInfo.Name.L, "", authErr)
 	}
 
 	mockTablePlan := logicalop.LogicalTableDual{}.Init(b.ctx, b.getSelectOffset())
@@ -3900,7 +3920,7 @@ func (b *PlanBuilder) buildInsert(ctx context.Context, insert *ast.InsertStmt) (
 	if err != nil {
 		return nil, err
 	}
-	err = insertPlan.buildOnInsertFKTriggers(b.ctx, b.is, tn.DBInfo.Name.L)
+	err = insertPlan.buildOnInsertFKTriggers(b.ctx, b.is, tnW.DBInfo.Name.L)
 	return insertPlan, err
 }
 
@@ -4172,7 +4192,8 @@ func (b *PlanBuilder) buildSelectPlanOfInsert(ctx context.Context, insert *ast.I
 			}
 		}
 	}
-	selectPlan, err := b.Build(ctx, insert.Select)
+	nodeW := resolve.NewNodeWWithCtx(insert.Select, b.resolveCtx)
+	selectPlan, err := b.Build(ctx, nodeW)
 	if err != nil {
 		return err
 	}
@@ -4251,12 +4272,13 @@ func (b *PlanBuilder) buildLoadData(ctx context.Context, ld *ast.LoadDataStmt) (
 		}
 		options = append(options, &loadDataOpt)
 	}
+	tnW := b.resolveCtx.GetTableName(ld.Table)
 	p := LoadData{
 		FileLocRef:         ld.FileLocRef,
 		OnDuplicate:        ld.OnDuplicate,
 		Path:               ld.Path,
 		Format:             ld.Format,
-		Table:              ld.Table,
+		Table:              tnW,
 		Charset:            ld.Charset,
 		Columns:            ld.Columns,
 		FieldsInfo:         ld.FieldsInfo,
@@ -4343,10 +4365,11 @@ func (b *PlanBuilder) buildImportInto(ctx context.Context, ld *ast.ImportIntoStm
 			)
 		}
 	}
+	tnW := b.resolveCtx.GetTableName(ld.Table)
 	p := ImportInto{
 		Path:               ld.Path,
 		Format:             ld.Format,
-		Table:              ld.Table,
+		Table:              tnW,
 		ColumnAssignments:  ld.ColumnAssignments,
 		ColumnsAndUserVars: ld.ColumnsAndUserVars,
 		Options:            options,
@@ -4405,7 +4428,8 @@ func (b *PlanBuilder) buildImportInto(ctx context.Context, ld *ast.ImportIntoStm
 
 	if ld.Select != nil {
 		// privilege of tables in select will be checked here
-		selectPlan, err2 := b.Build(ctx, ld.Select)
+		nodeW := resolve.NewNodeWWithCtx(ld.Select, b.resolveCtx)
+		selectPlan, err2 := b.Build(ctx, nodeW)
 		if err2 != nil {
 			return nil, err2
 		}
@@ -4478,10 +4502,11 @@ func (*PlanBuilder) buildIndexAdvise(node *ast.IndexAdviseStmt) base.Plan {
 }
 
 func (b *PlanBuilder) buildSplitRegion(node *ast.SplitRegionStmt) (base.Plan, error) {
-	if node.Table.TableInfo.TempTableType != model.TempTableNone {
+	tnW := b.resolveCtx.GetTableName(node.Table)
+	if tnW.TableInfo.TempTableType != model.TempTableNone {
 		return nil, plannererrors.ErrOptOnTemporaryTable.GenWithStackByArgs("split table")
 	}
-	if node.SplitSyntaxOpt != nil && node.SplitSyntaxOpt.HasPartition && node.Table.TableInfo.Partition == nil {
+	if node.SplitSyntaxOpt != nil && node.SplitSyntaxOpt.HasPartition && tnW.TableInfo.Partition == nil {
 		return nil, plannererrors.ErrPartitionClauseOnNonpartitioned
 	}
 	if len(node.IndexName.L) != 0 {
@@ -4491,7 +4516,8 @@ func (b *PlanBuilder) buildSplitRegion(node *ast.SplitRegionStmt) (base.Plan, er
 }
 
 func (b *PlanBuilder) buildSplitIndexRegion(node *ast.SplitRegionStmt) (base.Plan, error) {
-	tblInfo := node.Table.TableInfo
+	tnW := b.resolveCtx.GetTableName(node.Table)
+	tblInfo := tnW.TableInfo
 	if node.IndexName.L == strings.ToLower(mysql.PrimaryKeyName) &&
 		(tblInfo.IsCommonHandle || tblInfo.PKIsHandle) {
 		return nil, plannererrors.ErrKeyDoesNotExist.FastGen("unable to split clustered index, please split table instead.")
@@ -4614,7 +4640,8 @@ func (b *PlanBuilder) convertValue(valueItem ast.ExprNode, mockTablePlan base.Lo
 }
 
 func (b *PlanBuilder) buildSplitTableRegion(node *ast.SplitRegionStmt) (base.Plan, error) {
-	tblInfo := node.Table.TableInfo
+	tnW := b.resolveCtx.GetTableName(node.Table)
+	tblInfo := tnW.TableInfo
 	handleColInfos := buildHandleColumnInfos(tblInfo)
 	mockTablePlan := logicalop.LogicalTableDual{}.Init(b.ctx, b.getSelectOffset())
 	schema, names, err := expression.TableInfo2SchemaAndNames(b.ctx.GetExprCtx(), node.Table.Schema, tblInfo)
@@ -4874,7 +4901,8 @@ func (b *PlanBuilder) buildDDL(ctx context.Context, node ast.DDLNode) (base.Plan
 			stmt.AsViewSchema = true
 		}
 
-		plan, err := b.Build(ctx, v.Select)
+		nodeW := resolve.NewNodeWWithCtx(v.Select, b.resolveCtx)
+		plan, err := b.Build(ctx, nodeW)
 		if err != nil {
 			return nil, err
 		}
@@ -5061,9 +5089,10 @@ const (
 // buildTrace builds a trace plan. Inside this method, it first optimize the
 // underlying query and then constructs a schema, which will be used to constructs
 // rows result.
-func (*PlanBuilder) buildTrace(trace *ast.TraceStmt) (base.Plan, error) {
+func (b *PlanBuilder) buildTrace(trace *ast.TraceStmt) (base.Plan, error) {
 	p := &Trace{
 		StmtNode:             trace.Stmt,
+		ResolveCtx:           b.resolveCtx,
 		Format:               trace.Format,
 		OptimizerTrace:       trace.TracePlan,
 		OptimizerTraceTarget: trace.TracePlanTarget,
@@ -5173,7 +5202,8 @@ func (b *PlanBuilder) buildExplain(ctx context.Context, explain *ast.ExplainStmt
 		return nil, err
 	}
 
-	targetPlan, _, err := OptimizeAstNode(ctx, sctx, explain.Stmt, b.is)
+	nodeW := resolve.NewNodeWWithCtx(explain.Stmt, b.resolveCtx)
+	targetPlan, _, err := OptimizeAstNode(ctx, sctx, nodeW, b.is)
 	if err != nil {
 		return nil, err
 	}
@@ -5191,7 +5221,8 @@ func (b *PlanBuilder) buildSelectInto(ctx context.Context, sel *ast.SelectStmt)
 	if err != nil {
 		return nil, err
 	}
-	targetPlan, _, err := OptimizeAstNode(ctx, sctx, sel, b.is)
+	nodeW := resolve.NewNodeWWithCtx(sel, b.resolveCtx)
+	targetPlan, _, err := OptimizeAstNode(ctx, sctx, nodeW, b.is)
 	if err != nil {
 		return nil, err
 	}
@@ -5587,7 +5618,8 @@ func (b *PlanBuilder) buildCompactTable(node *ast.CompactTableStmt) (base.Plan,
 	b.visitInfo = appendVisitInfo(b.visitInfo, mysql.AlterPriv, node.Table.Schema.L,
 		node.Table.Name.L, "", authErr)
 
-	tblInfo := node.Table.TableInfo
+	tnW := b.resolveCtx.GetTableName(node.Table)
+	tblInfo := tnW.TableInfo
 	p := &CompactTable{
 		ReplicaKind:    node.ReplicaKind,
 		TableInfo:      tblInfo,
diff --git a/pkg/planner/core/point_get_plan.go b/pkg/planner/core/point_get_plan.go
index fc1b92282d..5fcf93342a 100644
--- a/pkg/planner/core/point_get_plan.go
+++ b/pkg/planner/core/point_get_plan.go
@@ -36,6 +36,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/baseimpl"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/planner/util/costusage"
@@ -880,7 +881,7 @@ type PointPlanVal struct {
 }
 
 // TryFastPlan tries to use the PointGetPlan for the query.
-func TryFastPlan(ctx base.PlanContext, node ast.Node) (p base.Plan) {
+func TryFastPlan(ctx base.PlanContext, node *resolve.NodeW) (p base.Plan) {
 	if checkStableResultMode(ctx) {
 		// the rule of stabilizing results has not taken effect yet, so cannot generate a plan here in this mode
 		return nil
@@ -888,7 +889,7 @@ func TryFastPlan(ctx base.PlanContext, node ast.Node) (p base.Plan) {
 
 	ctx.GetSessionVars().PlanID.Store(0)
 	ctx.GetSessionVars().PlanColumnID.Store(0)
-	switch x := node.(type) {
+	switch x := node.Node.(type) {
 	case *ast.SelectStmt:
 		if x.SelectIntoOpt != nil {
 			return nil
@@ -908,7 +909,7 @@ func TryFastPlan(ctx base.PlanContext, node ast.Node) (p base.Plan) {
 		}()
 		// Try to convert the `SELECT a, b, c FROM t WHERE (a, b, c) in ((1, 2, 4), (1, 3, 5))` to
 		// `PhysicalUnionAll` which children are `PointGet` if exists an unique key (a, b, c) in table `t`
-		if fp := tryWhereIn2BatchPointGet(ctx, x); fp != nil {
+		if fp := tryWhereIn2BatchPointGet(ctx, x, node.GetResolveContext()); fp != nil {
 			if checkFastPlanPrivilege(ctx, fp.dbName, fp.TblInfo.Name.L, mysql.SelectPriv) != nil {
 				return
 			}
@@ -919,7 +920,7 @@ func TryFastPlan(ctx base.PlanContext, node ast.Node) (p base.Plan) {
 			p = fp
 			return
 		}
-		if fp := tryPointGetPlan(ctx, x, isForUpdateReadSelectLock(x.LockInfo)); fp != nil {
+		if fp := tryPointGetPlan(ctx, x, node.GetResolveContext(), isForUpdateReadSelectLock(x.LockInfo)); fp != nil {
 			if checkFastPlanPrivilege(ctx, fp.dbName, fp.TblInfo.Name.L, mysql.SelectPriv) != nil {
 				return nil
 			}
@@ -938,9 +939,9 @@ func TryFastPlan(ctx base.PlanContext, node ast.Node) (p base.Plan) {
 			return
 		}
 	case *ast.UpdateStmt:
-		return tryUpdatePointPlan(ctx, x)
+		return tryUpdatePointPlan(ctx, x, node.GetResolveContext())
 	case *ast.DeleteStmt:
-		return tryDeletePointPlan(ctx, x)
+		return tryDeletePointPlan(ctx, x, node.GetResolveContext())
 	}
 	return nil
 }
@@ -1206,7 +1207,7 @@ func newBatchPointGetPlan(
 	return p.Init(ctx, statsInfo, schema, names, 0)
 }
 
-func tryWhereIn2BatchPointGet(ctx base.PlanContext, selStmt *ast.SelectStmt) *BatchPointGetPlan {
+func tryWhereIn2BatchPointGet(ctx base.PlanContext, selStmt *ast.SelectStmt, resolveCtx *resolve.Context) *BatchPointGetPlan {
 	if selStmt.OrderBy != nil || selStmt.GroupBy != nil ||
 		selStmt.Limit != nil || selStmt.Having != nil || selStmt.Distinct ||
 		len(selStmt.WindowSpecs) > 0 {
@@ -1223,10 +1224,12 @@ func tryWhereIn2BatchPointGet(ctx base.PlanContext, selStmt *ast.SelectStmt) *Ba
 	if tblName == nil {
 		return nil
 	}
-	tbl := tblName.TableInfo
-	if tbl == nil {
+	// tnW might be nil, in some ut, query is directly 'optimized' without pre-process
+	tnW := resolveCtx.GetTableName(tblName)
+	if tnW == nil {
 		return nil
 	}
+	tbl := tnW.TableInfo
 	// Skip the optimization with partition selection.
 	// TODO: Add test and remove this!
 	if len(tblName.PartitionNames) > 0 {
@@ -1307,7 +1310,7 @@ func tryWhereIn2BatchPointGet(ctx base.PlanContext, selStmt *ast.SelectStmt) *Ba
 // 2. It must be a single table select.
 // 3. All the columns must be public and not generated.
 // 4. The condition is an access path that the range is a unique key.
-func tryPointGetPlan(ctx base.PlanContext, selStmt *ast.SelectStmt, check bool) *PointGetPlan {
+func tryPointGetPlan(ctx base.PlanContext, selStmt *ast.SelectStmt, resolveCtx *resolve.Context, check bool) *PointGetPlan {
 	if selStmt.Having != nil || selStmt.OrderBy != nil {
 		return nil
 	} else if selStmt.Limit != nil {
@@ -1320,10 +1323,12 @@ func tryPointGetPlan(ctx base.PlanContext, selStmt *ast.SelectStmt, check bool)
 	if tblName == nil {
 		return nil
 	}
-	tbl := tblName.TableInfo
-	if tbl == nil {
+	// tnW might be nil, in some ut, query is directly 'optimized' without pre-process
+	tnW := resolveCtx.GetTableName(tblName)
+	if tnW == nil {
 		return nil
 	}
+	tbl := tnW.TableInfo
 
 	var pkColOffset int
 	for i, col := range tbl.Columns {
@@ -1374,10 +1379,10 @@ func tryPointGetPlan(ctx base.PlanContext, selStmt *ast.SelectStmt, check bool)
 		return nil
 	}
 
-	return checkTblIndexForPointPlan(ctx, tblName, schema, names, pairs, isTableDual, check)
+	return checkTblIndexForPointPlan(ctx, tnW, schema, names, pairs, isTableDual, check)
 }
 
-func checkTblIndexForPointPlan(ctx base.PlanContext, tblName *ast.TableName, schema *expression.Schema,
+func checkTblIndexForPointPlan(ctx base.PlanContext, tblName *resolve.TableNameW, schema *expression.Schema,
 	names []*types.FieldName, pairs []nameValuePair, isTableDual, check bool) *PointGetPlan {
 	check = check || ctx.GetSessionVars().IsIsolation(ast.ReadCommitted)
 	check = check && ctx.GetSessionVars().ConnectionID > 0
@@ -1894,7 +1899,7 @@ func checkIfAssignmentListHasSubQuery(list []*ast.Assignment) bool {
 	return false
 }
 
-func tryUpdatePointPlan(ctx base.PlanContext, updateStmt *ast.UpdateStmt) base.Plan {
+func tryUpdatePointPlan(ctx base.PlanContext, updateStmt *ast.UpdateStmt, resolveCtx *resolve.Context) base.Plan {
 	// Avoid using the point_get when assignment_list contains the sub-query in the UPDATE.
 	if checkIfAssignmentListHasSubQuery(updateStmt.List) {
 		return nil
@@ -1907,7 +1912,7 @@ func tryUpdatePointPlan(ctx base.PlanContext, updateStmt *ast.UpdateStmt) base.P
 		OrderBy: updateStmt.Order,
 		Limit:   updateStmt.Limit,
 	}
-	pointGet := tryPointGetPlan(ctx, selStmt, true)
+	pointGet := tryPointGetPlan(ctx, selStmt, resolveCtx, true)
 	if pointGet != nil {
 		if pointGet.IsTableDual {
 			return PhysicalTableDual{
@@ -1917,19 +1922,19 @@ func tryUpdatePointPlan(ctx base.PlanContext, updateStmt *ast.UpdateStmt) base.P
 		if ctx.GetSessionVars().TxnCtx.IsPessimistic {
 			pointGet.Lock, pointGet.LockWaitTime = getLockWaitTime(ctx, &ast.SelectLockInfo{LockType: ast.SelectLockForUpdate})
 		}
-		return buildPointUpdatePlan(ctx, pointGet, pointGet.dbName, pointGet.TblInfo, updateStmt)
+		return buildPointUpdatePlan(ctx, pointGet, pointGet.dbName, pointGet.TblInfo, updateStmt, resolveCtx)
 	}
-	batchPointGet := tryWhereIn2BatchPointGet(ctx, selStmt)
+	batchPointGet := tryWhereIn2BatchPointGet(ctx, selStmt, resolveCtx)
 	if batchPointGet != nil {
 		if ctx.GetSessionVars().TxnCtx.IsPessimistic {
 			batchPointGet.Lock, batchPointGet.LockWaitTime = getLockWaitTime(ctx, &ast.SelectLockInfo{LockType: ast.SelectLockForUpdate})
 		}
-		return buildPointUpdatePlan(ctx, batchPointGet, batchPointGet.dbName, batchPointGet.TblInfo, updateStmt)
+		return buildPointUpdatePlan(ctx, batchPointGet, batchPointGet.dbName, batchPointGet.TblInfo, updateStmt, resolveCtx)
 	}
 	return nil
 }
 
-func buildPointUpdatePlan(ctx base.PlanContext, pointPlan base.PhysicalPlan, dbName string, tbl *model.TableInfo, updateStmt *ast.UpdateStmt) base.Plan {
+func buildPointUpdatePlan(ctx base.PlanContext, pointPlan base.PhysicalPlan, dbName string, tbl *model.TableInfo, updateStmt *ast.UpdateStmt, resolveCtx *resolve.Context) base.Plan {
 	if checkFastPlanPrivilege(ctx, dbName, tbl.Name.L, mysql.SelectPriv, mysql.UpdatePriv) != nil {
 		return nil
 	}
@@ -1961,7 +1966,8 @@ func buildPointUpdatePlan(ctx base.PlanContext, pointPlan base.PhysicalPlan, dbN
 	}
 	if tbl.GetPartitionInfo() != nil {
 		pt := t.(table.PartitionedTable)
-		updateTableList := ExtractTableList(updateStmt.TableRefs.TableRefs, true)
+		nodeW := resolve.NewNodeWWithCtx(updateStmt.TableRefs.TableRefs, resolveCtx)
+		updateTableList := ExtractTableList(nodeW, true)
 		updatePlan.PartitionedTable = make([]table.PartitionedTable, 0, len(updateTableList))
 		for _, updateTable := range updateTableList {
 			if len(updateTable.PartitionNames) > 0 {
@@ -2022,7 +2028,7 @@ func buildOrderedList(ctx base.PlanContext, plan base.Plan, list []*ast.Assignme
 	return orderedList, allAssignmentsAreConstant
 }
 
-func tryDeletePointPlan(ctx base.PlanContext, delStmt *ast.DeleteStmt) base.Plan {
+func tryDeletePointPlan(ctx base.PlanContext, delStmt *ast.DeleteStmt, resolveCtx *resolve.Context) base.Plan {
 	if delStmt.IsMultiTable {
 		return nil
 	}
@@ -2033,7 +2039,7 @@ func tryDeletePointPlan(ctx base.PlanContext, delStmt *ast.DeleteStmt) base.Plan
 		OrderBy: delStmt.Order,
 		Limit:   delStmt.Limit,
 	}
-	if pointGet := tryPointGetPlan(ctx, selStmt, true); pointGet != nil {
+	if pointGet := tryPointGetPlan(ctx, selStmt, resolveCtx, true); pointGet != nil {
 		if pointGet.IsTableDual {
 			return PhysicalTableDual{
 				names: pointGet.outputNames,
@@ -2044,7 +2050,7 @@ func tryDeletePointPlan(ctx base.PlanContext, delStmt *ast.DeleteStmt) base.Plan
 		}
 		return buildPointDeletePlan(ctx, pointGet, pointGet.dbName, pointGet.TblInfo)
 	}
-	if batchPointGet := tryWhereIn2BatchPointGet(ctx, selStmt); batchPointGet != nil {
+	if batchPointGet := tryWhereIn2BatchPointGet(ctx, selStmt, resolveCtx); batchPointGet != nil {
 		if ctx.GetSessionVars().TxnCtx.IsPessimistic {
 			batchPointGet.Lock, batchPointGet.LockWaitTime = getLockWaitTime(ctx, &ast.SelectLockInfo{LockType: ast.SelectLockForUpdate})
 		}
diff --git a/pkg/planner/core/preprocess.go b/pkg/planner/core/preprocess.go
index 076799db3e..2da3a7e1e4 100644
--- a/pkg/planner/core/preprocess.go
+++ b/pkg/planner/core/preprocess.go
@@ -35,6 +35,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
@@ -122,7 +123,7 @@ func TryAddExtraLimit(ctx sessionctx.Context, node ast.StmtNode) ast.StmtNode {
 
 // Preprocess resolves table names of the node, and checks some statements' validation.
 // preprocessReturn used to extract the infoschema for the tableName and the timestamp from the asof clause.
-func Preprocess(ctx context.Context, sctx sessionctx.Context, node ast.Node, preprocessOpt ...PreprocessOpt) error {
+func Preprocess(ctx context.Context, sctx sessionctx.Context, node *resolve.NodeW, preprocessOpt ...PreprocessOpt) error {
 	defer tracing.StartRegion(ctx, "planner.Preprocess").End()
 	v := preprocessor{
 		ctx:                ctx,
@@ -130,6 +131,7 @@ func Preprocess(ctx context.Context, sctx sessionctx.Context, node ast.Node, pre
 		tableAliasInJoin:   make([]map[string]any, 0),
 		preprocessWith:     &preprocessWith{cteCanUsed: make([]string, 0), cteBeforeOffset: make([]int, 0)},
 		staleReadProcessor: staleread.NewStaleReadProcessor(ctx, sctx),
+		resolveCtx:         node.GetResolveContext(),
 	}
 	for _, optFn := range preprocessOpt {
 		optFn(&v)
@@ -138,7 +140,7 @@ func Preprocess(ctx context.Context, sctx sessionctx.Context, node ast.Node, pre
 	if v.PreprocessorReturn == nil {
 		v.PreprocessorReturn = &PreprocessorReturn{}
 	}
-	node.Accept(&v)
+	node.Node.Accept(&v)
 	// InfoSchema must be non-nil after preprocessing
 	v.ensureInfoSchema()
 	return errors.Trace(v.err)
@@ -236,6 +238,8 @@ type preprocessor struct {
 	// values that may be returned
 	*PreprocessorReturn
 	err error
+
+	resolveCtx *resolve.Context
 }
 
 func (p *preprocessor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
@@ -541,7 +545,8 @@ func (p *preprocessor) checkBindGrammar(originNode, hintedNode ast.StmtNode, def
 	}
 
 	// Check the bind operation is not on any temporary table.
-	tblNames := ExtractTableList(originNode, false)
+	nodeW := resolve.NewNodeWWithCtx(originNode, p.resolveCtx)
+	tblNames := ExtractTableList(nodeW, false)
 	for _, tn := range tblNames {
 		tbl, err := p.tableByName(tn)
 		if err != nil {
@@ -559,8 +564,11 @@ func (p *preprocessor) checkBindGrammar(originNode, hintedNode ast.StmtNode, def
 		}
 		tableInfo := tbl.Meta()
 		dbInfo, _ := infoschema.SchemaByTable(p.ensureInfoSchema(), tableInfo)
-		tn.TableInfo = tableInfo
-		tn.DBInfo = dbInfo
+		p.resolveCtx.AddTableName(&resolve.TableNameW{
+			TableName: tn,
+			DBInfo:    dbInfo,
+			TableInfo: tableInfo,
+		})
 	}
 
 	originSQL := parser.NormalizeForBinding(utilparser.RestoreWithDefaultDB(originNode, defaultDB, originNode.Text()), false)
@@ -1625,8 +1633,11 @@ func (p *preprocessor) handleTableName(tn *ast.TableName) {
 			return
 		}
 	}
-	tn.TableInfo = tableInfo
-	tn.DBInfo = dbInfo
+	p.resolveCtx.AddTableName(&resolve.TableNameW{
+		TableName: tn,
+		DBInfo:    dbInfo,
+		TableInfo: tableInfo,
+	})
 }
 
 func (p *preprocessor) checkNotInRepair(tn *ast.TableName) {
diff --git a/pkg/planner/core/resolve/BUILD.bazel b/pkg/planner/core/resolve/BUILD.bazel
new file mode 100644
index 0000000000..8d97749998
--- /dev/null
+++ b/pkg/planner/core/resolve/BUILD.bazel
@@ -0,0 +1,12 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "resolve",
+    srcs = ["resolve.go"],
+    importpath = "github.com/pingcap/tidb/pkg/planner/core/resolve",
+    visibility = ["//visibility:public"],
+    deps = [
+        "//pkg/parser/ast",
+        "//pkg/parser/model",
+    ],
+)
diff --git a/pkg/planner/core/resolve/resolve.go b/pkg/planner/core/resolve/resolve.go
new file mode 100644
index 0000000000..aab14ec18f
--- /dev/null
+++ b/pkg/planner/core/resolve/resolve.go
@@ -0,0 +1,90 @@
+// Copyright 2024 PingCAP, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package resolve is used for semantic resolve of the AST tree.
+// semantic resolve is mostly done by 'core.preprocessor', in tableListExtractor
+// and updatableTableListResolver we also do some resolve for aliases.
+package resolve
+
+import (
+	"github.com/pingcap/tidb/pkg/parser/ast"
+	"github.com/pingcap/tidb/pkg/parser/model"
+)
+
+// TableNameW is a wrapper around ast.TableName to store more information.
+type TableNameW struct {
+	*ast.TableName
+	DBInfo    *model.DBInfo
+	TableInfo *model.TableInfo
+}
+
+// NodeW is a wrapper around ast.Node to store resolve context.
+type NodeW struct {
+	Node       ast.Node
+	resolveCtx *Context
+}
+
+// NewNodeW creates a NodeW.
+func NewNodeW(node ast.Node) *NodeW {
+	return &NodeW{
+		Node:       node,
+		resolveCtx: NewContext(),
+	}
+}
+
+// NewNodeWWithCtx creates a NodeW with the given Context.
+func NewNodeWWithCtx(node ast.Node, resolveCtx *Context) *NodeW {
+	return &NodeW{
+		Node:       node,
+		resolveCtx: resolveCtx,
+	}
+}
+
+// CloneWithNewNode creates a new NodeW with the given ast.Node.
+func (n *NodeW) CloneWithNewNode(newNode ast.Node) *NodeW {
+	return &NodeW{
+		Node:       newNode,
+		resolveCtx: n.resolveCtx,
+	}
+}
+
+// GetResolveContext returns the Context of the NodeW.
+func (n *NodeW) GetResolveContext() *Context {
+	return n.resolveCtx
+}
+
+// Context is used to store the context and result of resolving AST tree.
+type Context struct {
+	tableNames map[*ast.TableName]*TableNameW
+}
+
+// NewContext creates a Context.
+func NewContext() *Context {
+	return &Context{
+		tableNames: make(map[*ast.TableName]*TableNameW),
+	}
+}
+
+// AddTableName adds the AST table name and its corresponding TableNameW to the Context.
+func (c *Context) AddTableName(tableNameW *TableNameW) {
+	c.tableNames[tableNameW.TableName] = tableNameW
+}
+
+// GetTableName returns the TableNameW of the AST table name.
+// the TableNameW should have been added to the Context in pre-process phase before
+// calling this function, if it doesn't exist, pre-process should have returned error,
+// so we don't check nil-ness in most cases.
+func (c *Context) GetTableName(tableName *ast.TableName) *TableNameW {
+	return c.tableNames[tableName]
+}
diff --git a/pkg/planner/core/trace.go b/pkg/planner/core/trace.go
index e6c81dea46..afb9daec1b 100644
--- a/pkg/planner/core/trace.go
+++ b/pkg/planner/core/trace.go
@@ -16,14 +16,16 @@ package core
 
 import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 )
 
 // Trace represents a trace plan.
 type Trace struct {
 	baseSchemaProducer
 
-	StmtNode ast.StmtNode
-	Format   string
+	StmtNode   ast.StmtNode
+	ResolveCtx *resolve.Context
+	Format     string
 
 	// OptimizerTrace indicates `trace plan target = 'xxx' <statement>` case
 	OptimizerTrace       bool
diff --git a/pkg/planner/funcdep/BUILD.bazel b/pkg/planner/funcdep/BUILD.bazel
index c9bd170ee0..36a074d5a2 100644
--- a/pkg/planner/funcdep/BUILD.bazel
+++ b/pkg/planner/funcdep/BUILD.bazel
@@ -31,6 +31,7 @@ go_test(
         "//pkg/parser",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/sessionctx",
         "//pkg/sessiontxn",
         "//pkg/testkit",
diff --git a/pkg/planner/memo/BUILD.bazel b/pkg/planner/memo/BUILD.bazel
index d546a99ba5..46ea8736a5 100644
--- a/pkg/planner/memo/BUILD.bazel
+++ b/pkg/planner/memo/BUILD.bazel
@@ -41,6 +41,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/pattern",
         "//pkg/planner/property",
         "//pkg/sessionctx/variable",
diff --git a/pkg/planner/optimize.go b/pkg/planner/optimize.go
index 6379ffae3f..ac596e7491 100644
--- a/pkg/planner/optimize.go
+++ b/pkg/planner/optimize.go
@@ -34,6 +34,7 @@ import (
 	pctx "github.com/pingcap/tidb/pkg/planner/context"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/util/debugtrace"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/sessionctx"
@@ -128,7 +129,7 @@ func getPlanFromNonPreparedPlanCache(ctx context.Context, sctx sessionctx.Contex
 }
 
 // Optimize does optimization and creates a Plan.
-func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is infoschema.InfoSchema) (plan base.Plan, slice types.NameSlice, retErr error) {
+func Optimize(ctx context.Context, sctx sessionctx.Context, node *resolve.NodeW, is infoschema.InfoSchema) (plan base.Plan, slice types.NameSlice, retErr error) {
 	defer tracing.StartRegion(ctx, "planner.Optimize").End()
 	sessVars := sctx.GetSessionVars()
 	pctx := sctx.GetPlanCtx()
@@ -138,7 +139,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 	}
 
 	if !sessVars.InRestrictedSQL && (variable.RestrictedReadOnly.Load() || variable.VarTiDBSuperReadOnly.Load()) {
-		allowed, err := allowInReadOnlyMode(pctx, node)
+		allowed, err := allowInReadOnlyMode(pctx, node.Node)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -147,7 +148,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 		}
 	}
 
-	if sessVars.SQLMode.HasStrictMode() && !IsReadOnly(node, sessVars) {
+	if sessVars.SQLMode.HasStrictMode() && !IsReadOnly(node.Node, sessVars) {
 		sessVars.StmtCtx.TiFlashEngineRemovedDueToStrictSQLMode = true
 		_, hasTiFlashAccess := sessVars.IsolationReadEngines[kv.TiFlash]
 		if hasTiFlashAccess {
@@ -162,12 +163,12 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 	}
 
 	// handle the execute statement
-	if execAST, ok := node.(*ast.ExecuteStmt); ok {
-		p, names, err := OptimizeExecStmt(ctx, sctx, execAST, is)
+	if _, ok := node.Node.(*ast.ExecuteStmt); ok {
+		p, names, err := OptimizeExecStmt(ctx, sctx, node, is)
 		return p, names, err
 	}
 
-	tableHints := hint.ExtractTableHintsFromStmtNode(node, sessVars.StmtCtx)
+	tableHints := hint.ExtractTableHintsFromStmtNode(node.Node, sessVars.StmtCtx)
 	originStmtHints, _, warns := hint.ParseStmtHints(tableHints,
 		setVarHintChecker, hypoIndexChecker(ctx, is),
 		sessVars.CurrentDB, byte(kv.ReplicaReadFollower))
@@ -237,7 +238,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 	}
 
 	enableUseBinding := sessVars.UsePlanBaselines
-	stmtNode, isStmtNode := node.(ast.StmtNode)
+	stmtNode, isStmtNode := node.Node.(ast.StmtNode)
 	binding, match, scope := bindinfo.MatchSQLBinding(sctx, stmtNode)
 	var bindings bindinfo.Bindings
 	if match {
@@ -267,7 +268,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 	if isStmtNode {
 		// add the extra Limit after matching the bind record
 		stmtNode = core.TryAddExtraLimit(sctx, stmtNode)
-		node = stmtNode
+		node = node.CloneWithNewNode(stmtNode)
 	}
 
 	// try to get Plan from the NonPrepared Plan Cache
@@ -394,7 +395,7 @@ func Optimize(ctx context.Context, sctx sessionctx.Context, node ast.Node, is in
 // OptimizeForForeignKeyCascade does optimization and creates a Plan for foreign key cascade.
 // Compare to Optimize, OptimizeForForeignKeyCascade only build plan by StmtNode,
 // doesn't consider plan cache and plan binding, also doesn't do privilege check.
-func OptimizeForForeignKeyCascade(ctx context.Context, sctx pctx.PlanContext, node ast.StmtNode, is infoschema.InfoSchema) (base.Plan, error) {
+func OptimizeForForeignKeyCascade(ctx context.Context, sctx pctx.PlanContext, node *resolve.NodeW, is infoschema.InfoSchema) (base.Plan, error) {
 	builder := planBuilderPool.Get().(*core.PlanBuilder)
 	defer planBuilderPool.Put(builder.ResetForReuse())
 	hintProcessor := hint.NewQBHintHandler(sctx.GetSessionVars().StmtCtx)
@@ -458,10 +459,10 @@ var planBuilderPool = sync.Pool{
 // optimizeCnt is a global variable only used for test.
 var optimizeCnt int
 
-func optimize(ctx context.Context, sctx pctx.PlanContext, node ast.Node, is infoschema.InfoSchema) (base.Plan, types.NameSlice, float64, error) {
+func optimize(ctx context.Context, sctx pctx.PlanContext, node *resolve.NodeW, is infoschema.InfoSchema) (base.Plan, types.NameSlice, float64, error) {
 	failpoint.Inject("checkOptimizeCountOne", func(val failpoint.Value) {
 		// only count the optimization for SQL with specified text
-		if testSQL, ok := val.(string); ok && testSQL == node.OriginalText() {
+		if testSQL, ok := val.(string); ok && testSQL == node.Node.OriginalText() {
 			optimizeCnt++
 			if optimizeCnt > 1 {
 				failpoint.Return(nil, nil, 0, errors.New("gofail wrong optimizerCnt error"))
@@ -480,7 +481,7 @@ func optimize(ctx context.Context, sctx pctx.PlanContext, node ast.Node, is info
 
 	// build logical plan
 	hintProcessor := hint.NewQBHintHandler(sctx.GetSessionVars().StmtCtx)
-	node.Accept(hintProcessor)
+	node.Node.Accept(hintProcessor)
 	defer hintProcessor.HandleUnusedViewHints()
 	builder := planBuilderPool.Get().(*core.PlanBuilder)
 	defer planBuilderPool.Put(builder.ResetForReuse())
@@ -495,7 +496,7 @@ func optimize(ctx context.Context, sctx pctx.PlanContext, node ast.Node, is info
 	// we need the table information to check privilege, which is collected
 	// into the visitInfo in the logical plan builder.
 	if pm := privilege.GetPrivilegeManager(sctx); pm != nil {
-		visitInfo := core.VisitInfo4PrivCheck(ctx, is, node, builder.GetVisitInfo())
+		visitInfo := core.VisitInfo4PrivCheck(ctx, is, node.Node, builder.GetVisitInfo())
 		if err := core.CheckPrivilege(activeRoles, pm, visitInfo); err != nil {
 			return nil, nil, 0, err
 		}
@@ -531,7 +532,7 @@ func optimize(ctx context.Context, sctx pctx.PlanContext, node ast.Node, is info
 
 // OptimizeExecStmt to handle the "execute" statement
 func OptimizeExecStmt(ctx context.Context, sctx sessionctx.Context,
-	execAst *ast.ExecuteStmt, is infoschema.InfoSchema) (base.Plan, types.NameSlice, error) {
+	execAst *resolve.NodeW, is infoschema.InfoSchema) (base.Plan, types.NameSlice, error) {
 	builder := planBuilderPool.Get().(*core.PlanBuilder)
 	defer planBuilderPool.Put(builder.ResetForReuse())
 	pctx := sctx.GetPlanCtx()
@@ -555,7 +556,7 @@ func OptimizeExecStmt(ctx context.Context, sctx sessionctx.Context,
 	return exec, names, nil
 }
 
-func buildLogicalPlan(ctx context.Context, sctx pctx.PlanContext, node ast.Node, builder *core.PlanBuilder) (base.Plan, error) {
+func buildLogicalPlan(ctx context.Context, sctx pctx.PlanContext, node *resolve.NodeW, builder *core.PlanBuilder) (base.Plan, error) {
 	sctx.GetSessionVars().PlanID.Store(0)
 	sctx.GetSessionVars().PlanColumnID.Store(0)
 	sctx.GetSessionVars().MapScalarSubQ = nil
diff --git a/pkg/server/BUILD.bazel b/pkg/server/BUILD.bazel
index 2b1a8a8047..91f3218c3a 100644
--- a/pkg/server/BUILD.bazel
+++ b/pkg/server/BUILD.bazel
@@ -43,6 +43,7 @@ go_library(
         "//pkg/parser/terror",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/plugin",
         "//pkg/privilege",
         "//pkg/privilege/conn",
diff --git a/pkg/server/conn.go b/pkg/server/conn.go
index 39e0e56b11..71cbc5ca27 100644
--- a/pkg/server/conn.go
+++ b/pkg/server/conn.go
@@ -75,6 +75,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/plugin"
 	"github.com/pingcap/tidb/pkg/privilege"
 	"github.com/pingcap/tidb/pkg/privilege/conn"
@@ -1936,11 +1937,12 @@ func (cc *clientConn) prefetchPointPlanKeys(ctx context.Context, stmts []ast.Stm
 			return nil, nil
 		}
 		// TODO: the preprocess is run twice, we should find some way to avoid do it again.
-		if err = plannercore.Preprocess(ctx, cc.getCtx(), stmt); err != nil {
+		nodeW := resolve.NewNodeW(stmt)
+		if err = plannercore.Preprocess(ctx, cc.getCtx(), nodeW); err != nil {
 			// error might happen, see https://github.com/pingcap/tidb/issues/39664
 			return nil, nil
 		}
-		p := plannercore.TryFastPlan(cc.ctx.Session.GetPlanCtx(), stmt)
+		p := plannercore.TryFastPlan(cc.ctx.Session.GetPlanCtx(), nodeW)
 		pointPlans[i] = p
 		if p == nil {
 			continue
diff --git a/pkg/server/extension.go b/pkg/server/extension.go
index 2478c99d5c..e6d0f1a32b 100644
--- a/pkg/server/extension.go
+++ b/pkg/server/extension.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/parser/auth"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/types"
@@ -218,7 +219,8 @@ func (e *stmtEventInfo) RelatedTables() []stmtctx.TableEntry {
 	if e.sc != nil && e.err == nil {
 		return e.sc.Tables
 	}
-	tableNames := core.ExtractTableList(e.stmtNode, false)
+	nodeW := resolve.NewNodeW(e.stmtNode)
+	tableNames := core.ExtractTableList(nodeW, false)
 	tableEntries := make([]stmtctx.TableEntry, 0, len(tableNames))
 	for i, tableName := range tableNames {
 		if tableName != nil {
diff --git a/pkg/session/BUILD.bazel b/pkg/session/BUILD.bazel
index db889bb529..7c356bbcaa 100644
--- a/pkg/session/BUILD.bazel
+++ b/pkg/session/BUILD.bazel
@@ -61,6 +61,7 @@ go_library(
         "//pkg/planner/contextimpl",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/plugin",
         "//pkg/privilege",
         "//pkg/privilege/conn",
diff --git a/pkg/session/nontransactional.go b/pkg/session/nontransactional.go
index 390de607cd..e272fbb74b 100644
--- a/pkg/session/nontransactional.go
+++ b/pkg/session/nontransactional.go
@@ -31,6 +31,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/parser/opcode"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	session_metrics "github.com/pingcap/tidb/pkg/session/metrics"
 	sessiontypes "github.com/pingcap/tidb/pkg/session/types"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
@@ -81,7 +82,8 @@ func HandleNonTransactionalDML(ctx context.Context, stmt *ast.NonTransactionalDM
 	defer func() {
 		sessVars.ReadStaleness = originalReadStaleness
 	}()
-	err := core.Preprocess(ctx, se, stmt)
+	nodeW := resolve.NewNodeW(stmt)
+	err := core.Preprocess(ctx, se, nodeW)
 	if err != nil {
 		return nil, err
 	}
@@ -89,7 +91,7 @@ func HandleNonTransactionalDML(ctx context.Context, stmt *ast.NonTransactionalDM
 		return nil, err
 	}
 
-	tableName, selectSQL, shardColumnInfo, tableSources, err := buildSelectSQL(stmt, se)
+	tableName, selectSQL, shardColumnInfo, tableSources, err := buildSelectSQL(stmt, nodeW.GetResolveContext(), se)
 	if err != nil {
 		return nil, err
 	}
@@ -113,7 +115,8 @@ func HandleNonTransactionalDML(ctx context.Context, stmt *ast.NonTransactionalDM
 		return nil, err
 	}
 
-	splitStmts, err := runJobs(ctx, jobs, stmt, tableName, se, stmt.DMLStmt.WhereExpr())
+	tnW := nodeW.GetResolveContext().GetTableName(tableName)
+	splitStmts, err := runJobs(ctx, jobs, stmt, tnW, se, stmt.DMLStmt.WhereExpr())
 	if err != nil {
 		return nil, err
 	}
@@ -255,7 +258,7 @@ func checkReadClauses(limit *ast.Limit, order *ast.OrderByClause) error {
 
 // single-threaded worker. work on the key range [start, end]
 func runJobs(ctx context.Context, jobs []job, stmt *ast.NonTransactionalDMLStmt,
-	tableName *ast.TableName, se sessiontypes.Session, originalCondition ast.ExprNode) ([]string, error) {
+	tableName *resolve.TableNameW, se sessiontypes.Session, originalCondition ast.ExprNode) ([]string, error) {
 	// prepare for the construction of statement
 	var shardColumnRefer *ast.ResultField
 	var shardColumnType types.FieldType
@@ -336,8 +339,7 @@ func doOneJob(ctx context.Context, job *job, totalJobCount int, options statemen
 	if job.start.IsNull() {
 		isNullCondition := &ast.IsNullExpr{
 			Expr: &ast.ColumnNameExpr{
-				Name:  options.stmt.ShardColumn,
-				Refer: options.shardColumnRefer,
+				Name: options.stmt.ShardColumn,
 			},
 			Not: false,
 		}
@@ -352,8 +354,7 @@ func doOneJob(ctx context.Context, job *job, totalJobCount int, options statemen
 			leCondition := &ast.BinaryOperationExpr{
 				Op: opcode.LE,
 				L: &ast.ColumnNameExpr{
-					Name:  options.stmt.ShardColumn,
-					Refer: options.shardColumnRefer,
+					Name: options.stmt.ShardColumn,
 				},
 				R: right,
 			}
@@ -373,8 +374,7 @@ func doOneJob(ctx context.Context, job *job, totalJobCount int, options statemen
 		right.Datum = job.end
 		whereCondition = &ast.BetweenExpr{
 			Expr: &ast.ColumnNameExpr{
-				Name:  options.stmt.ShardColumn,
-				Refer: options.shardColumnRefer,
+				Name: options.stmt.ShardColumn,
 			},
 			Left:  left,
 			Right: right,
@@ -530,7 +530,7 @@ func appendNewJob(jobs []job, id int, start types.Datum, end types.Datum, size i
 	return jobs
 }
 
-func buildSelectSQL(stmt *ast.NonTransactionalDMLStmt, se sessiontypes.Session) (
+func buildSelectSQL(stmt *ast.NonTransactionalDMLStmt, resolveCtx *resolve.Context, se sessiontypes.Session) (
 	*ast.TableName, string, *model.ColumnInfo, []*ast.TableSource, error) {
 	// only use the first table
 	join, ok := stmt.DMLStmt.TableRefsJoin()
@@ -571,8 +571,9 @@ func buildSelectSQL(stmt *ast.NonTransactionalDMLStmt, se sessiontypes.Session)
 		sb.WriteString("TRUE")
 	}
 	// assure NULL values are placed first
+	tnW := resolveCtx.GetTableName(tableName)
 	selectSQL := fmt.Sprintf("SELECT `%s` FROM `%s`.`%s` WHERE %s ORDER BY IF(ISNULL(`%s`),0,1),`%s`",
-		stmt.ShardColumn.Name.O, tableName.DBInfo.Name.O, tableName.Name.O, sb.String(), stmt.ShardColumn.Name.O, stmt.ShardColumn.Name.O)
+		stmt.ShardColumn.Name.O, tnW.DBInfo.Name.O, tableName.Name.O, sb.String(), stmt.ShardColumn.Name.O, stmt.ShardColumn.Name.O)
 	return tableName, selectSQL, shardColumnInfo, tableSources, nil
 }
 
diff --git a/pkg/sessiontxn/isolation/BUILD.bazel b/pkg/sessiontxn/isolation/BUILD.bazel
index afaafcf40a..56ccfa1de1 100644
--- a/pkg/sessiontxn/isolation/BUILD.bazel
+++ b/pkg/sessiontxn/isolation/BUILD.bazel
@@ -67,6 +67,7 @@ go_test(
         "//pkg/parser",
         "//pkg/parser/ast",
         "//pkg/planner",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx",
         "//pkg/sessiontxn",
diff --git a/pkg/util/generatedexpr/generated_expr.go b/pkg/util/generatedexpr/generated_expr.go
index cc7f8055c3..0a407a504a 100644
--- a/pkg/util/generatedexpr/generated_expr.go
+++ b/pkg/util/generatedexpr/generated_expr.go
@@ -44,10 +44,6 @@ func (nr *nameResolver) Leave(inNode ast.Node) (node ast.Node, ok bool) {
 	case *ast.ColumnNameExpr:
 		for _, col := range nr.tableInfo.Columns {
 			if col.Name.L == v.Name.Name.L {
-				v.Refer = &ast.ResultField{
-					Column: col,
-					Table:  nr.tableInfo,
-				}
 				return inNode, true
 			}
 		}
diff --git a/pkg/util/ranger/BUILD.bazel b/pkg/util/ranger/BUILD.bazel
index 22f01dba8c..7790e2da04 100644
--- a/pkg/util/ranger/BUILD.bazel
+++ b/pkg/util/ranger/BUILD.bazel
@@ -59,6 +59,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx",
         "//pkg/testkit",


Test patch:
diff --git a/pkg/executor/brie_test.go b/pkg/executor/brie_test.go
index 151bc5e09e..6a94568772 100644
--- a/pkg/executor/brie_test.go
+++ b/pkg/executor/brie_test.go
@@ -34,6 +34,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/types"
 	"github.com/pingcap/tidb/pkg/util/mock"
 	"github.com/stretchr/testify/require"
@@ -82,7 +83,8 @@ func TestFetchShowBRIE(t *testing.T) {
 	p.SetParserConfig(parser.ParserConfig{EnableWindowFunction: true, EnableStrictDoubleTypeCheck: true})
 	stmt, err := p.ParseOneStmt("show backups", "", "")
 	require.NoError(t, err)
-	plan, err := core.BuildLogicalPlanForTest(ctx, sctx, stmt, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := core.BuildLogicalPlanForTest(ctx, sctx, nodeW, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
 	require.NoError(t, err)
 	schema := plan.Schema()
 
@@ -161,7 +163,8 @@ func TestBRIEBuilderOPtions(t *testing.T) {
 	require.NoError(t, err)
 	stmt, err := p.ParseOneStmt("BACKUP TABLE `a` TO 'noop://' CHECKSUM_CONCURRENCY = 4 IGNORE_STATS = 1 COMPRESSION_LEVEL = 4 COMPRESSION_TYPE = 'lz4' ENCRYPTION_METHOD = 'aes256-ctr' ENCRYPTION_KEYFILE = '/tmp/keyfile'", "", "")
 	require.NoError(t, err)
-	plan, err := core.BuildLogicalPlanForTest(ctx, sctx, stmt, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := core.BuildLogicalPlanForTest(ctx, sctx, nodeW, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
 	require.NoError(t, err)
 	s, ok := stmt.(*ast.BRIEStmt)
 	require.True(t, ok)
@@ -196,7 +199,8 @@ func TestBRIEBuilderOPtions(t *testing.T) {
 
 	stmt, err = p.ParseOneStmt("RESTORE TABLE `a` FROM 'noop://' CHECKSUM_CONCURRENCY = 4 WAIT_TIFLASH_READY = 1 WITH_SYS_TABLE = 1 LOAD_STATS = 1", "", "")
 	require.NoError(t, err)
-	plan, err = core.BuildLogicalPlanForTest(ctx, sctx, stmt, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
+	nodeW = resolve.NewNodeW(stmt)
+	plan, err = core.BuildLogicalPlanForTest(ctx, sctx, nodeW, infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable(), core.MockView()}))
 	require.NoError(t, err)
 	s, ok = stmt.(*ast.BRIEStmt)
 	require.True(t, ok)
diff --git a/pkg/executor/importer/importer_testkit_test.go b/pkg/executor/importer/importer_testkit_test.go
index 76a274bbe9..90f31e6ca9 100644
--- a/pkg/executor/importer/importer_testkit_test.go
+++ b/pkg/executor/importer/importer_testkit_test.go
@@ -40,6 +40,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -267,8 +268,11 @@ func getTableImporter(ctx context.Context, t *testing.T, store kv.Storage, table
 		selectPlan = &plannercore.PhysicalSelection{}
 	}
 	plan, err := importer.NewImportPlan(ctx, tk.Session(), &plannercore.ImportInto{
-		Path:       path,
-		Table:      &ast.TableName{Name: table.Meta().Name, DBInfo: dbInfo},
+		Path: path,
+		Table: &resolve.TableNameW{
+			TableName: &ast.TableName{Name: table.Meta().Name},
+			DBInfo:    dbInfo,
+		},
 		Options:    opts,
 		SelectPlan: selectPlan,
 	}, table)
diff --git a/pkg/executor/importer/table_import_testkit_test.go b/pkg/executor/importer/table_import_testkit_test.go
index 8ab1979ccc..2c4f19c5cf 100644
--- a/pkg/executor/importer/table_import_testkit_test.go
+++ b/pkg/executor/importer/table_import_testkit_test.go
@@ -27,6 +27,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/parser/model"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/types"
@@ -83,8 +84,10 @@ func TestImportFromSelectCleanup(t *testing.T) {
 	table, err := do.InfoSchema().TableByName(context.Background(), model.NewCIStr("test"), model.NewCIStr("t"))
 	require.NoError(t, err)
 	plan, err := importer.NewImportPlan(ctx, tk.Session(), plannercore.ImportInto{
-		Table: &ast.TableName{
-			Name: model.NewCIStr("t"),
+		Table: &resolve.TableNameW{
+			TableName: &ast.TableName{
+				Name: model.NewCIStr("t"),
+			},
 			DBInfo: &model.DBInfo{
 				Name: model.NewCIStr("test"),
 				ID:   dbInfo.ID,
diff --git a/pkg/executor/metrics_reader_test.go b/pkg/executor/metrics_reader_test.go
index 07b5bfd5d8..2d18abb9ce 100644
--- a/pkg/executor/metrics_reader_test.go
+++ b/pkg/executor/metrics_reader_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/stretchr/testify/require"
 )
@@ -65,9 +66,10 @@ func TestStmtLabel(t *testing.T) {
 		stmtNode, err := parser.New().ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err)
 		preprocessorReturn := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), tk.Session(), stmtNode, plannercore.WithPreprocessorReturn(preprocessorReturn))
+		nodeW := resolve.NewNodeW(stmtNode)
+		err = plannercore.Preprocess(context.Background(), tk.Session(), nodeW, plannercore.WithPreprocessorReturn(preprocessorReturn))
 		require.NoError(t, err)
-		_, _, err = planner.Optimize(context.TODO(), tk.Session(), stmtNode, preprocessorReturn.InfoSchema)
+		_, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, preprocessorReturn.InfoSchema)
 		require.NoError(t, err)
 		require.Equal(t, tt.label, ast.GetStmtLabel(stmtNode))
 	}
diff --git a/pkg/executor/test/executor/BUILD.bazel b/pkg/executor/test/executor/BUILD.bazel
index 7862539266..22e09810a3 100644
--- a/pkg/executor/test/executor/BUILD.bazel
+++ b/pkg/executor/test/executor/BUILD.bazel
@@ -28,6 +28,7 @@ go_test(
         "//pkg/planner",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx",
         "//pkg/sessionctx/stmtctx",
diff --git a/pkg/executor/test/executor/executor_test.go b/pkg/executor/test/executor/executor_test.go
index 95a575a0d7..7fd91f5840 100644
--- a/pkg/executor/test/executor/executor_test.go
+++ b/pkg/executor/test/executor/executor_test.go
@@ -50,6 +50,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
@@ -785,7 +786,8 @@ func TestUnreasonablyClose(t *testing.T) {
 
 		executorBuilder := executor.NewMockExecutorBuilderForTest(tk.Session(), is)
 
-		p, _, _ := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, _ := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NotNil(t, p)
 
 		// This for loop level traverses the plan tree to get which operators are covered.
@@ -874,7 +876,8 @@ func TestTwiceCloseUnionExec(t *testing.T) {
 		require.NoError(t, err, comment)
 
 		executorBuilder := executor.NewMockExecutorBuilderForTest(tk.Session(), is)
-		p, _, _ := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, _ := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		e := executorBuilder.Build(p)
 		chk := exec.NewFirstChunk(e)
 		require.NoError(t, exec.Open(context.Background(), e), comment)
@@ -1990,9 +1993,10 @@ func TestIsPointGet(t *testing.T) {
 		stmtNode, err := s.ParseOneStmt(sqlStr, "", "")
 		require.NoError(t, err)
 		preprocessorReturn := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), ctx, stmtNode, plannercore.WithPreprocessorReturn(preprocessorReturn))
+		nodeW := resolve.NewNodeW(stmtNode)
+		err = plannercore.Preprocess(context.Background(), ctx, nodeW, plannercore.WithPreprocessorReturn(preprocessorReturn))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmtNode, preprocessorReturn.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, preprocessorReturn.InfoSchema)
 		require.NoError(t, err)
 		ret := plannercore.IsPointGetWithPKOrUniqueKeyByAutoCommit(ctx.GetSessionVars(), p)
 		require.Equal(t, result, ret)
@@ -2030,9 +2034,10 @@ func TestClusteredIndexIsPointGet(t *testing.T) {
 		stmtNode, err := s.ParseOneStmt(sqlStr, "", "")
 		require.NoError(t, err)
 		preprocessorReturn := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), ctx, stmtNode, plannercore.WithPreprocessorReturn(preprocessorReturn))
+		nodeW := resolve.NewNodeW(stmtNode)
+		err = plannercore.Preprocess(context.Background(), ctx, nodeW, plannercore.WithPreprocessorReturn(preprocessorReturn))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmtNode, preprocessorReturn.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, preprocessorReturn.InfoSchema)
 		require.NoError(t, err)
 		ret := plannercore.IsPointGetWithPKOrUniqueKeyByAutoCommit(ctx.GetSessionVars(), p)
 		require.Equal(t, result, ret)
diff --git a/pkg/expression/integration_test/BUILD.bazel b/pkg/expression/integration_test/BUILD.bazel
index 1a90ed073e..97fbba2ef2 100644
--- a/pkg/expression/integration_test/BUILD.bazel
+++ b/pkg/expression/integration_test/BUILD.bazel
@@ -22,6 +22,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx/variable",
         "//pkg/store/mockstore",
diff --git a/pkg/expression/integration_test/integration_test.go b/pkg/expression/integration_test/integration_test.go
index eb38fe3495..603a0ced97 100644
--- a/pkg/expression/integration_test/integration_test.go
+++ b/pkg/expression/integration_test/integration_test.go
@@ -43,6 +43,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/store/mockstore"
@@ -928,9 +929,10 @@ func TestFilterExtractFromDNF(t *testing.T) {
 		require.NoError(t, err, "error %v, for expr %s", err, tt.exprStr)
 		require.Len(t, stmts, 1)
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err, "error %v, for resolve name, expr %s", err, tt.exprStr)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err, "error %v, for build plan, expr %s", err, tt.exprStr)
 		selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 		conds := make([]expression.Expression, len(selection.Conditions))
diff --git a/pkg/expression/typeinfer_test.go b/pkg/expression/typeinfer_test.go
index 280743fe2f..0f8989d9cd 100644
--- a/pkg/expression/typeinfer_test.go
+++ b/pkg/expression/typeinfer_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/charset"
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
@@ -126,9 +127,10 @@ func TestInferType(t *testing.T) {
 		require.NoError(t, err)
 
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmt, plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err, comment)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmt, ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err, comment)
 		tp := p.Schema().Columns[0].RetType
 		require.Equal(t, tt.tp, tp.GetType(), comment)
diff --git a/pkg/planner/cardinality/selectivity_test.go b/pkg/planner/cardinality/selectivity_test.go
index bec05976ed..f5622d4c7d 100644
--- a/pkg/planner/cardinality/selectivity_test.go
+++ b/pkg/planner/cardinality/selectivity_test.go
@@ -34,6 +34,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
@@ -88,9 +89,10 @@ func BenchmarkSelectivity(b *testing.B) {
 	require.NoErrorf(b, err, "error %v, for expr %s", err, exprs)
 	require.Len(b, stmts, 1)
 	ret := &plannercore.PreprocessorReturn{}
-	err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+	nodeW := resolve.NewNodeW(stmts[0])
+	err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 	require.NoErrorf(b, err, "for %s", exprs)
-	p, err := plannercore.BuildLogicalPlanForTest(context.Background(), sctx, stmts[0], ret.InfoSchema)
+	p, err := plannercore.BuildLogicalPlanForTest(context.Background(), sctx, nodeW, ret.InfoSchema)
 	require.NoErrorf(b, err, "error %v, for building plan, expr %s", err, exprs)
 
 	file, err := os.Create("cpu.profile")
@@ -446,9 +448,10 @@ func TestSelectivity(t *testing.T) {
 		require.Len(t, stmts, 1)
 
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoErrorf(t, err, "for expr %s", tt.exprs)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoErrorf(t, err, "for building plan, expr %s", err, tt.exprs)
 
 		sel := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
@@ -504,9 +507,10 @@ func TestDNFCondSelectivity(t *testing.T) {
 		require.Len(t, stmts, 1)
 
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoErrorf(t, err, "error %v, for sql %s", err, tt)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoErrorf(t, err, "error %v, for building plan, sql %s", err, tt)
 
 		sel := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
diff --git a/pkg/planner/cardinality/trace_test.go b/pkg/planner/cardinality/trace_test.go
index cc194f9cff..e1759c6707 100644
--- a/pkg/planner/cardinality/trace_test.go
+++ b/pkg/planner/cardinality/trace_test.go
@@ -32,6 +32,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
@@ -81,7 +82,8 @@ func TestTraceCE(t *testing.T) {
 		stmtCtx.OptimizerCETrace = nil
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
-		_, _, err = plannercore.OptimizeAstNode(context.Background(), sctx, stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		_, _, err = plannercore.OptimizeAstNode(context.Background(), sctx, nodeW, is)
 		require.NoError(t, err)
 
 		traceResult := sctx.GetSessionVars().StmtCtx.OptimizerCETrace
@@ -205,9 +207,10 @@ func TestTraceDebugSelectivity(t *testing.T) {
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmt, plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, err := plannercore.BuildLogicalPlanForTest(context.Background(), sctx, stmt, ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(context.Background(), sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 
 		sel := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
diff --git a/pkg/planner/cascades/optimize_test.go b/pkg/planner/cascades/optimize_test.go
index 0ae8cc0e6d..417c3ca720 100644
--- a/pkg/planner/cascades/optimize_test.go
+++ b/pkg/planner/cascades/optimize_test.go
@@ -27,6 +27,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/memo"
 	"github.com/pingcap/tidb/pkg/planner/pattern"
 	"github.com/pingcap/tidb/pkg/planner/property"
@@ -44,8 +45,8 @@ func TestImplGroupZeroCost(t *testing.T) {
 
 	stmt, err := p.ParseOneStmt("select t1.a, t2.a from t as t1 left join t as t2 on t1.a = t2.a where t1.a < 1.0", "", "")
 	require.NoError(t, err)
-
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 
 	logic, ok := plan.(base.LogicalPlan)
@@ -72,7 +73,8 @@ func TestInitGroupSchema(t *testing.T) {
 	stmt, err := p.ParseOneStmt("select a from t", "", "")
 	require.NoError(t, err)
 
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 
 	logic, ok := plan.(base.LogicalPlan)
@@ -97,7 +99,8 @@ func TestFillGroupStats(t *testing.T) {
 	stmt, err := p.ParseOneStmt("select * from t t1 join t t2 on t1.a = t2.a", "", "")
 	require.NoError(t, err)
 
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 
 	logic, ok := plan.(base.LogicalPlan)
@@ -131,7 +134,8 @@ func TestPreparePossibleProperties(t *testing.T) {
 	stmt, err := p.ParseOneStmt("select f, sum(a) from t group by f", "", "")
 	require.NoError(t, err)
 
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 
 	logic, ok := plan.(base.LogicalPlan)
@@ -228,7 +232,8 @@ func TestAppliedRuleSet(t *testing.T) {
 	stmt, err := p.ParseOneStmt("select 1", "", "")
 	require.NoError(t, err)
 
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 
 	logic, ok := plan.(base.LogicalPlan)
diff --git a/pkg/planner/cascades/stringer_test.go b/pkg/planner/cascades/stringer_test.go
index 329960354e..d4773325ce 100644
--- a/pkg/planner/cascades/stringer_test.go
+++ b/pkg/planner/cascades/stringer_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/memo"
 	"github.com/pingcap/tidb/pkg/planner/pattern"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
@@ -63,7 +64,8 @@ func TestGroupStringer(t *testing.T) {
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
 
-		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 		require.NoError(t, err)
 
 		logic, ok := plan.(base.LogicalPlan)
diff --git a/pkg/planner/cascades/transformation_rules_test.go b/pkg/planner/cascades/transformation_rules_test.go
index c8219e023d..cbd526c56d 100644
--- a/pkg/planner/cascades/transformation_rules_test.go
+++ b/pkg/planner/cascades/transformation_rules_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/memo"
 	"github.com/pingcap/tidb/pkg/planner/pattern"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
@@ -46,7 +47,8 @@ func testGroupToString(t *testing.T, input []string, output []struct {
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
 
-		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 		require.NoError(t, err)
 
 		logic, ok := plan.(base.LogicalPlan)
@@ -98,7 +100,8 @@ func TestAggPushDownGather(t *testing.T) {
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
 
-		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 		require.NoError(t, err)
 
 		logic, ok := plan.(base.LogicalPlan)
diff --git a/pkg/planner/core/casetest/BUILD.bazel b/pkg/planner/core/casetest/BUILD.bazel
index 839db4514a..4373abd827 100644
--- a/pkg/planner/core/casetest/BUILD.bazel
+++ b/pkg/planner/core/casetest/BUILD.bazel
@@ -21,6 +21,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/property",
         "//pkg/planner/util/coretestsdk",
         "//pkg/testkit",
diff --git a/pkg/planner/core/casetest/cbotest/BUILD.bazel b/pkg/planner/core/casetest/cbotest/BUILD.bazel
index 76b6a2dda2..375b5b2d22 100644
--- a/pkg/planner/core/casetest/cbotest/BUILD.bazel
+++ b/pkg/planner/core/casetest/cbotest/BUILD.bazel
@@ -17,6 +17,7 @@ go_test(
         "//pkg/parser/model",
         "//pkg/planner",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessionctx/variable",
         "//pkg/statistics",
diff --git a/pkg/planner/core/casetest/cbotest/cbo_test.go b/pkg/planner/core/casetest/cbotest/cbo_test.go
index d695158fda..0e368d38b0 100644
--- a/pkg/planner/core/casetest/cbotest/cbo_test.go
+++ b/pkg/planner/core/casetest/cbotest/cbo_test.go
@@ -27,6 +27,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
 	"github.com/pingcap/tidb/pkg/statistics"
@@ -212,9 +213,10 @@ func TestIndexRead(t *testing.T) {
 		require.Len(t, stmts, 1)
 		stmt := stmts[0]
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		planString := core.ToString(p)
 		testdata.OnRecord(func() {
@@ -243,9 +245,10 @@ func TestEmptyTable(t *testing.T) {
 		require.Len(t, stmts, 1)
 		stmt := stmts[0]
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		planString := core.ToString(p)
 		testdata.OnRecord(func() {
@@ -310,9 +313,10 @@ func TestAnalyze(t *testing.T) {
 		err = executor.ResetContextOfStmt(ctx, stmt)
 		require.NoError(t, err)
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		planString := core.ToString(p)
 		testdata.OnRecord(func() {
diff --git a/pkg/planner/core/casetest/dag/BUILD.bazel b/pkg/planner/core/casetest/dag/BUILD.bazel
index bcd13ebcde..d48c087513 100644
--- a/pkg/planner/core/casetest/dag/BUILD.bazel
+++ b/pkg/planner/core/casetest/dag/BUILD.bazel
@@ -18,6 +18,7 @@ go_test(
         "//pkg/parser/model",
         "//pkg/planner",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/sessiontxn",
         "//pkg/testkit",
diff --git a/pkg/planner/core/casetest/dag/dag_test.go b/pkg/planner/core/casetest/dag/dag_test.go
index 9fc497b8c3..df49498515 100644
--- a/pkg/planner/core/casetest/dag/dag_test.go
+++ b/pkg/planner/core/casetest/dag/dag_test.go
@@ -26,6 +26,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -68,7 +69,8 @@ func TestDAGPlanBuilderSimpleCase(t *testing.T) {
 		require.NoError(t, err, comment)
 		require.NoError(t, sessiontxn.NewTxn(context.Background(), tk.Session()))
 		tk.Session().GetSessionVars().StmtCtx.OriginalSQL = tt
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -104,7 +106,8 @@ func TestDAGPlanBuilderJoin(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -141,7 +144,8 @@ func TestDAGPlanBuilderSubquery(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -171,7 +175,8 @@ func TestDAGPlanTopN(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -203,10 +208,10 @@ func TestDAGPlanBuilderBasePhysicalPlan(t *testing.T) {
 		comment := fmt.Sprintf("input: %s", tt)
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-
-		err = core.Preprocess(context.Background(), se, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), se, nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), se, stmt, is)
+		p, _, err := planner.Optimize(context.TODO(), se, nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -245,7 +250,8 @@ func TestDAGPlanBuilderUnion(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -280,7 +286,8 @@ func TestDAGPlanBuilderUnionScan(t *testing.T) {
 		require.NoError(t, err, comment)
 		dom := domain.GetDomain(tk.Session())
 		require.NoError(t, dom.Reload())
-		plan, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, dom.InfoSchema())
+		nodeW := resolve.NewNodeW(stmt)
+		plan, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, dom.InfoSchema())
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -317,7 +324,8 @@ func TestDAGPlanBuilderAgg(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -349,7 +357,8 @@ func doTestDAGPlanBuilderWindow(t *testing.T, vars, input []string, output []str
 
 		err = sessiontxn.NewTxn(context.Background(), tk.Session())
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
diff --git a/pkg/planner/core/casetest/flatplan/BUILD.bazel b/pkg/planner/core/casetest/flatplan/BUILD.bazel
index f246f4fe15..fcd9da8b6a 100644
--- a/pkg/planner/core/casetest/flatplan/BUILD.bazel
+++ b/pkg/planner/core/casetest/flatplan/BUILD.bazel
@@ -17,6 +17,7 @@ go_test(
         "//pkg/parser/model",
         "//pkg/planner",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/testkit",
         "//pkg/testkit/testdata",
         "//pkg/testkit/testmain",
diff --git a/pkg/planner/core/casetest/flatplan/flat_plan_test.go b/pkg/planner/core/casetest/flatplan/flat_plan_test.go
index ae4461ea6b..f057165bf2 100644
--- a/pkg/planner/core/casetest/flatplan/flat_plan_test.go
+++ b/pkg/planner/core/casetest/flatplan/flat_plan_test.go
@@ -25,6 +25,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
 	"github.com/stretchr/testify/require"
@@ -83,7 +84,8 @@ func TestFlatPhysicalPlan(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, test)
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(context.Background(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.Background(), tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 
 		explained := core.FlattenPhysicalPlan(p, false)
diff --git a/pkg/planner/core/casetest/physicalplantest/BUILD.bazel b/pkg/planner/core/casetest/physicalplantest/BUILD.bazel
index 43f0543c42..bb2eafb9f7 100644
--- a/pkg/planner/core/casetest/physicalplantest/BUILD.bazel
+++ b/pkg/planner/core/casetest/physicalplantest/BUILD.bazel
@@ -21,6 +21,7 @@ go_test(
         "//pkg/parser/model",
         "//pkg/planner",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/util/coretestsdk",
         "//pkg/testkit",
         "//pkg/testkit/external",
diff --git a/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go b/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go
index 65e9bd4148..ec027c7333 100644
--- a/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go
+++ b/pkg/planner/core/casetest/physicalplantest/physical_plan_test.go
@@ -30,6 +30,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/util/coretestsdk"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/testkit/external"
@@ -73,7 +74,8 @@ func TestRefine(t *testing.T) {
 		require.NoError(t, err, comment)
 		sc := tk.Session().GetSessionVars().StmtCtx
 		sc.SetTypeFlags(sc.TypeFlags().WithIgnoreTruncateErr(false))
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -106,7 +108,8 @@ func TestAggEliminator(t *testing.T) {
 		require.NoError(t, err, comment)
 		sc := tk.Session().GetSessionVars().StmtCtx
 		sc.SetTypeFlags(sc.TypeFlags().WithIgnoreTruncateErr(false))
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -135,7 +138,8 @@ func TestRuleColumnPruningLogicalApply(t *testing.T) {
 		comment := fmt.Sprintf("input: %s", tt)
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -164,7 +168,8 @@ func TestSemiJoinToInner(t *testing.T) {
 	for i, tt := range input {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -191,7 +196,8 @@ func TestUnmatchedTableInHint(t *testing.T) {
 		tk.Session().GetSessionVars().StmtCtx.SetWarnings(nil)
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err)
-		_, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		_, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		warnings := tk.Session().GetSessionVars().StmtCtx.GetWarnings()
 		testdata.OnRecord(func() {
@@ -633,7 +639,8 @@ func TestHintScope(t *testing.T) {
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(context.Background(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.Background(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = test
@@ -671,7 +678,8 @@ func TestJoinHints(t *testing.T) {
 		require.NoError(t, err, comment)
 
 		tk.Session().GetSessionVars().StmtCtx.SetWarnings(nil)
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		warnings := tk.Session().GetSessionVars().StmtCtx.GetWarnings()
 
@@ -733,7 +741,8 @@ func TestAggregationHints(t *testing.T) {
 		stmt, err := p.ParseOneStmt(test.SQL, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		warnings := tk.Session().GetSessionVars().StmtCtx.GetWarnings()
 
@@ -784,7 +793,8 @@ func TestSemiJoinRewriteHints(t *testing.T) {
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err, comment)
 
-		_, _, err = planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		_, _, err = planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		warnings := tk.Session().GetSessionVars().StmtCtx.GetWarnings()
 
@@ -839,7 +849,8 @@ func TestAggToCopHint(t *testing.T) {
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		planString := core.ToString(p)
 		testdata.OnRecord(func() {
@@ -932,7 +943,8 @@ func TestIndexHint(t *testing.T) {
 		stmt, err := p.ParseOneStmt(test, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = test
@@ -986,7 +998,8 @@ func TestIndexMergeHint(t *testing.T) {
 		sctx := tk.Session()
 		err = executor.ResetContextOfStmt(sctx, stmt)
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		testdata.OnRecord(func() {
 			output[i].SQL = test
@@ -1036,7 +1049,8 @@ func TestQueryBlockHint(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -1082,7 +1096,8 @@ func TestInlineProjection(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -1139,7 +1154,8 @@ func TestIndexJoinHint(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql: %s", i, tt)
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
@@ -1178,7 +1194,8 @@ func TestHintFromDiffDatabase(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql: %s", i, tt)
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(ctx, tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(ctx, tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		testdata.OnRecord(func() {
 			output[i].SQL = tt
diff --git a/pkg/planner/core/casetest/planstats/BUILD.bazel b/pkg/planner/core/casetest/planstats/BUILD.bazel
index 8d738edc3b..3a67afd4fb 100644
--- a/pkg/planner/core/casetest/planstats/BUILD.bazel
+++ b/pkg/planner/core/casetest/planstats/BUILD.bazel
@@ -19,6 +19,7 @@ go_test(
         "//pkg/planner",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/sessionctx",
         "//pkg/sessionctx/stmtctx",
         "//pkg/statistics",
diff --git a/pkg/planner/core/casetest/planstats/plan_stats_test.go b/pkg/planner/core/casetest/planstats/plan_stats_test.go
index a7e8367fbb..cdd964173d 100644
--- a/pkg/planner/core/casetest/planstats/plan_stats_test.go
+++ b/pkg/planner/core/casetest/planstats/plan_stats_test.go
@@ -30,6 +30,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessionctx/stmtctx"
 	"github.com/pingcap/tidb/pkg/statistics"
@@ -213,7 +214,8 @@ func TestPlanStatsLoad(t *testing.T) {
 		require.NoError(t, err)
 		err = executor.ResetContextOfStmt(ctx, stmt)
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, is)
 		require.NoError(t, err)
 		tbl, err := is.TableByName(context.Background(), model.NewCIStr("test"), model.NewCIStr("t"))
 		require.NoError(t, err)
@@ -283,7 +285,8 @@ func TestPlanStatsLoadTimeout(t *testing.T) {
 	stmt, err := p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
 	tk.MustExec("set global tidb_stats_load_pseudo_timeout=false")
-	_, _, err = planner.Optimize(context.TODO(), ctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	_, _, err = planner.Optimize(context.TODO(), ctx, nodeW, is)
 	require.Error(t, err) // fail sql for timeout when pseudo=false
 
 	tk.MustExec("set global tidb_stats_load_pseudo_timeout=true")
@@ -296,7 +299,7 @@ func TestPlanStatsLoadTimeout(t *testing.T) {
 	tk.MustExec(sql)
 	failpoint.Disable("github.com/pingcap/tidb/pkg/planner/core/assertSyncWaitFailed")
 
-	plan, _, err := planner.Optimize(context.TODO(), ctx, stmt, is)
+	plan, _, err := planner.Optimize(context.TODO(), ctx, nodeW, is)
 	require.NoError(t, err) // not fail sql for timeout when pseudo=true
 	switch pp := plan.(type) {
 	case *plannercore.PhysicalTableReader:
diff --git a/pkg/planner/core/casetest/stats_test.go b/pkg/planner/core/casetest/stats_test.go
index 10a81f642d..cefca935b0 100644
--- a/pkg/planner/core/casetest/stats_test.go
+++ b/pkg/planner/core/casetest/stats_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
@@ -57,11 +58,12 @@ func TestGroupNDVs(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), tk.Session(), stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
 		tk.Session().GetSessionVars().PlanColumnID.Store(0)
 		builder, _ := core.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), ret.InfoSchema, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		p, err = core.LogicalOptimizeTest(ctx, builder.GetOptFlag(), p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
diff --git a/pkg/planner/core/cbo_test.go b/pkg/planner/core/cbo_test.go
index f925c9f621..2f821d8e8b 100644
--- a/pkg/planner/core/cbo_test.go
+++ b/pkg/planner/core/cbo_test.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/stretchr/testify/require"
@@ -140,13 +141,14 @@ func BenchmarkOptimize(b *testing.B) {
 		require.Len(b, stmts, 1)
 		stmt := stmts[0]
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(b, err)
 
 		b.Run(tt.sql, func(b *testing.B) {
 			b.ResetTimer()
 			for i := 0; i < b.N; i++ {
-				_, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+				_, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 				require.NoError(b, err)
 			}
 			b.ReportAllocs()
diff --git a/pkg/planner/core/collect_column_stats_usage_test.go b/pkg/planner/core/collect_column_stats_usage_test.go
index 7f027f8a51..e316eac2ab 100644
--- a/pkg/planner/core/collect_column_stats_usage_test.go
+++ b/pkg/planner/core/collect_column_stats_usage_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/infoschema"
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/util/hint"
 	"github.com/stretchr/testify/require"
@@ -112,10 +113,11 @@ func TestSkipSystemTables(t *testing.T) {
 	ctx := context.Background()
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 	require.NoError(t, err)
 	builder, _ := NewPlanBuilder().Init(s.ctx, s.is, hint.NewQBHintHandler(nil))
-	p, err := builder.Build(ctx, stmt)
+	p, err := builder.Build(ctx, nodeW)
 	require.NoError(t, err)
 	lp, ok := p.(base.LogicalPlan)
 	require.True(t, ok)
@@ -302,10 +304,11 @@ func TestCollectPredicateColumns(t *testing.T) {
 		}
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err, comment)
 		builder, _ := NewPlanBuilder().Init(s.ctx, s.is, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		lp, ok := p.(base.LogicalPlan)
 		require.True(t, ok, comment)
@@ -387,10 +390,11 @@ func TestCollectHistNeededColumns(t *testing.T) {
 		}
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err, comment)
 		builder, _ := NewPlanBuilder().Init(s.ctx, s.is, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		lp, ok := p.(base.LogicalPlan)
 		require.True(t, ok, comment)
diff --git a/pkg/planner/core/indexmerge_path_test.go b/pkg/planner/core/indexmerge_path_test.go
index 9056baeb83..517ccddc2f 100644
--- a/pkg/planner/core/indexmerge_path_test.go
+++ b/pkg/planner/core/indexmerge_path_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/util/hint"
@@ -56,11 +57,12 @@ func TestCollectFilters4MVIndexMutations(t *testing.T) {
 	require.NoError(t, err)
 	tk.Session().GetSessionVars().PlanID.Store(0)
 	tk.Session().GetSessionVars().PlanColumnID.Store(0)
-	err = core.Preprocess(context.Background(), tk.Session(), stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
 	require.NoError(t, err)
 	require.NoError(t, sessiontxn.GetTxnManager(tk.Session()).AdviseWarmup())
 	builder, _ := core.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), is, hint.NewQBHintHandler(nil))
-	p, err := builder.Build(context.TODO(), stmt)
+	p, err := builder.Build(context.TODO(), nodeW)
 	require.NoError(t, err)
 	logicalP, err := core.LogicalOptimizeTest(context.TODO(), builder.GetOptFlag(), p.(base.LogicalPlan))
 	require.NoError(t, err)
diff --git a/pkg/planner/core/indexmerge_test.go b/pkg/planner/core/indexmerge_test.go
index 5760f084d5..7d495c24ec 100644
--- a/pkg/planner/core/indexmerge_test.go
+++ b/pkg/planner/core/indexmerge_test.go
@@ -25,6 +25,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/testkit/testdata"
 	"github.com/pingcap/tidb/pkg/util/hint"
@@ -84,11 +85,12 @@ func TestIndexMergePathGeneration(t *testing.T) {
 	for i, tc := range input {
 		stmt, err := parser.ParseOneStmt(tc, "", "")
 		require.NoErrorf(t, err, "case:%v sql:%s", i, tc)
-		err = Preprocess(context.Background(), sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, is, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		if err != nil {
 			testdata.OnRecord(func() {
 				output[i] = err.Error()
diff --git a/pkg/planner/core/issuetest/BUILD.bazel b/pkg/planner/core/issuetest/BUILD.bazel
index 46b2ea56a7..2fa7a2e123 100644
--- a/pkg/planner/core/issuetest/BUILD.bazel
+++ b/pkg/planner/core/issuetest/BUILD.bazel
@@ -16,6 +16,7 @@ go_test(
         "//pkg/planner",
         "//pkg/planner/core",
         "//pkg/planner/core/base",
+        "//pkg/planner/core/resolve",
         "//pkg/testkit",
         "//pkg/testkit/testdata",
         "//pkg/testkit/testmain",
diff --git a/pkg/planner/core/issuetest/planner_issue_test.go b/pkg/planner/core/issuetest/planner_issue_test.go
index 724a00361a..0139fb7b27 100644
--- a/pkg/planner/core/issuetest/planner_issue_test.go
+++ b/pkg/planner/core/issuetest/planner_issue_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/stretchr/testify/require"
 )
@@ -37,7 +38,8 @@ func TestIssue43461(t *testing.T) {
 	stmt, err := parser.New().ParseOneStmt("select * from t use index(b) where b > 1 order by b limit 1", "", "")
 	require.NoError(t, err)
 
-	p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, domain.InfoSchema())
+	nodeW := resolve.NewNodeW(stmt)
+	p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, domain.InfoSchema())
 	require.NoError(t, err)
 	require.NotNil(t, p)
 
diff --git a/pkg/planner/core/logical_plan_trace_test.go b/pkg/planner/core/logical_plan_trace_test.go
index 36774d60e7..3cfcc594a6 100644
--- a/pkg/planner/core/logical_plan_trace_test.go
+++ b/pkg/planner/core/logical_plan_trace_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/config"
 	"github.com/pingcap/tidb/pkg/domain"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/util/hint"
 	"github.com/stretchr/testify/require"
@@ -408,7 +409,8 @@ func TestSingleRuleTraceStep(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, sql)
 		stmt, err := s.p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err, comment)
 		sctx := MockContext()
 		sctx.GetSessionVars().StmtCtx.EnableOptimizeTrace = true
@@ -416,7 +418,7 @@ func TestSingleRuleTraceStep(t *testing.T) {
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
 		ctx := context.TODO()
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		flag := uint64(0)
 		for _, f := range tc.flags {
diff --git a/pkg/planner/core/logical_plans_test.go b/pkg/planner/core/logical_plans_test.go
index 3cb0af0b48..6c8aac29b6 100644
--- a/pkg/planner/core/logical_plans_test.go
+++ b/pkg/planner/core/logical_plans_test.go
@@ -34,6 +34,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/core/rule"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
@@ -139,7 +140,8 @@ func TestPredicatePushDown(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca)
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		p, err = logicalOptimize(context.TODO(), rule.FlagConvertOuterToInnerJoin|rule.FlagPredicatePushDown|rule.FlagDecorrelate|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -159,7 +161,8 @@ func TestImplicitCastNotNullFlag(t *testing.T) {
 	defer s.Close()
 	stmt, err := s.p.ParseOneStmt(ca, "", "")
 	require.NoError(t, err, comment)
-	p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 	require.NoError(t, err)
 	p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagJoinReOrder|rule.FlagPruneColumns|rule.FlagEliminateProjection, p.(base.LogicalPlan))
 	require.NoError(t, err)
@@ -177,7 +180,8 @@ func TestEliminateProjectionUnderUnion(t *testing.T) {
 	defer s.Close()
 	stmt, err := s.p.ParseOneStmt(ca, "", "")
 	require.NoError(t, err, comment)
-	p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 	require.NoError(t, err)
 	p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagJoinReOrder|rule.FlagPruneColumns|rule.FlagEliminateProjection, p.(base.LogicalPlan))
 	require.NoError(t, err)
@@ -205,7 +209,8 @@ func TestJoinPredicatePushDown(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca)
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagDecorrelate|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -246,7 +251,8 @@ func TestOuterWherePredicatePushDown(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca)
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagDecorrelate|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -292,7 +298,8 @@ func TestSimplifyOuterJoin(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca)
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagConvertOuterToInnerJoin, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -333,7 +340,8 @@ func TestAntiSemiJoinConstFalse(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca.sql)
 		stmt, err := s.p.ParseOneStmt(ca.sql, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagDecorrelate|rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -362,7 +370,8 @@ func TestDeriveNotNullConds(t *testing.T) {
 		comment := fmt.Sprintf("for %s", ca)
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagDecorrelate, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -391,7 +400,8 @@ func TestExtraPKNotNullFlag(t *testing.T) {
 	comment := fmt.Sprintf("for %s", sql)
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err, comment)
-	p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 	require.NoError(t, err, comment)
 	ds := p.(*logicalop.LogicalProjection).Children()[0].(*logicalop.LogicalAggregation).Children()[0].(*DataSource)
 	require.Equal(t, "_tidb_rowid", ds.Columns[2].Name.L)
@@ -410,9 +420,13 @@ func buildLogicPlan4GroupBy(s *plannerSuite, t *testing.T, sql string) (base.Pla
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err, comment)
 
-	stmt.(*ast.SelectStmt).From.TableRefs.Left.(*ast.TableSource).Source.(*ast.TableName).TableInfo = mockedTableInfo
-
-	p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	tn := stmt.(*ast.SelectStmt).From.TableRefs.Left.(*ast.TableSource).Source.(*ast.TableName)
+	nodeW.GetResolveContext().AddTableName(&resolve.TableNameW{
+		TableName: tn,
+		TableInfo: mockedTableInfo,
+	})
+	p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, nodeW, s.is)
 	return p, err
 }
 
@@ -474,7 +488,8 @@ func TestDupRandJoinCondsPushDown(t *testing.T) {
 	defer s.Close()
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err, comment)
-	p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, nodeW, s.is)
 	require.NoError(t, err, comment)
 	p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown, p.(base.LogicalPlan))
 	require.NoError(t, err, comment)
@@ -544,7 +559,8 @@ func TestTablePartition(t *testing.T) {
 		testdata.OnRecord(func() {
 
 		})
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, isChoices[ca.IsIdx])
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, isChoices[ca.IsIdx])
 		require.NoError(t, err)
 		p, err = logicalOptimize(context.TODO(), rule.FlagDecorrelate|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagPredicatePushDown|rule.FlagPartitionProcessor, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -568,9 +584,10 @@ func TestSubquery(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(ca, "", "")
 		require.NoError(t, err, comment)
 
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		if lp, ok := p.(base.LogicalPlan); ok {
 			p, err = logicalOptimize(context.TODO(), rule.FlagBuildKeyInfo|rule.FlagDecorrelate|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagSemiJoinRewrite, lp)
@@ -597,9 +614,10 @@ func TestPlanBuilder(t *testing.T) {
 		require.NoError(t, err, comment)
 
 		s.ctx.GetSessionVars().SetHashJoinConcurrency(1)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		if lp, ok := p.(base.LogicalPlan); ok {
 			p, err = logicalOptimize(context.TODO(), rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, lp)
@@ -624,7 +642,8 @@ func TestJoinReOrder(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagJoinReOrder, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -653,7 +672,8 @@ func TestEagerAggregation(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		p, err = logicalOptimize(context.TODO(), rule.FlagBuildKeyInfo|rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagPushDownAgg, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -679,7 +699,8 @@ func TestColumnPruning(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		lp, err := logicalOptimize(ctx, rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -708,7 +729,8 @@ func TestSortByItemsPruning(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		lp, err := logicalOptimize(ctx, rule.FlagEliminateProjection|rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -738,7 +760,8 @@ func TestProjectionEliminator(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		p, err = logicalOptimize(context.TODO(), rule.FlagBuildKeyInfo|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagEliminateProjection, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -752,7 +775,8 @@ func TestCS3389(t *testing.T) {
 	ctx := context.Background()
 	stmt, err := s.p.ParseOneStmt("select count(*) from t where a in (select b from t2 where  a is null);", "", "")
 	require.NoError(t, err)
-	p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 	require.NoError(t, err)
 	p, err = logicalOptimize(context.TODO(), rule.FlagBuildKeyInfo|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagEliminateProjection|rule.FlagJoinReOrder, p.(base.LogicalPlan))
 	require.NoError(t, err)
@@ -1006,9 +1030,10 @@ func TestValidate(t *testing.T) {
 		comment := fmt.Sprintf("for %s", sql)
 		stmt, err := s.p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
-		_, err = BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		_, err = BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		if tt.err == nil {
 			require.NoError(t, err, comment)
 		} else {
@@ -1061,7 +1086,8 @@ func TestUniqueKeyInfo(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		lp, err := logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagBuildKeyInfo, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -1084,7 +1110,8 @@ func TestAggPrune(t *testing.T) {
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
 		domain.GetDomain(s.ctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 
 		p, err = logicalOptimize(context.TODO(), rule.FlagPredicatePushDown|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain|rule.FlagBuildKeyInfo|rule.FlagEliminateAgg|rule.FlagEliminateProjection, p.(base.LogicalPlan))
@@ -1510,12 +1537,13 @@ func TestVisitInfo(t *testing.T) {
 		require.NoError(t, err, comment)
 
 		// TODO: to fix, Table 'test.ttt' doesn't exist
-		_ = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		_ = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
 		builder.ctx.GetSessionVars().SetHashJoinConcurrency(1)
-		_, err = builder.Build(context.TODO(), stmt)
+		_, err = builder.Build(context.TODO(), nodeW)
 		require.NoError(t, err, comment)
 
 		checkVisitInfo(t, builder.visitInfo, tt.ans, comment)
@@ -1593,12 +1621,13 @@ func TestUnion(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt)
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		plan, err := builder.Build(ctx, stmt)
+		plan, err := builder.Build(ctx, nodeW)
 		testdata.OnRecord(func() {
 			output[i].Err = err != nil
 		})
@@ -1635,12 +1664,13 @@ func TestTopNPushDown(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt)
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -1696,7 +1726,8 @@ func TestNameResolver(t *testing.T) {
 		require.NoError(t, err, comment)
 		s.ctx.GetSessionVars().SetHashJoinConcurrency(1)
 
-		_, err = BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		_, err = BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		if test.err == "" {
 			require.NoError(t, err)
 		} else {
@@ -1722,12 +1753,13 @@ func TestOuterJoinEliminator(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt)
 		stmt, err := s.p.ParseOneStmt(tt, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -1762,11 +1794,12 @@ func TestSelectView(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt.sql)
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -1860,7 +1893,8 @@ func (s *plannerSuiteWithOptimizeVars) optimize(ctx context.Context, sql string)
 	if err != nil {
 		return nil, nil, err
 	}
-	err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 	if err != nil {
 		return nil, nil, err
 	}
@@ -1876,7 +1910,7 @@ func (s *plannerSuiteWithOptimizeVars) optimize(ctx context.Context, sql string)
 	}
 	builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-	p, err := builder.Build(ctx, stmt)
+	p, err := builder.Build(ctx, nodeW)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -1973,12 +2007,13 @@ func TestSkylinePruning(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt.sql)
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		if err != nil {
 			require.EqualError(t, err, tt.result, comment)
 			domain.GetDomain(sctx).StatsHandle().Close()
@@ -2049,10 +2084,11 @@ func TestFastPlanContextTables(t *testing.T) {
 	for _, tt := range tests {
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		s.ctx.GetSessionVars().StmtCtx.Tables = nil
-		p := TryFastPlan(s.ctx, stmt)
+		p := TryFastPlan(s.ctx, nodeW)
 		if tt.fastPlan {
 			require.NotNil(t, p)
 			require.Equal(t, 1, len(s.ctx.GetSessionVars().StmtCtx.Tables))
@@ -2082,12 +2118,13 @@ func TestUpdateEQCond(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt.sql)
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err)
 		sctx := MockContext()
 		builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -2103,7 +2140,8 @@ func TestConflictedJoinTypeHints(t *testing.T) {
 	ctx := context.TODO()
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 	require.NoError(t, err)
 	sctx := MockContext()
 	defer func() {
@@ -2111,7 +2149,7 @@ func TestConflictedJoinTypeHints(t *testing.T) {
 	}()
 	builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-	p, err := builder.Build(ctx, stmt)
+	p, err := builder.Build(ctx, nodeW)
 	require.NoError(t, err)
 	p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 	require.NoError(t, err)
@@ -2130,7 +2168,8 @@ func TestSimplyOuterJoinWithOnlyOuterExpr(t *testing.T) {
 	ctx := context.TODO()
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 	require.NoError(t, err)
 	sctx := MockContext()
 	defer func() {
@@ -2138,7 +2177,7 @@ func TestSimplyOuterJoinWithOnlyOuterExpr(t *testing.T) {
 	}()
 	builder, _ := NewPlanBuilder().Init(sctx, s.is, hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(s.is)
-	p, err := builder.Build(ctx, stmt)
+	p, err := builder.Build(ctx, nodeW)
 	require.NoError(t, err)
 	p, err = logicalOptimize(ctx, builder.optFlag, p.(base.LogicalPlan))
 	require.NoError(t, err)
@@ -2188,9 +2227,10 @@ func TestResolvingCorrelatedAggregate(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt.sql)
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		p, err = logicalOptimize(context.TODO(), rule.FlagBuildKeyInfo|rule.FlagEliminateProjection|rule.FlagPruneColumns|rule.FlagPruneColumnsAgain, p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -2231,9 +2271,10 @@ func TestFastPathInvalidBatchPointGet(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tc.sql)
 		stmt, err := s.p.ParseOneStmt(tc.sql, "", "")
 		require.NoError(t, err, comment)
-		err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 		require.NoError(t, err, comment)
-		plan := TryFastPlan(s.ctx, stmt)
+		plan := TryFastPlan(s.ctx, nodeW)
 		if tc.fastPlan {
 			require.NotNil(t, plan)
 		} else {
@@ -2254,9 +2295,10 @@ func TestTraceFastPlan(t *testing.T) {
 	comment := fmt.Sprintf("sql:%s", sql)
 	stmt, err := s.p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err, comment)
-	err = Preprocess(context.Background(), s.sctx, stmt, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = Preprocess(context.Background(), s.sctx, nodeW, WithPreprocessorReturn(&PreprocessorReturn{InfoSchema: s.is}))
 	require.NoError(t, err, comment)
-	plan := TryFastPlan(s.ctx, stmt)
+	plan := TryFastPlan(s.ctx, nodeW)
 	require.NotNil(t, plan)
 	require.NotNil(t, s.ctx.GetSessionVars().StmtCtx.OptimizeTracer)
 	require.NotNil(t, s.ctx.GetSessionVars().StmtCtx.OptimizeTracer.FinalPlan)
@@ -2273,7 +2315,8 @@ func TestWindowLogicalPlanAmbiguous(t *testing.T) {
 	for i := 0; i < iterations; i++ {
 		stmt, err := s.p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
-		p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(context.Background(), s.sctx, nodeW, s.is)
 		require.NoError(t, err)
 		if planString == "" {
 			planString = ToString(p)
@@ -2314,7 +2357,8 @@ func TestRemoveOrderbyInSubquery(t *testing.T) {
 		comment := fmt.Sprintf("case:%v sql:%s", i, tt.sql)
 		stmt, err := s.p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		p, err := BuildLogicalPlanForTest(ctx, s.sctx, stmt, s.is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := BuildLogicalPlanForTest(ctx, s.sctx, nodeW, s.is)
 		require.NoError(t, err, comment)
 		require.Equal(t, tt.best, ToString(p), comment)
 	}
@@ -2333,7 +2377,8 @@ func TestRollupExpand(t *testing.T) {
 	s.ctx.GetSessionVars().PlanID.Store(0)
 	s.ctx.GetSessionVars().PlanColumnID.Store(0)
 	builder, _ := NewPlanBuilder().Init(s.ctx, s.is, hint.NewQBHintHandler(nil))
-	p, err := builder.Build(ctx, stmt)
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := builder.Build(ctx, nodeW)
 	require.NoError(t, err)
 
 	// fetch the current
diff --git a/pkg/planner/core/memtable_infoschema_extractor_test.go b/pkg/planner/core/memtable_infoschema_extractor_test.go
index aa7545ee02..bcc61d1e68 100644
--- a/pkg/planner/core/memtable_infoschema_extractor_test.go
+++ b/pkg/planner/core/memtable_infoschema_extractor_test.go
@@ -338,11 +338,42 @@ func cleanDataColumns(tk *testkit.TestKit) {
 	tk.MustExec("drop database schema_columns2")
 }
 
-func TestMemtableInfoschemaExtractor(t *testing.T) {
+func testMemtableInfoschemaExtractor(t *testing.T, tcs []testCase) {
 	store := testkit.CreateMockStore(t)
 	tk := testkit.NewTestKit(t, store)
 
 	tk.MustExec("set global tidb_enable_check_constraint = true")
+	countSQL := 0
+	for _, tc := range tcs {
+		names := tc.prepareData(tk)
+		conditions := []string{}
+		for i := len(names) - 1; i >= 0; i-- {
+			all := names[i].enumerateAll()
+			if len(conditions) == 0 {
+				conditions = all
+			} else {
+				newConditions := []string{}
+				for _, a := range all {
+					for _, b := range conditions {
+						newConditions = append(newConditions, fmt.Sprintf("%s and %s", a, b))
+					}
+				}
+				conditions = newConditions
+			}
+		}
+
+		countSQL += len(conditions)
+		for _, c := range conditions {
+			sql := fmt.Sprintf("select * from information_schema.%s where %s", tc.memTableName, c)
+			tk.MustQuery(sql)
+		}
+
+		tc.cleanData(tk)
+	}
+	fmt.Printf("Total SQLs: %d\n", countSQL)
+}
+
+func TestMemtableInfoschemaExtractorPart1(t *testing.T) {
 	tcs := []testCase{
 		{
 			memTableName: infoschema.TableTiDBIndexes,
@@ -374,6 +405,12 @@ func TestMemtableInfoschemaExtractor(t *testing.T) {
 			prepareData:  prepareDataPartitions,
 			cleanData:    cleanDataPartitions,
 		},
+	}
+	testMemtableInfoschemaExtractor(t, tcs)
+}
+
+func TestMemtableInfoschemaExtractorPart2(t *testing.T) {
+	tcs := []testCase{
 		{
 			memTableName: infoschema.TableStatistics,
 			prepareData:  prepareDataStatistics,
@@ -405,33 +442,5 @@ func TestMemtableInfoschemaExtractor(t *testing.T) {
 			cleanData:    cleanDataStatistics,
 		},
 	}
-
-	countSQL := 0
-	for _, tc := range tcs {
-		names := tc.prepareData(tk)
-		conditions := []string{}
-		for i := len(names) - 1; i >= 0; i-- {
-			all := names[i].enumerateAll()
-			if len(conditions) == 0 {
-				conditions = all
-			} else {
-				newConditions := []string{}
-				for _, a := range all {
-					for _, b := range conditions {
-						newConditions = append(newConditions, fmt.Sprintf("%s and %s", a, b))
-					}
-				}
-				conditions = newConditions
-			}
-		}
-
-		countSQL += len(conditions)
-		for _, c := range conditions {
-			sql := fmt.Sprintf("select * from information_schema.%s where %s", tc.memTableName, c)
-			tk.MustQuery(sql)
-		}
-
-		tc.cleanData(tk)
-	}
-	fmt.Printf("Total SQLs: %d\n", countSQL)
+	testMemtableInfoschemaExtractor(t, tcs)
 }
diff --git a/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel b/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel
index 5abb04dba5..8bb780a67a 100644
--- a/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel
+++ b/pkg/planner/core/operator/logicalop/logicalop_test/BUILD.bazel
@@ -15,6 +15,7 @@ go_test(
         "//pkg/planner/core",
         "//pkg/planner/core/base",
         "//pkg/planner/core/operator/logicalop",
+        "//pkg/planner/core/resolve",
         "//pkg/planner/util",
         "//pkg/session",
         "//pkg/session/types",
diff --git a/pkg/planner/core/operator/logicalop/logicalop_test/logical_mem_table_predicate_extractor_test.go b/pkg/planner/core/operator/logicalop/logicalop_test/logical_mem_table_predicate_extractor_test.go
index 640a486df9..e5a7306424 100644
--- a/pkg/planner/core/operator/logicalop/logicalop_test/logical_mem_table_predicate_extractor_test.go
+++ b/pkg/planner/core/operator/logicalop/logicalop_test/logical_mem_table_predicate_extractor_test.go
@@ -32,6 +32,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/session"
 	sessiontypes "github.com/pingcap/tidb/pkg/session/types"
@@ -48,7 +49,8 @@ func getLogicalMemTable(t *testing.T, dom *domain.Domain, se sessiontypes.Sessio
 
 	ctx := context.Background()
 	builder, _ := plannercore.NewPlanBuilder().Init(se.GetPlanCtx(), dom.InfoSchema(), hint.NewQBHintHandler(nil))
-	plan, err := builder.Build(ctx, stmt)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, err := builder.Build(ctx, nodeW)
 	require.NoError(t, err)
 
 	logicalPlan, err := plannercore.LogicalOptimizeTest(ctx, builder.GetOptFlag(), plan.(base.LogicalPlan))
@@ -1800,7 +1802,8 @@ func TestExtractorInPreparedStmt(t *testing.T) {
 		tk.MustExec(setStmt)
 		stmt, err := parser.ParseOneStmt(exec, "", "")
 		require.NoError(t, err)
-		plan, _, err := planner.OptimizeExecStmt(context.Background(), tk.Session(), stmt.(*ast.ExecuteStmt), dom.InfoSchema())
+		nodeW := resolve.NewNodeW(stmt)
+		plan, _, err := planner.OptimizeExecStmt(context.Background(), tk.Session(), nodeW, dom.InfoSchema())
 		require.NoError(t, err)
 		extractor := plan.(*plannercore.Execute).Plan.(*plannercore.PhysicalMemTable).Extractor
 		ca.checker(extractor)
@@ -1817,7 +1820,8 @@ func TestExtractorInPreparedStmt(t *testing.T) {
 			BinaryArgs: params,
 			PrepStmt:   prepStmt,
 		}
-		plan, _, err := planner.OptimizeExecStmt(context.Background(), tk.Session(), execStmt, dom.InfoSchema())
+		nodeW := resolve.NewNodeW(execStmt)
+		plan, _, err := planner.OptimizeExecStmt(context.Background(), tk.Session(), nodeW, dom.InfoSchema())
 		require.NoError(t, err)
 		extractor := plan.(*plannercore.Execute).Plan.(*plannercore.PhysicalMemTable).Extractor
 		ca.checker(extractor)
diff --git a/pkg/planner/core/physical_plan_test.go b/pkg/planner/core/physical_plan_test.go
index 16861bc316..c28a465567 100644
--- a/pkg/planner/core/physical_plan_test.go
+++ b/pkg/planner/core/physical_plan_test.go
@@ -32,6 +32,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/planner/util/coretestsdk"
@@ -92,9 +93,10 @@ func TestAnalyzeBuildSucc(t *testing.T) {
 		} else if err != nil {
 			continue
 		}
-		err = core.Preprocess(context.Background(), tk.Session(), stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err)
-		_, _, err = planner.Optimize(context.Background(), tk.Session(), stmt, is)
+		_, _, err = planner.Optimize(context.Background(), tk.Session(), nodeW, is)
 		if tt.succ {
 			require.NoError(t, err, comment)
 		} else {
@@ -134,9 +136,10 @@ func TestAnalyzeSetRate(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
 
-		err = core.Preprocess(context.Background(), tk.Session(), stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(context.Background(), tk.Session(), stmt, is)
+		p, _, err := planner.Optimize(context.Background(), tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 		ana := p.(*core.Analyze)
 		require.Equal(t, tt.rate, math.Float64frombits(ana.Opts[ast.AnalyzeOptSampleRate]))
@@ -169,7 +172,8 @@ func TestRequestTypeSupportedOff(t *testing.T) {
 	is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
 	stmt, err := parser.New().ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	p, _, err := planner.Optimize(context.TODO(), se, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, _, err := planner.Optimize(context.TODO(), se, nodeW, is)
 	require.NoError(t, err)
 	require.Equal(t, expect, core.ToString(p), fmt.Sprintf("sql: %s", sql))
 }
@@ -197,7 +201,8 @@ func TestDoSubQuery(t *testing.T) {
 		comment := fmt.Sprintf("for %s", tt.sql)
 		stmt, err := p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		nodeW := resolve.NewNodeW(stmt)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err)
 		require.Equal(t, tt.best, core.ToString(p), comment)
 	}
@@ -212,7 +217,8 @@ func TestIndexLookupCartesianJoin(t *testing.T) {
 	require.NoError(t, err)
 
 	is := infoschema.MockInfoSchema([]*model.TableInfo{core.MockSignedTable(), core.MockUnsignedTable()})
-	p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 	require.NoError(t, err)
 	require.Equal(t, "LeftHashJoin{TableReader(Table(t))->TableReader(Table(t))}", core.ToString(p))
 
@@ -351,9 +357,11 @@ func TestHintAlias(t *testing.T) {
 		stmt2, err := p.ParseOneStmt(tt.sql2, "", "")
 		require.NoError(t, err, comment)
 
-		p1, _, err := planner.Optimize(ctx, tk.Session(), stmt1, is)
+		nodeW1 := resolve.NewNodeW(stmt1)
+		p1, _, err := planner.Optimize(ctx, tk.Session(), nodeW1, is)
 		require.NoError(t, err)
-		p2, _, err := planner.Optimize(ctx, tk.Session(), stmt2, is)
+		nodeW2 := resolve.NewNodeW(stmt2)
+		p2, _, err := planner.Optimize(ctx, tk.Session(), nodeW2, is)
 		require.NoError(t, err)
 
 		require.Equal(t, core.ToString(p2), core.ToString(p1))
@@ -388,9 +396,10 @@ func TestDAGPlanBuilderSplitAvg(t *testing.T) {
 		stmt, err := p.ParseOneStmt(tt.sql, "", "")
 		require.NoError(t, err, comment)
 
-		err = core.Preprocess(context.Background(), tk.Session(), stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), tk.Session(), nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+		p, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 		require.NoError(t, err, comment)
 
 		require.Equal(t, tt.plan, core.ToString(p), comment)
diff --git a/pkg/planner/core/physical_plan_trace_test.go b/pkg/planner/core/physical_plan_trace_test.go
index 6e1ba8653f..a98d54685c 100644
--- a/pkg/planner/core/physical_plan_trace_test.go
+++ b/pkg/planner/core/physical_plan_trace_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/util/hint"
@@ -91,14 +92,15 @@ func TestPhysicalOptimizeWithTraceEnabled(t *testing.T) {
 		sql := testcase.sql
 		stmt, err := p.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
 		require.NoError(t, err)
 		sctx := core.MockContext()
 		sctx.GetSessionVars().StmtCtx.EnableOptimizeTrace = true
 		sctx.GetSessionVars().CostModelVersion = 2
 		builder, _ := core.NewPlanBuilder().Init(sctx, dom.InfoSchema(), hint.NewQBHintHandler(nil))
 		domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(dom.InfoSchema())
-		plan, err := builder.Build(context.TODO(), stmt)
+		plan, err := builder.Build(context.TODO(), nodeW)
 		require.NoError(t, err)
 		_, _, err = core.DoOptimize(context.TODO(), sctx, builder.GetOptFlag(), plan.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -147,7 +149,8 @@ func TestPhysicalOptimizerTrace(t *testing.T) {
 
 	stmt, err := p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
 	require.NoError(t, err)
 	sctx := core.MockContext()
 	defer func() {
@@ -157,7 +160,7 @@ func TestPhysicalOptimizerTrace(t *testing.T) {
 	sctx.GetSessionVars().AllowAggPushDown = true
 	builder, _ := core.NewPlanBuilder().Init(sctx, dom.InfoSchema(), hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(dom.InfoSchema())
-	plan, err := builder.Build(context.TODO(), stmt)
+	plan, err := builder.Build(context.TODO(), nodeW)
 	require.NoError(t, err)
 	flag := uint64(0)
 	// flagGcSubstitute | flagStabilizeResults | flagSkewDistinctAgg | flagEliminateOuterJoin | flagPushDownAgg
@@ -214,7 +217,8 @@ func TestPhysicalOptimizerTraceChildrenNotDuplicated(t *testing.T) {
 	sql := "select * from t"
 	stmt, err := p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
 	require.NoError(t, err)
 	sctx := core.MockContext()
 	defer func() {
@@ -223,7 +227,7 @@ func TestPhysicalOptimizerTraceChildrenNotDuplicated(t *testing.T) {
 	sctx.GetSessionVars().StmtCtx.EnableOptimizeTrace = true
 	builder, _ := core.NewPlanBuilder().Init(sctx, dom.InfoSchema(), hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(dom.InfoSchema())
-	plan, err := builder.Build(context.TODO(), stmt)
+	plan, err := builder.Build(context.TODO(), nodeW)
 	require.NoError(t, err)
 	_, _, err = core.DoOptimize(context.TODO(), sctx, builder.GetOptFlag(), plan.(base.LogicalPlan))
 	require.NoError(t, err)
diff --git a/pkg/planner/core/plan_cache_test.go b/pkg/planner/core/plan_cache_test.go
index 72ea4e2fc7..d9aaa70cb9 100644
--- a/pkg/planner/core/plan_cache_test.go
+++ b/pkg/planner/core/plan_cache_test.go
@@ -31,6 +31,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/planner"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -65,9 +66,10 @@ func TestNonPreparedPlanCachePlanString(t *testing.T) {
 		require.NoError(t, err)
 		stmt := stmts[0]
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), ctx, stmt, plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), ctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		return plannercore.ToString(p)
 	}
@@ -107,11 +109,12 @@ func TestNonPreparedPlanCacheInformationSchema(t *testing.T) {
 
 	stmt, err := p.ParseOneStmt("select avg(a),avg(b),avg(c) from t", "", "")
 	require.NoError(t, err)
-	err = plannercore.Preprocess(context.Background(), tk.Session(), stmt, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = plannercore.Preprocess(context.Background(), tk.Session(), nodeW, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
 	require.NoError(t, err) // no error
-	_, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+	_, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 	require.NoError(t, err) // no error
-	_, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, is)
+	_, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, is)
 	require.NoError(t, err) // no error
 	require.True(t, tk.Session().GetSessionVars().FoundInPlanCache)
 }
@@ -1152,9 +1155,10 @@ func TestNonPreparedPlanCachePanic(t *testing.T) {
 		stmtNode, err := s.ParseOneStmt(sql, "", "")
 		require.NoError(t, err)
 		preprocessorReturn := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), ctx, stmtNode, plannercore.WithPreprocessorReturn(preprocessorReturn))
+		nodeW := resolve.NewNodeW(stmtNode)
+		err = plannercore.Preprocess(context.Background(), ctx, nodeW, plannercore.WithPreprocessorReturn(preprocessorReturn))
 		require.NoError(t, err)
-		_, _, err = planner.Optimize(context.TODO(), ctx, stmtNode, preprocessorReturn.InfoSchema)
+		_, _, err = planner.Optimize(context.TODO(), ctx, nodeW, preprocessorReturn.InfoSchema)
 		require.NoError(t, err) // not panic
 	}
 }
diff --git a/pkg/planner/core/plan_cost_detail_test.go b/pkg/planner/core/plan_cost_detail_test.go
index 0c7804db8b..55fb24daea 100644
--- a/pkg/planner/core/plan_cost_detail_test.go
+++ b/pkg/planner/core/plan_cost_detail_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/util/hint"
@@ -134,7 +135,8 @@ func TestPlanCostDetail(t *testing.T) {
 func optimize(t *testing.T, sql string, p *parser.Parser, ctx sessionctx.Context, dom *domain.Domain) map[string]*tracing.PhysicalPlanCostDetail {
 	stmt, err := p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = plannercore.Preprocess(context.Background(), ctx, stmt, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = plannercore.Preprocess(context.Background(), ctx, nodeW, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
 	require.NoError(t, err)
 	sctx := plannercore.MockContext()
 	defer func() {
@@ -145,7 +147,7 @@ func optimize(t *testing.T, sql string, p *parser.Parser, ctx sessionctx.Context
 	sctx.GetSessionVars().CostModelVersion = 1
 	builder, _ := plannercore.NewPlanBuilder().Init(sctx, dom.InfoSchema(), hint.NewQBHintHandler(nil))
 	domain.GetDomain(sctx).MockInfoCacheAndLoadInfoSchema(dom.InfoSchema())
-	plan, err := builder.Build(context.TODO(), stmt)
+	plan, err := builder.Build(context.TODO(), nodeW)
 	require.NoError(t, err)
 	_, _, err = plannercore.DoOptimize(context.TODO(), sctx, builder.GetOptFlag(), plan.(base.LogicalPlan))
 	require.NoError(t, err)
diff --git a/pkg/planner/core/plan_cost_ver2_test.go b/pkg/planner/core/plan_cost_ver2_test.go
index 84108a25b4..46403ff09e 100644
--- a/pkg/planner/core/plan_cost_ver2_test.go
+++ b/pkg/planner/core/plan_cost_ver2_test.go
@@ -27,6 +27,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util/costusage"
 	"github.com/pingcap/tidb/pkg/planner/util/optimizetrace"
@@ -145,7 +146,8 @@ func BenchmarkGetPlanCost(b *testing.B) {
 	sctx := tk.Session()
 	sctx.GetSessionVars().CostModelVersion = 2
 	is := sessiontxn.GetTxnManager(sctx).GetTxnInfoSchema()
-	plan, _, err := planner.Optimize(context.TODO(), sctx, stmt, is)
+	nodeW := resolve.NewNodeW(stmt)
+	plan, _, err := planner.Optimize(context.TODO(), sctx, nodeW, is)
 	if err != nil {
 		b.Fatal(err)
 	}
diff --git a/pkg/planner/core/plan_replayer_capture_test.go b/pkg/planner/core/plan_replayer_capture_test.go
index 07b0c7ff89..4c74f41ffa 100644
--- a/pkg/planner/core/plan_replayer_capture_test.go
+++ b/pkg/planner/core/plan_replayer_capture_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/statistics"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -65,7 +66,8 @@ func getTableStats(sql string, t *testing.T, ctx sessionctx.Context, dom *domain
 	p := parser.New()
 	stmt, err := p.ParseOneStmt(sql, "", "")
 	require.NoError(t, err)
-	err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
+	nodeW := resolve.NewNodeW(stmt)
+	err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: dom.InfoSchema()}))
 	require.NoError(t, err)
 	sctx := core.MockContext()
 	sctx.GetSessionVars().EnablePlanReplayerCapture = true
@@ -74,7 +76,7 @@ func getTableStats(sql string, t *testing.T, ctx sessionctx.Context, dom *domain
 	defer func() {
 		domain.GetDomain(sctx).StatsHandle().Close()
 	}()
-	plan, err := builder.Build(context.TODO(), stmt)
+	plan, err := builder.Build(context.TODO(), nodeW)
 	require.NoError(t, err)
 	_, _, err = core.DoOptimize(context.TODO(), sctx, builder.GetOptFlag(), plan.(base.LogicalPlan))
 	require.NoError(t, err)
diff --git a/pkg/planner/core/planbuilder_test.go b/pkg/planner/core/planbuilder_test.go
index 40c0390bba..4019d02d17 100644
--- a/pkg/planner/core/planbuilder_test.go
+++ b/pkg/planner/core/planbuilder_test.go
@@ -32,6 +32,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/model"
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/planner/util"
 	"github.com/pingcap/tidb/pkg/statistics"
@@ -701,12 +702,15 @@ func TestGetFullAnalyzeColumnsInfo(t *testing.T) {
 			FieldType: *types.NewFieldType(mysql.TypeLonglong),
 		},
 	}
-	tableName.TableInfo = &model.TableInfo{
-		Columns: columns,
+	tblNameW := &resolve.TableNameW{
+		TableName: tableName,
+		TableInfo: &model.TableInfo{
+			Columns: columns,
+		},
 	}
 
 	// Test case 1: AllColumns.
-	cols, _, err := pb.getFullAnalyzeColumnsInfo(tableName, model.AllColumns, nil, nil, nil, false, false)
+	cols, _, err := pb.getFullAnalyzeColumnsInfo(tblNameW, model.AllColumns, nil, nil, nil, false, false)
 	require.NoError(t, err)
 	require.Equal(t, columns, cols)
 
@@ -718,7 +722,7 @@ func TestGetFullAnalyzeColumnsInfo(t *testing.T) {
 	// Test case 3: ColumnList.
 	specifiedCols := []*model.ColumnInfo{columns[0], columns[2]}
 	mustAnalyzedCols.data[3] = struct{}{}
-	cols, _, err = pb.getFullAnalyzeColumnsInfo(tableName, model.ColumnList, specifiedCols, nil, mustAnalyzedCols, false, false)
+	cols, _, err = pb.getFullAnalyzeColumnsInfo(tblNameW, model.ColumnList, specifiedCols, nil, mustAnalyzedCols, false, false)
 	require.NoError(t, err)
 	require.Equal(t, specifiedCols, cols)
 }
diff --git a/pkg/planner/core/point_get_plan_test.go b/pkg/planner/core/point_get_plan_test.go
index cde9eb6be3..1b568eed27 100644
--- a/pkg/planner/core/point_get_plan_test.go
+++ b/pkg/planner/core/point_get_plan_test.go
@@ -22,6 +22,7 @@ import (
 	"github.com/pingcap/tidb/pkg/metrics"
 	"github.com/pingcap/tidb/pkg/planner"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -163,9 +164,10 @@ func TestPointGetId(t *testing.T) {
 		require.Len(t, stmts, 1)
 		stmt := stmts[0]
 		ret := &core.PreprocessorReturn{}
-		err = core.Preprocess(context.Background(), ctx, stmt, core.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmt)
+		err = core.Preprocess(context.Background(), ctx, nodeW, core.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, _, err := planner.Optimize(context.TODO(), ctx, stmt, ret.InfoSchema)
+		p, _, err := planner.Optimize(context.TODO(), ctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		// Test explain format = 'brief' result is useless, plan id will be reset when running `explain`.
 		require.Equal(t, 1, p.ID())
diff --git a/pkg/planner/core/preprocess_test.go b/pkg/planner/core/preprocess_test.go
index c508cda3d1..99b9016cd6 100644
--- a/pkg/planner/core/preprocess_test.go
+++ b/pkg/planner/core/preprocess_test.go
@@ -28,6 +28,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser/mysql"
 	"github.com/pingcap/tidb/pkg/parser/terror"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -46,7 +47,8 @@ func runSQL(t *testing.T, ctx sessionctx.Context, is infoschema.InfoSchema, sql
 	if inPrepare {
 		opts = append(opts, core.InPrepare)
 	}
-	err = core.Preprocess(context.Background(), ctx, stmt, append(opts, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))...)
+	nodeW := resolve.NewNodeW(stmt)
+	err = core.Preprocess(context.Background(), ctx, nodeW, append(opts, core.WithPreprocessorReturn(&core.PreprocessorReturn{InfoSchema: is}))...)
 	require.Truef(t, terror.ErrorEqual(err, terr), "sql: %s, err:%v", sql, err)
 }
 
@@ -402,7 +404,8 @@ func TestPreprocessCTE(t *testing.T) {
 		require.NoError(t, err)
 		require.Len(t, stmts, 1)
 
-		err = core.Preprocess(context.Background(), tk.Session(), stmts[0])
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = core.Preprocess(context.Background(), tk.Session(), nodeW)
 		require.NoError(t, err)
 
 		var rs strings.Builder
diff --git a/pkg/planner/core/rule_generate_column_substitute_test.go b/pkg/planner/core/rule_generate_column_substitute_test.go
index f86aefd88f..6808197fc8 100644
--- a/pkg/planner/core/rule_generate_column_substitute_test.go
+++ b/pkg/planner/core/rule_generate_column_substitute_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/stretchr/testify/require"
 )
@@ -265,7 +266,8 @@ func BenchmarkSubstituteExpression(b *testing.B) {
 	fmt.Println(sql)
 	stmt, err := s.GetParser().ParseOneStmt(sql, "", "")
 	require.NoError(b, err, sql)
-	p, err := core.BuildLogicalPlanForTest(ctx, s.GetSCtx(), stmt, s.GetIS())
+	nodeW := resolve.NewNodeW(stmt)
+	p, err := core.BuildLogicalPlanForTest(ctx, s.GetSCtx(), nodeW, s.GetIS())
 	require.NoError(b, err)
 	selection := p.(base.LogicalPlan).Children()[0]
 	m := make(core.ExprColumnMap, len(selection.Schema().Columns))
diff --git a/pkg/planner/core/stringer_test.go b/pkg/planner/core/stringer_test.go
index 57d721c0d3..4525917006 100644
--- a/pkg/planner/core/stringer_test.go
+++ b/pkg/planner/core/stringer_test.go
@@ -21,6 +21,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/testkit"
 	"github.com/pingcap/tidb/pkg/util/hint"
 	"github.com/stretchr/testify/require"
@@ -119,7 +120,8 @@ func TestPlanStringer(t *testing.T) {
 		require.NoError(t, err, "for %s", tt.sql)
 		ret := &core.PreprocessorReturn{}
 		builder, _ := core.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), ret.InfoSchema, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(context.TODO(), stmt)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := builder.Build(context.TODO(), nodeW)
 		require.NoError(t, err, "for %s", tt.sql)
 		p, err = core.LogicalOptimize(context.TODO(), builder.GetOptFlag(), p.(base.LogicalPlan))
 		require.NoError(t, err, "for %s", tt.sql)
diff --git a/pkg/planner/core/tests/prepare/BUILD.bazel b/pkg/planner/core/tests/prepare/BUILD.bazel
index ecf7e3f339..82ca9bfa6c 100644
--- a/pkg/planner/core/tests/prepare/BUILD.bazel
+++ b/pkg/planner/core/tests/prepare/BUILD.bazel
@@ -19,6 +19,7 @@ go_test(
         "//pkg/parser",
         "//pkg/parser/auth",
         "//pkg/planner/core",
+        "//pkg/planner/core/resolve",
         "//pkg/session",
         "//pkg/session/types",
         "//pkg/sessionctx/variable",
diff --git a/pkg/planner/core/tests/prepare/prepare_test.go b/pkg/planner/core/tests/prepare/prepare_test.go
index 77a40d3eb8..b5cb613ea2 100644
--- a/pkg/planner/core/tests/prepare/prepare_test.go
+++ b/pkg/planner/core/tests/prepare/prepare_test.go
@@ -33,6 +33,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/parser/auth"
 	"github.com/pingcap/tidb/pkg/planner/core"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	sessiontypes "github.com/pingcap/tidb/pkg/session/types"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
@@ -397,7 +398,8 @@ func TestPrepareCacheDeferredFunction(t *testing.T) {
 		require.NoError(t, err)
 		is := tk.Session().GetInfoSchema().(infoschema.InfoSchema)
 		builder, _ := core.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), is, hint.NewQBHintHandler(nil))
-		p, err := builder.Build(ctx, stmt)
+		nodeW := resolve.NewNodeW(stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		execPlan, ok := p.(*core.Execute)
 		require.True(t, ok)
diff --git a/pkg/planner/core/util_test.go b/pkg/planner/core/util_test.go
index 944b1eefef..8fae0096f9 100644
--- a/pkg/planner/core/util_test.go
+++ b/pkg/planner/core/util_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/parser/model"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/stretchr/testify/require"
 )
 
@@ -306,7 +307,8 @@ func TestExtractTableList(t *testing.T) {
 	for i, c := range cases {
 		stmtNode, err := p.ParseOneStmt(c.sql, "", "")
 		require.NoError(t, err, "case %d sql: %s", i, c.sql)
-		tableNames := ExtractTableList(stmtNode, c.asName)
+		nodeW := resolve.NewNodeW(stmtNode)
+		tableNames := ExtractTableList(nodeW, c.asName)
 		require.Len(t, tableNames, len(c.expect), "case %d sql: %s, len: %d, actual: %s", i, c.sql, len(tableNames), tableNamesAsStr(tableNames))
 		sortTableNames(tableNames)
 		sortTableNames(c.expect)
diff --git a/pkg/planner/funcdep/extract_fd_test.go b/pkg/planner/funcdep/extract_fd_test.go
index 9aa79a48b0..aae6e33252 100644
--- a/pkg/planner/funcdep/extract_fd_test.go
+++ b/pkg/planner/funcdep/extract_fd_test.go
@@ -24,6 +24,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -220,12 +221,13 @@ func TestFDSet_ExtractFD(t *testing.T) {
 		require.NoError(t, err, comment)
 		tk.Session().GetSessionVars().PlanID.Store(0)
 		tk.Session().GetSessionVars().PlanColumnID.Store(0)
-		err = plannercore.Preprocess(context.Background(), tk.Session(), stmt, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), tk.Session(), nodeW, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err)
 		require.NoError(t, sessiontxn.GetTxnManager(tk.Session()).AdviseWarmup())
 		builder, _ := plannercore.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), is, hint.NewQBHintHandler(nil))
 		// extract FD to every OP
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err)
 		p, err = plannercore.LogicalOptimizeTest(ctx, builder.GetOptFlag(), p.(base.LogicalPlan))
 		require.NoError(t, err)
@@ -319,12 +321,13 @@ func TestFDSet_ExtractFDForApply(t *testing.T) {
 		require.NoError(t, err, comment)
 		tk.Session().GetSessionVars().PlanID.Store(0)
 		tk.Session().GetSessionVars().PlanColumnID.Store(0)
-		err = plannercore.Preprocess(context.Background(), tk.Session(), stmt, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), tk.Session(), nodeW, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err, comment)
 		require.NoError(t, sessiontxn.GetTxnManager(tk.Session()).AdviseWarmup())
 		builder, _ := plannercore.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), is, hint.NewQBHintHandler(nil))
 		// extract FD to every OP
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		p, err = plannercore.LogicalOptimizeTest(ctx, builder.GetOptFlag(), p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
@@ -367,12 +370,13 @@ func TestFDSet_MakeOuterJoin(t *testing.T) {
 		require.NoError(t, err, comment)
 		tk.Session().GetSessionVars().PlanID.Store(0)
 		tk.Session().GetSessionVars().PlanColumnID.Store(0)
-		err = plannercore.Preprocess(context.Background(), tk.Session(), stmt, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
+		nodeW := resolve.NewNodeW(stmt)
+		err = plannercore.Preprocess(context.Background(), tk.Session(), nodeW, plannercore.WithPreprocessorReturn(&plannercore.PreprocessorReturn{InfoSchema: is}))
 		require.NoError(t, err, comment)
 		require.NoError(t, sessiontxn.GetTxnManager(tk.Session()).AdviseWarmup())
 		builder, _ := plannercore.NewPlanBuilder().Init(tk.Session().GetPlanCtx(), is, hint.NewQBHintHandler(nil))
 		// extract FD to every OP
-		p, err := builder.Build(ctx, stmt)
+		p, err := builder.Build(ctx, nodeW)
 		require.NoError(t, err, comment)
 		p, err = plannercore.LogicalOptimizeTest(ctx, builder.GetOptFlag(), p.(base.LogicalPlan))
 		require.NoError(t, err, comment)
diff --git a/pkg/planner/memo/group_test.go b/pkg/planner/memo/group_test.go
index 32ec633d48..d0e553b3bf 100644
--- a/pkg/planner/memo/group_test.go
+++ b/pkg/planner/memo/group_test.go
@@ -26,6 +26,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/planner/pattern"
 	"github.com/pingcap/tidb/pkg/planner/property"
 	"github.com/pingcap/tidb/pkg/sessionctx/variable"
@@ -107,7 +108,8 @@ func TestGroupFingerPrint(t *testing.T) {
 		do := domain.GetDomain(ctx)
 		do.StatsHandle().Close()
 	}()
-	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt1, is)
+	nodeW := resolve.NewNodeW(stmt1)
+	plan, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW, is)
 	require.NoError(t, err)
 	logic1, ok := plan.(base.LogicalPlan)
 	require.True(t, ok)
@@ -240,7 +242,8 @@ func TestBuildKeyInfo(t *testing.T) {
 	// case 1: primary key has constant constraint
 	stmt1, err := p.ParseOneStmt("select a from t where a = 10", "", "")
 	require.NoError(t, err)
-	p1, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt1, is)
+	nodeW1 := resolve.NewNodeW(stmt1)
+	p1, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW1, is)
 	require.NoError(t, err)
 	logic1, ok := p1.(base.LogicalPlan)
 	require.True(t, ok)
@@ -252,7 +255,8 @@ func TestBuildKeyInfo(t *testing.T) {
 	// case 2: group by column is key
 	stmt2, err := p.ParseOneStmt("select b, sum(a) from t group by b", "", "")
 	require.NoError(t, err)
-	p2, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, stmt2, is)
+	nodeW2 := resolve.NewNodeW(stmt2)
+	p2, err := plannercore.BuildLogicalPlanForTest(context.Background(), ctx, nodeW2, is)
 	require.NoError(t, err)
 	logic2, ok := p2.(base.LogicalPlan)
 	require.True(t, ok)
diff --git a/pkg/sessiontxn/isolation/optimistic_test.go b/pkg/sessiontxn/isolation/optimistic_test.go
index 0508b362a3..f6e45b4054 100644
--- a/pkg/sessiontxn/isolation/optimistic_test.go
+++ b/pkg/sessiontxn/isolation/optimistic_test.go
@@ -29,6 +29,7 @@ import (
 	"github.com/pingcap/tidb/pkg/parser"
 	"github.com/pingcap/tidb/pkg/parser/ast"
 	"github.com/pingcap/tidb/pkg/planner"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/sessiontxn"
 	"github.com/pingcap/tidb/pkg/sessiontxn/isolation"
@@ -75,7 +76,8 @@ func TestOptimisticTxnContextProviderTS(t *testing.T) {
 	stmt := stmts[0]
 	provider = initializeOptimisticProvider(t, tk, false)
 	require.NoError(t, provider.OnStmtStart(context.TODO(), stmt))
-	plan, _, err := planner.Optimize(context.TODO(), tk.Session(), stmt, provider.GetTxnInfoSchema())
+	nodeW := resolve.NewNodeW(stmt)
+	plan, _, err := planner.Optimize(context.TODO(), tk.Session(), nodeW, provider.GetTxnInfoSchema())
 	require.NoError(t, err)
 	require.NoError(t, provider.AdviseOptimizeWithPlan(plan))
 	readTS, err = provider.GetStmtReadTS()
@@ -89,7 +91,7 @@ func TestOptimisticTxnContextProviderTS(t *testing.T) {
 	provider = initializeOptimisticProvider(t, tk, false)
 	require.NoError(t, provider.OnStmtStart(context.TODO(), stmt))
 	require.NoError(t, provider.AdviseWarmup())
-	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, provider.GetTxnInfoSchema())
+	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, provider.GetTxnInfoSchema())
 	require.NoError(t, err)
 	require.NoError(t, provider.AdviseOptimizeWithPlan(plan))
 	readTS, err = provider.GetStmtReadTS()
@@ -103,7 +105,7 @@ func TestOptimisticTxnContextProviderTS(t *testing.T) {
 	compareTS = getOracleTS(t, se)
 	provider = initializeOptimisticProvider(t, tk, true)
 	require.NoError(t, provider.OnStmtStart(context.TODO(), stmt))
-	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, provider.GetTxnInfoSchema())
+	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, provider.GetTxnInfoSchema())
 	require.NoError(t, err)
 	require.NoError(t, provider.AdviseOptimizeWithPlan(plan))
 	readTS, err = provider.GetStmtReadTS()
@@ -118,7 +120,7 @@ func TestOptimisticTxnContextProviderTS(t *testing.T) {
 	compareTS = getOracleTS(t, se)
 	provider = initializeOptimisticProvider(t, tk, false)
 	require.NoError(t, provider.OnStmtStart(context.TODO(), stmt))
-	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), stmt, provider.GetTxnInfoSchema())
+	plan, _, err = planner.Optimize(context.TODO(), tk.Session(), nodeW, provider.GetTxnInfoSchema())
 	require.NoError(t, err)
 	require.NoError(t, provider.AdviseOptimizeWithPlan(plan))
 	readTS, err = provider.GetStmtReadTS()
diff --git a/pkg/util/ranger/bench_test.go b/pkg/util/ranger/bench_test.go
index 69bba53273..543d5a4fd2 100644
--- a/pkg/util/ranger/bench_test.go
+++ b/pkg/util/ranger/bench_test.go
@@ -22,6 +22,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -111,10 +112,11 @@ WHERE
 	require.NoError(b, err)
 	require.Len(b, stmts, 1)
 	ret := &plannercore.PreprocessorReturn{}
-	err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+	nodeW := resolve.NewNodeW(stmts[0])
+	err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 	require.NoError(b, err)
 	ctx := context.Background()
-	p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+	p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 	require.NoError(b, err)
 	selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 	tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo
diff --git a/pkg/util/ranger/ranger_test.go b/pkg/util/ranger/ranger_test.go
index ba8a6028ee..2177561925 100644
--- a/pkg/util/ranger/ranger_test.go
+++ b/pkg/util/ranger/ranger_test.go
@@ -29,6 +29,7 @@ import (
 	plannercore "github.com/pingcap/tidb/pkg/planner/core"
 	"github.com/pingcap/tidb/pkg/planner/core/base"
 	"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
+	"github.com/pingcap/tidb/pkg/planner/core/resolve"
 	"github.com/pingcap/tidb/pkg/session"
 	"github.com/pingcap/tidb/pkg/sessionctx"
 	"github.com/pingcap/tidb/pkg/testkit"
@@ -282,9 +283,10 @@ func TestTableRange(t *testing.T) {
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			conds := make([]expression.Expression, len(selection.Conditions))
@@ -481,9 +483,10 @@ create table t(
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo
@@ -844,9 +847,10 @@ func TestColumnRange(t *testing.T) {
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			sel := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			ds, ok := sel.Children()[0].(*plannercore.DataSource)
@@ -1003,9 +1007,10 @@ func TestIndexRangeForYear(t *testing.T) {
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo
@@ -1073,9 +1078,10 @@ func TestPrefixIndexRangeScan(t *testing.T) {
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo
@@ -1421,9 +1427,10 @@ create table t(
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo
@@ -1663,9 +1670,10 @@ func TestTableShardIndex(t *testing.T) {
 			require.NoError(t, err)
 			require.Len(t, stmts, 1)
 			ret := &plannercore.PreprocessorReturn{}
-			err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+			nodeW := resolve.NewNodeW(stmts[0])
+			err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 			require.NoError(t, err)
-			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+			p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 			require.NoError(t, err)
 			selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 			conds := make([]expression.Expression, len(selection.Conditions))
@@ -1691,9 +1699,10 @@ func TestTableShardIndex(t *testing.T) {
 		require.NoError(t, err)
 		require.Len(t, stmts, 1)
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		selection, ok := p.(*plannercore.Update).SelectPlan.(*plannercore.PhysicalSelection)
 		require.True(t, ok)
@@ -1709,9 +1718,10 @@ func TestTableShardIndex(t *testing.T) {
 		require.NoError(t, err)
 		require.Len(t, stmts, 1)
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err)
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err)
 		selection, ok := p.(*plannercore.Delete).SelectPlan.(*plannercore.PhysicalSelection)
 		require.True(t, ok)
@@ -1855,9 +1865,10 @@ func getSelectionFromQuery(t *testing.T, sctx sessionctx.Context, sql string) *l
 	require.NoError(t, err)
 	require.Len(t, stmts, 1)
 	ret := &plannercore.PreprocessorReturn{}
-	err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+	nodeW := resolve.NewNodeW(stmts[0])
+	err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 	require.NoError(t, err)
-	p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+	p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 	require.NoError(t, err)
 	selection, isSelection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 	require.True(t, isSelection)
@@ -2320,9 +2331,10 @@ create table t(
 		require.NoError(t, err, fmt.Sprintf("error %v, for expr %s", err, tt.exprStr))
 		require.Len(t, stmts, 1)
 		ret := &plannercore.PreprocessorReturn{}
-		err = plannercore.Preprocess(context.Background(), sctx, stmts[0], plannercore.WithPreprocessorReturn(ret))
+		nodeW := resolve.NewNodeW(stmts[0])
+		err = plannercore.Preprocess(context.Background(), sctx, nodeW, plannercore.WithPreprocessorReturn(ret))
 		require.NoError(t, err, fmt.Sprintf("error %v, for resolve name, expr %s", err, tt.exprStr))
-		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, stmts[0], ret.InfoSchema)
+		p, err := plannercore.BuildLogicalPlanForTest(ctx, sctx, nodeW, ret.InfoSchema)
 		require.NoError(t, err, fmt.Sprintf("error %v, for build plan, expr %s", err, tt.exprStr))
 		selection := p.(base.LogicalPlan).Children()[0].(*logicalop.LogicalSelection)
 		tbl := selection.Children()[0].(*plannercore.DataSource).TableInfo