This is an automated email from the ASF dual-hosted git repository.

zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 25c5856f7e9 Optimize gen-ut (#37982)
25c5856f7e9 is described below

commit 25c5856f7e94b04e3949d910ed29e0d1441e166f
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Feb 8 17:50:12 2026 +0800

    Optimize gen-ut (#37982)
    
    * Optimize gen-ut
    
    * Optimize gen-ut
    
    * Optimize gen-ut
    
    * Optimize gen-ut
    
    * Optimize gen-ut
    
    * Optimize gen-ut
---
 .codex/skills/gen-ut/SKILL.md                      | 306 ++++++++++++---------
 .codex/skills/gen-ut/agents/openai.yaml            |   5 +-
 .../handler/ProxyBackendHandlerFactoryTest.java    | 297 ++++++++++++++------
 3 files changed, 392 insertions(+), 216 deletions(-)

diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index 4372cc39cf7..875ec6b1951 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -2,173 +2,231 @@
 name: gen-ut
 description: >-
   Generate standard unit tests for one or more target classes in Apache 
ShardingSphere;
-  use unified rules to complete test coverage and pass quality gates.
+  use unified rules to make target classes reach 100% class/line/branch 
coverage and pass quality gates.
 ---
 
 # Generate Unit Tests
 
-## Input Contract
+## Input Conventions
 
 Required input:
-- Target class list: one or more classes, preferably fully qualified names.
+- Target class list: one or more classes, preferably fully qualified class 
names.
 
 Optional input:
-- Module name (to scope Maven commands).
-- Test class list (for targeted test execution).
-- Extra constraints (for example forbidden APIs, command allowlist, coverage 
threshold).
+- Module name (used to scope Maven command execution).
+- Test class list (used for targeted test execution).
 
-Handling missing input:
+Missing-input handling:
 - If target classes are missing, request the class list before any coding work.
-- If test classes are missing, discover existing related test classes by the 
`TargetClassName + Test` convention first; if resolution fails, mark as blocked 
and ask the user to provide test classes.
-
-Test class placeholder convention:
-- `<ResolvedTestClass>` can be one fully qualified test class or a 
comma-separated list.
-- `<ResolvedTestFileSet>` is the concrete test file path list resolved from 
`<ResolvedTestClass>` (space-separated in shell commands).
+- If test classes are missing, first discover existing related test classes by 
the `TargetClassName + Test` convention.
+- If related test classes do not exist, create `<TargetClassName>Test` in the 
resolved module test source set and continue.
+- If `<ResolvedTestModules>` cannot be inferred from related test files or 
target class source files, mark as blocked and require the user to explicitly 
provide module scope.
+
+Input-blocking status mapping:
+- Input blocked (awaiting supplemental input): target classes are missing, or 
`<ResolvedTestModules>` cannot be determined and the user has not provided 
module scope.
+- After input is complete and the task enters execution, determine 
completion/blocking by `R9`, `R10`, and `R3`.
+- Execution-phase status mapping: dead-code blocking follows `R9-B`; 
out-of-scope failures follow `R9-C`.
+
+Test-class placeholder conventions:
+- `<ResolvedTestClass>` can be one fully qualified test class, or a 
comma-separated list.
+- `<ResolvedTestFileSet>` is the concrete editable file list (space-separated 
in shell commands), including:
+  - test source files resolved from `<ResolvedTestClass>`;
+  - new test files and test resources strictly required for these target 
classes.
+  - Must be resolved to concrete paths in workflow step 3.
+- `<ResolvedTestModules>` is a comma-separated Maven module list for scoped 
verification commands.
+  - Resolution order:
+    1. If explicit module input is provided, use it first;
+    2. Otherwise infer from related test files (`<ResolvedTestFileSet>`) via 
the nearest parent Maven module (`pom.xml`);
+    3. Otherwise infer from target class source files via the nearest parent 
Maven module (`pom.xml`).
+
+Terminology:
+- `Related test class`: existing `TargetClassName + Test` classes resolved 
within the same module test scope.
+- `Distinct observable assertion`: assertions for different public outcomes or 
side effects.
 
 ## Mandatory Constraints (Single Source of Rules)
 
-- `R1`: Follow `CODE_OF_CONDUCT.md`.
-- `R2`: Use standard unit tests with `@Test` only; forbid `@RepeatedTest` and 
`@ParameterizedTest`.
-- `R3`: Do not modify production code; only modify `src/test/java` and 
`src/test/resources`.
-- `R4`: Enumerate all branch paths of target public methods before coding.
-- `R5`: Apply minimal branch coverage: one branch maps to one test method;
-  do not cover the same branch in multiple test methods.
-- `R6`: Each test method covers one scenario and invokes the target public 
method at most once (extra assertions are allowed for the same scenario).
-- `R7`: Prefer `TypedSPILoader` and `OrderedSPILoader` to build subjects under 
test; subjects under test must be class-level fields.
-- `R8`: If dead code exists, report class name, file path, exact line number, 
and unreachable reason.
-- `R9`: Completion criteria must satisfy one of the following:
-  - `R9-A`: Target class coverage reaches 100% for class/line/branch, target 
test classes are executed successfully by Surefire, Checkstyle passes,
-    and all commands with exit codes are recorded.
-  - `R9-B`: If dead code blocks 100% branch coverage under the "no production 
code changes" rule,
-    report fully as required by `R8` and wait for user decision.
-- `R10`: If a related test class already exists for a target class, extend 
that class to add only missing-coverage tests; create a new test class only 
when no related test class exists.
-- `R11`: Do not claim completion if target tests were not actually executed 
due compile/runtime blockers. First remove blockers with minimal test-scope 
fixes and rerun verification;
-  only when blockers are outside scope and cannot be resolved safely in-turn, 
report exact blocker files/lines/commands and request user decision.
-- `R12`: Boolean assertion policy in tests:
-  - Must use `assertTrue` / `assertFalse` for boolean checks.
-  - Forbidden assertion patterns:
-    - `assertThat(<boolean expression>, is(true))`
-    - `assertThat(<boolean expression>, is(false))`
-    - `assertEquals(true, ...)`
-    - `assertEquals(false, ...)`
-- `R13`: Hard gate for `R12`:
-  - Use the unified scan regex:
-    - 
`assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,`
-  - Run hard-gate scan twice:
-    - after test implementation (early fail-fast gate);
-    - before final delivery (final release gate).
-  - If any match is found, task state is "not complete" until all violations 
are fixed and scan is rerun clean.
-- `R14`: Change scope must be strictly limited to tests resolved from input 
target classes.
-  - Allowed edit files: only `<ResolvedTestClass>` mapped source files under 
`src/test/java` or `src/test/resources`.
-  - Forbidden: modifying any other test file to fix unrelated build/lint/gate 
failures.
-  - Exception: only if the user explicitly approves scope expansion in the 
current turn.
-- `R15`: No meaningless test code; each line must have direct necessity for 
the scenario.
-  - Do not add redundant mocks/stubs/assertions that do not change branch 
selection, object behavior, or verification result.
-  - Prefer Mockito default return values unless explicit stubbing is required 
by the scenario.
-  - If a line is only cosmetic and removing it does not affect scenario setup 
or assertions, remove it.
-- `R16`: Inline single-use local variables in tests.
-  - If a local variable is used exactly once, inline it at the call site.
-  - Exception: keep the variable only when it is required for additional 
stubbing/verification or shared assertions.
-- `R17`: Test-method necessity hard gate; no coverage-equivalent duplicates.
-  - Each test method must add at least one unique value: either an uncovered 
branch/path from `R4/R5`, or a distinct externally observable behavior 
assertion not already covered.
-  - If removing a test method keeps target-class line/branch coverage 
unchanged and does not lose unique behavior verification, remove that method.
-  - For factory/route fallback scenarios, keep only one representative method 
per branch outcome unless the user explicitly requests an extra regression 
guard.
-
-## Execution Boundary
-
-- Only handle unit-test-scope tasks; do not perform production feature 
refactoring.
-- Do not edit generated directories (such as `target/`).
-- Do not use destructive git operations (such as `git reset --hard`, `git 
checkout --`).
-- If module name is missing, module-less command templates are allowed; if 
module is specified, module-scoped commands are mandatory.
-- If failures come from out-of-scope test files under `R14`, report blockers 
and wait for user decision instead of editing those files.
+- Definition-source principle: rule definitions exist only in the "Mandatory 
Constraints" section; other sections only reference rule IDs.
+- Layered index:
+  - `L1 (Base Constraints Layer)`: `R1`, `R2`, `R3`
+  - `L2 (Test Design and Implementation Layer)`: `R4`, `R5`, `R6`, `R7`, `R8`
+  - `L3 (Status Determination and Blocking Handling Layer)`: `R9`, `R10`
+  - `L4 (Hard Quality Gate Layer)`: `R11`, `R12`
+- `R1 (L1-Base Constraints Layer)`: Follow `CODE_OF_CONDUCT.md`.
+- `R2 (L1-Base Constraints Layer)`: Use JUnit `@Test`; `@RepeatedTest` is 
forbidden.
+- `R3 (L1-Base Constraints Layer)`: Change scope is strictly limited to the 
test scope resolved from input target classes.
+  - Editable files are only `<ResolvedTestFileSet>`.
+  - Modifying other test files to fix unrelated build/check/gate failures is 
forbidden.
+  - Scope can be expanded only when explicitly approved by the user in the 
current turn.
+- `R4 (L2-Test Design and Implementation Layer)`: Branch-path rule: before 
coding, enumerate all branch paths of target public methods; by default one 
branch/path maps to only one test method; necessity of additional tests on the 
same branch is judged by `R11`.
+- `R5 (L2-Test Design and Implementation Layer)`: Each test covers one 
scenario, and invokes the target public method at most once; additional 
assertions are allowed in the same scenario.
+- `R6 (L2-Test Design and Implementation Layer)`: When the class under test 
can be obtained via SPI, default to `TypedSPILoader` and `OrderedSPILoader`.
+  - "Obtainable via SPI": the class implements `TypedSPI`/`DatabaseTypedSPI`, 
or its implementation can be discovered by an SPI loader.
+  - If SPI is not used for instantiation, the reason must be recorded in the 
plan before implementation.
+- `R7 (L2-Test Design and Implementation Layer)`: If the target class already 
has related tests, update in place: first fill missing coverage, then delete or 
merge coverage-equivalent tests by `R11`; create new tests only when no related 
tests exist.
+- `R8 (L2-Test Design and Implementation Layer)`: If dead code exists, report 
class name, file path, exact line number, and unreachable reason.
+- `R9 (L3-Status Determination and Blocking Handling Layer)`: Completion is 
determined by one of the following:
+  - `R9-A`: target class coverage (class/line/branch) is 100%, target test 
classes run successfully in Surefire, and required quality gates (`R12` hard 
gate, Checkstyle) pass, with commands and exit codes attached.
+  - `R9-B`: if dead code blocks 100% branch coverage under the "do not modify 
production code" rule, report by `R8` and mark blocked.
+  - `R9-C`: if failure occurs outside `R3` scope, report blocking evidence by 
`R10` and mark blocked.
+  - Priority: evaluate `R9-B` first, then `R9-C`; only when neither applies 
can `R9-A` be marked complete.
+- `R10 (L3-Status Determination and Blocking Handling Layer)`: Blocking 
handling: first remove blocking within the smallest test scope and re-verify.
+  - If blocking is outside `R3` scope and cannot be safely solved in the 
current task, report exact blocking file/line/command and request user decision.
+- `R11 (L4-Hard Quality Gate Layer)`: Hard gate for test necessity:
+  - Decision order is fixed as "objective pruning -> exception-retention 
review"; reversing the order and causing repeated rework is forbidden.
+  - Objective-pruning phase: first identify and batch-delete 
coverage-equivalent tests based on `R4` branch mapping and coverage results, 
then uniformly re-verify coverage.
+  - Objective-pruning phase: then delete redundant mocks/stubs/assertions and 
single-use local variables that do not affect branch selection/collaborator 
behavior/observable assertion results.
+  - Each retained item must have a one-line necessity-reason label; retained 
items without labels are treated as redundant.
+  - Meaningless test code is not allowed; every line must have direct 
necessity for the scenario.
+  - If deleting a line leaves the above three dimensions unchanged, that line 
is redundant (including redundant mocks/stubs/assertions) and must be deleted.
+  - Unless a scenario explicitly requires stubbing, use Mockito default return 
values.
+  - Single-use local variables should be inlined at the call site; retain only 
when the variable is used for extra stubbing/verification across two or more 
statements or for shared assertions.
+  - Coverage-equivalent duplicate test methods are forbidden.
+  - Each test method must add unique value: cover an uncovered branch/path, or 
add an uncovered distinct observable assertion.
+  - Coverage-equivalent duplicate: same target public method, same 
branch/path, and no assertion difference; changing only literals/mock 
names/fixture values with unchanged assertion results is also duplicate.
+  - If deleting a test method does not change line/branch coverage of the 
target class, the method must be deleted.
+  - For factory/routing fallback scenarios, by default keep only one 
representative method for each branch result; add extra methods only when there 
is assertion difference or the user explicitly requests extra regression 
protection.
+- `R12 (L4-Hard Quality Gate Layer)`: Boolean assertions and hard gate:
+  - Boolean checks must use `assertTrue` / `assertFalse`; the following 
patterns are forbidden:
+  - `assertThat(<boolean expression>, is(true))`
+  - `assertThat(<boolean expression>, is(false))`
+  - `assertEquals(true, ...)`
+  - `assertEquals(false, ...)`
+  - The single source of regex is "Validation and Commands / Item 4".
+  - The hard-gate scan must run twice: after test implementation (early 
fail-fast gate) and before final delivery (final release gate).
+  - Any hit is considered "incomplete" until all issues are fixed and a clean 
rescan is obtained.
+
+## Execution Boundaries
+
+- Only handle unit test tasks.
+- Do not modify production code.
+- Only `src/test/java` and `src/test/resources` may be modified.
+- Do not edit generated directories (for example, `target/`).
+- Do not use destructive git operations (for example, `git reset --hard`, `git 
checkout --`).
+- If module name is not provided, infer `<ResolvedTestModules>` and keep 
commands module-scoped; repository-scoped commands are forbidden unless 
approved by the user.
 
 ## Workflow
 
 1. Re-check `AGENTS.md` and `CODE_OF_CONDUCT.md`.
-2. Locate target classes and existing test classes.
-3. Output branch-path inventory according to `R4`.
-4. Output branch-to-test mapping according to `R5`.
-5. Perform dead-code analysis according to `R8` and record findings.
-6. Implement or extend tests according to 
`R2/R3/R6/R7/R10/R12/R13/R14/R15/R16/R17`.
-7. Run necessity self-check according to `R15/R16/R17` and remove redundant 
mocks/stubs/assertions/single-use locals/coverage-equivalent test methods.
-8. Run the first `R13` hard-gate scan (early fail-fast) and fix all hits.
-9. Run verification commands and iterate.
-10. Run the second `R13` hard-gate scan (final release gate) and ensure clean.
-11. Deliver results using the output structure.
-
-## Verification and Commands
-
-With module input:
-
-1. Targeted unit tests:
+2. Locate target classes and related test classes.
+3. Resolve `<ResolvedTestClass>`, `<ResolvedTestFileSet>`, and 
`<ResolvedTestModules>`, and record module-resolution evidence (`pom.xml` 
paths).
+4. Output branch list and test mapping (`R4`); if extra tests exist on the 
same branch, record their necessity basis (`R11`).
+5. Execute dead-code analysis by `R8` and record the result.
+6. Implement or extend tests by implementation rules (`R2`, `R4`, `R5`, `R6`, 
`R7`, `R3`, `R11`, `R12`, execution boundaries).
+7. Execute necessity self-check by `R11` (objective pruning -> 
exception-retention review), removing redundant mocks/stubs/assertions, 
single-use local variables, and coverage-equivalent test methods.
+8. Execute the first hard-gate scan by `R12` (early fail-fast) and fix all 
hits.
+9. Run verification commands in layered order (target tests -> target module 
gate -> fallback gate when necessary) and iterate.
+10. Execute the second hard-gate scan by `R12` (final release gate) and ensure 
clean results.
+11. Determine final status by `R9`, then deliver with the rule-evidence 
mapping.
+
+## Validation and Commands
+
+Execution decision tree (rule references only):
+0. Input blocking (awaiting supplemental input) first: process by 
"Input-blocking status mapping" and do not enter `R9` determination.
+1. Completion determination follows `R9` only.
+2. Failure within `R3` scope: fix within `<ResolvedTestFileSet>` by `R10` and 
rerun.
+3. Failure outside `R3` scope: record evidence by `R10` and mark blocked by 
`R9-C`.
+4. `<FallbackGateModuleFlags>` is only for troubleshooting, does not expand 
edit scope (`R3`), and does not change completion determination (`R9-A`).
+
+Flag presets:
+- When module input is provided:
+  - `<TestModuleFlags>` = `-pl <module>`
+  - `<GateModuleFlags>` = `-pl <module>`
+- When module input is not provided:
+  - `<TestModuleFlags>` = `-pl <ResolvedTestModules>`
+  - `<GateModuleFlags>` = `-pl <ResolvedTestModules>`
+  - `<FallbackGateModuleFlags>` = `<GateModuleFlags> -am` (used only when gate 
fails because of cross-module dependency gaps).
+
+1. Targeted unit test:
 ```bash
-./mvnw -pl <module> -DskipITs -Dspotless.skip=true -Dtest=<ResolvedTestClass> 
-Dsurefire.failIfNoSpecifiedTests=false test
+./mvnw <TestModuleFlags> -DskipITs -Dspotless.skip=true 
-Dtest=<ResolvedTestClass> -Dsurefire.failIfNoSpecifiedTests=false test
 ```
 
 2. Coverage:
 ```bash
-./mvnw -pl <module> -am -DskipITs -Djacoco.skip=false test jacoco:report
+./mvnw <GateModuleFlags> -DskipITs -Djacoco.skip=false test jacoco:report 
jacoco:check@jacoco-check -Pcoverage-check
 ```
-
-3. Checkstyle:
+If `jacoco-check@jacoco-check` Maven execution is not defined in the target 
module's `pom.xml`, use:
 ```bash
-./mvnw -pl <module> -am -Pcheck checkstyle:check -DskipTests
+./mvnw <GateModuleFlags> -DskipITs -Djacoco.skip=false test jacoco:report
 ```
+Then record manual coverage evidence with the following template (must include 
the generation command and an accessible report path):
+- `Target classes`: `<ClassA>,<ClassB>,...`
+- `Coverage`: `class=<...>, line=<...>, branch=<...>`
+- `Report generation command`: `<report generation command>`
+- `Report path`: `<report path>`
 
-4. `R13` hard-gate scan (must be clean, run in step 8 and step 10):
+3. Checkstyle:
 ```bash
-bash -lc 'if rg -n 
"assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,"
 <ResolvedTestFileSet>; then echo "[R13] forbidden boolean assertion found"; 
exit 1; fi'
+./mvnw <GateModuleFlags> -Pcheck checkstyle:check -DskipTests
 ```
 
-Without module input:
-
-1. Targeted unit tests:
+4. `R12` hard-gate scan (must be clean; execute in workflow step 8 and step 
10):
 ```bash
-./mvnw -DskipITs -Dspotless.skip=true -Dtest=<ResolvedTestClass> 
-Dsurefire.failIfNoSpecifiedTests=false test
+bash -lc '
+BOOLEAN_ASSERTION_BAN_REGEX="assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,"
+if rg -n "$BOOLEAN_ASSERTION_BAN_REGEX" <ResolvedTestFileSet>; then
+  echo "[R12] forbidden boolean assertion found"
+  exit 1
+fi'
 ```
 
-2. Coverage:
-```bash
-./mvnw -DskipITs -Djacoco.skip=false test jacoco:report
-```
+Command-execution rules:
+- Record every command and its exit code.
+- Retry only once for retryable errors (for example, temporary 
plugin-resolution failure, repository mirror timeout, transient network 
jitter); handle non-retryable errors directly by `R10`.
+- If a gate command fails because of cross-module dependency gaps, remediate 
once with `<FallbackGateModuleFlags>` and record the fallback reason.
+- If a command fails, record the failed command and key error lines by the 
execution decision tree (`R10`, `R3`, `R9-C`).
 
-3. Checkstyle:
+Blocking report template:
+- `Failed command`: `<failed command>`
+- `Exit code`: `<code>`
+- `Key error line`: `<key error line>`
+- `Within R3 scope`: `<yes/no>`
+- `Status`: `<R9-B or R9-C>`
+
+5. Minimal pre-delivery machine-check list (recommended order):
 ```bash
-./mvnw -Pcheck checkstyle:check -DskipTests
+git diff --name-only
 ```
-
-4. `R13` hard-gate scan (must be clean, run in step 8 and step 10):
+- Check against `<ResolvedTestFileSet>` item by item to confirm modified files 
do not exceed `R3` scope.
 ```bash
-bash -lc 'if rg -n 
"assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,"
 <ResolvedTestFileSet>; then echo "[R13] forbidden boolean assertion found"; 
exit 1; fi'
+./mvnw <TestModuleFlags> -DskipITs -Dspotless.skip=true 
-Dtest=<ResolvedTestClass> -Dsurefire.failIfNoSpecifiedTests=false test
 ```
-
-Command execution rules:
-- Record every command and exit code.
-- If a command fails, record the failure reason and execute at least one 
remediation attempt; if still blocked, continue clearing blockers within test 
scope before escalating.
-- Escalate to user only when blockers are outside safe scope or require 
non-test changes; include exact failing commands, error lines, and attempted 
remediations.
+- Execute item 4 `R12` hard-gate scan command and record the result (final 
release gate).
 
 ## Output Structure
 
-Follow this order:
-
-1. Goal and constraints (mapped to `R1-R17`)
-2. Plan and implementation (including branch mapping result)
-3. Dead-code and coverage results (according to `R8/R9`)
-4. Verification commands and exit codes
-5. `R13` hard-gate evidence (both scan commands and exit codes)
-6. Risks and next actions
+Use the following order:
+
+1. Goal and constraints (mapped to `R1-R12`)
+2. Status (determined by `R9`) + one-line reason
+3. Plan and implementation (including module-resolution evidence, 
branch-mapping results, and any `R11` exception reasons and labels)
+4. Dead code and coverage results (at minimum include: target-class coverage 
values, dead-code location, and reason)
+5. Verification and quality-gate evidence (at minimum include: key commands, 
exit codes, and `R12` scan results)
+6. Rule-evidence mapping (`R#->evidence`, at least covering `R4`, `R7`, `R8`, 
`R9`, `R10`, `R3`, `R11`, `R12`)
+7. Risks and next actions
+
+Minimal template for rule-evidence mapping:
+- `R4`: branch list and branch-test mapping.
+- `R7`: evidence of in-place update or creation decision for related test 
classes.
+- `R8`: dead-code location (class, path, line number, reason).
+- `R9`: final status and determination reason.
+- `R10/R3`: blocking-scope determination and blocking-report template.
+- `R11`: necessity self-check result (deleted items, retained-reason labels, 
coverage re-verification result).
+- `R12`: boolean-assertion policy and results of two hard-gate scans.
 
 ## Quality Self-Check
 
-- Rule definitions must exist only in "Mandatory Constraints"; other sections 
should reference rule IDs only.
-- Final state must satisfy `R9`, and all applicable rules (including `R10`, 
`R11`, `R12`, `R13`, `R14`, `R15`, `R16`, and `R17`) must be met, with complete 
command and exit-code records.
-- `R13` command is mandatory evidence; missing `R13` command record or 
non-clean scan means not complete.
+- Rule definitions may appear only in the "Mandatory Constraints" section; 
other sections may only reference rule IDs.
+- Final status must satisfy `R9`; command-based rules provide command and 
exit-code evidence, and non-command rules provide mapping/code evidence.
+- The `R12` command is mandatory evidence; missing `R12` records or a 
non-clean scan is considered incomplete.
+- Blocking-state evidence must follow `R10`.
+- Output must include "Rule-evidence mapping" and must be consistent with the 
final status in `R9`.
 
 ## Maintenance Rules
 
-- After changing any `R` numbering, run `rg -n "R[0-9]+" 
.codex/skills/gen-ut/SKILL.md` to ensure there are no dangling references.
-- After changing skill rules, verify trigger semantics are consistent between 
`SKILL.md` and `agents/openai.yaml`.
-- Fixed final-review order:
-  1. Numbering consistency check
-  2. Duplicate phrase scan
-  3. Semantic alignment check between `SKILL.md` and `agents/openai.yaml`
+- After modifying any `R` index, run `rg -n "R[0-9]+" 
.codex/skills/gen-ut/SKILL.md` to ensure no dangling references.
+- After modifying skill rules, verify semantic consistency between `SKILL.md` 
and `agents/openai.yaml` trigger semantics.
+- Fixed final review order:
+  1. Index consistency check
+  2. Repeated-phrase scan
+  3. Semantic consistency check between `SKILL.md` and `agents/openai.yaml`
diff --git a/.codex/skills/gen-ut/agents/openai.yaml 
b/.codex/skills/gen-ut/agents/openai.yaml
index 8e23918d678..d0144180863 100644
--- a/.codex/skills/gen-ut/agents/openai.yaml
+++ b/.codex/skills/gen-ut/agents/openai.yaml
@@ -17,8 +17,9 @@
 
 interface:
   display_name: "Generate Unit Tests"
-  short_description: "Generate standard ShardingSphere unit tests with strict 
quality gates and mandatory executable verification"
+  short_description: "Generate standard ShardingSphere unit tests with strict 
quality gates, reporting R9-A completion or R9-B/R9-C blocked status with 
evidence"
   default_prompt: >-
     Use $gen-ut to generate standard unit tests for one or more ShardingSphere 
classes.
     Provide the class set (optional module name and test class) and execute 
according to SKILL.md rules.
-    Do not claim completion unless target tests are actually executed 
successfully; clear test-scope blockers first.
+    Determine final status strictly by SKILL.md R9:
+    report completion only as R9-A (target tests pass and 100% 
class/line/branch coverage is verified for target classes) or blocked states 
R9-B/R9-C with required evidence.
diff --git 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/ProxyBackendHandlerFactoryTest.java
 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/ProxyBackendHandlerFactoryTest.java
index cd0ff87be8a..e6abf757502 100644
--- 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/ProxyBackendHandlerFactoryTest.java
+++ 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/handler/ProxyBackendHandlerFactoryTest.java
@@ -21,25 +21,33 @@ import 
org.apache.shardingsphere.authority.rule.AuthorityRule;
 import 
org.apache.shardingsphere.authority.rule.builder.DefaultAuthorityRuleConfigurationBuilder;
 import org.apache.shardingsphere.authority.rule.builder.DefaultUser;
 import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import 
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
 import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
 import 
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
+import org.apache.shardingsphere.infra.executor.checker.SQLExecutionChecker;
 import org.apache.shardingsphere.infra.hint.HintValueContext;
+import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
 import 
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
 import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
 import org.apache.shardingsphere.infra.metadata.user.Grantee;
 import org.apache.shardingsphere.infra.session.connection.ConnectionContext;
 import 
org.apache.shardingsphere.infra.session.connection.transaction.TransactionConnectionContext;
+import org.apache.shardingsphere.infra.session.query.QueryContext;
+import org.apache.shardingsphere.infra.spi.ShardingSphereServiceLoader;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mode.manager.ContextManager;
 import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
 import org.apache.shardingsphere.mode.state.ShardingSphereState;
 import org.apache.shardingsphere.parser.rule.SQLParserRule;
 import 
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigurationBuilder;
-import 
org.apache.shardingsphere.proxy.backend.connector.DatabaseProxyConnector;
 import 
org.apache.shardingsphere.proxy.backend.connector.ProxyDatabaseConnectionManager;
 import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
-import 
org.apache.shardingsphere.proxy.backend.handler.admin.DatabaseAdminQueryProxyBackendHandler;
+import 
org.apache.shardingsphere.proxy.backend.handler.admin.DatabaseAdminProxyBackendHandlerFactory;
+import 
org.apache.shardingsphere.proxy.backend.handler.data.DatabaseProxyBackendHandler;
+import 
org.apache.shardingsphere.proxy.backend.handler.data.DatabaseProxyBackendHandlerFactory;
 import 
org.apache.shardingsphere.proxy.backend.handler.data.type.UnicastDatabaseProxyBackendHandler;
+import 
org.apache.shardingsphere.proxy.backend.handler.database.type.CreateDatabaseProxyBackendHandler;
+import 
org.apache.shardingsphere.proxy.backend.handler.database.type.DropDatabaseProxyBackendHandler;
 import 
org.apache.shardingsphere.proxy.backend.handler.distsql.DistSQLQueryProxyBackendHandler;
 import 
org.apache.shardingsphere.proxy.backend.handler.distsql.DistSQLUpdateProxyBackendHandler;
 import 
org.apache.shardingsphere.proxy.backend.handler.skip.SkipProxyBackendHandler;
@@ -54,52 +62,56 @@ import 
org.apache.shardingsphere.proxy.backend.handler.tcl.local.type.SetTransac
 import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
 import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.EmptyStatement;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.table.RenameTableStatement;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
 import org.apache.shardingsphere.transaction.rule.TransactionRule;
 import 
org.apache.shardingsphere.transaction.rule.builder.DefaultTransactionRuleConfigurationBuilder;
 import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.ArgumentsProvider;
-import org.junit.jupiter.params.provider.ArgumentsSource;
-import org.junit.jupiter.params.support.ParameterDeclarations;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Answers;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 
+import java.sql.SQLFeatureNotSupportedException;
 import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Optional;
 import java.util.Properties;
 import java.util.stream.Stream;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.isA;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings({ProxyContext.class, 
DatabaseAdminProxyBackendHandlerFactory.class})
 @MockitoSettings(strictness = Strictness.LENIENT)
 class ProxyBackendHandlerFactoryTest {
     
     private final DatabaseType databaseType = 
TypedSPILoader.getService(DatabaseType.class, "MySQL");
     
+    private final DatabaseType postgreSQLDatabaseType = 
TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+    
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private ConnectionSession connectionSession;
     
     @BeforeEach
     void setUp() {
+        when(DatabaseAdminProxyBackendHandlerFactory.newInstance(any(), any(), 
any(), any(), any())).thenCallRealMethod();
         ConnectionContext connectionContext = mockConnectionContext();
         
when(connectionSession.getConnectionContext()).thenReturn(connectionContext);
         when(connectionSession.getCurrentDatabaseName()).thenReturn("db");
@@ -115,7 +127,7 @@ class ProxyBackendHandlerFactoryTest {
     
     private ConnectionContext mockConnectionContext() {
         ConnectionContext result = mock(ConnectionContext.class);
-        
when(result.getCurrentDatabaseName()).thenReturn(Optional.of("foo_db"));
+        when(result.getCurrentDatabaseName()).thenReturn(Optional.of("db"));
         when(result.getGrantee()).thenReturn(new Grantee(DefaultUser.USERNAME, 
"%"));
         
when(result.getTransactionContext()).thenReturn(mock(TransactionConnectionContext.class));
         return result;
@@ -123,9 +135,16 @@ class ProxyBackendHandlerFactoryTest {
     
     private ContextManager mockContextManager() {
         MetaDataContexts metaDataContexts = mock(MetaDataContexts.class, 
RETURNS_DEEP_STUBS);
-        when(metaDataContexts.getMetaData().getDatabase("db")).thenReturn(new 
ShardingSphereDatabase("foo_db", mock(), mock(), new 
RuleMetaData(Collections.emptyList()), Collections.emptyList()));
+        when(metaDataContexts.getMetaData().getDatabase("db")).thenReturn(new 
ShardingSphereDatabase("db", mock(), mock(), new 
RuleMetaData(Collections.emptyList()), Collections.emptyList()));
+        when(metaDataContexts.getMetaData().getDatabase("information_schema"))
+                .thenReturn(new ShardingSphereDatabase("information_schema", 
mock(), mock(), new RuleMetaData(Collections.emptyList()), 
Collections.emptyList()));
+        
when(metaDataContexts.getMetaData().containsDatabase("db")).thenReturn(true);
+        
when(metaDataContexts.getMetaData().containsDatabase("information_schema")).thenReturn(true);
         ContextManager result = mock(ContextManager.class, RETURNS_DEEP_STUBS);
         when(result.getMetaDataContexts()).thenReturn(metaDataContexts);
+        
when(result.getDatabase("db").getProtocolType()).thenReturn(databaseType);
+        when(result.getDatabase("db").containsDataSource()).thenReturn(true);
+        
when(result.getDatabase("information_schema").getProtocolType()).thenReturn(databaseType);
         when(metaDataContexts.getMetaData().getProps()).thenReturn(new 
ConfigurationProperties(new Properties()));
         RuleMetaData globalRuleMetaData = new RuleMetaData(Arrays.asList(
                 new AuthorityRule(new 
DefaultAuthorityRuleConfigurationBuilder().build()),
@@ -135,65 +154,100 @@ class ProxyBackendHandlerFactoryTest {
         return result;
     }
     
-    @Test
-    void assertNewInstanceWithDistSQL() throws SQLException {
-        String sql = "set dist variable sql_show='true'";
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newInstanceWithDistSQLArguments")
+    void assertNewInstanceWithDistSQL(final String caseName, final String sql, 
final Class<? extends ProxyBackendHandler> expectedHandlerType) throws 
SQLException {
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLUpdateProxyBackendHandler.class));
-        sql = "show dist variable where name = sql_show";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLQueryProxyBackendHandler.class));
-        sql = "show dist variables";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLQueryProxyBackendHandler.class));
+        assertThat(caseName, actual, isA(expectedHandlerType));
     }
     
     @ParameterizedTest(name = "{0}")
-    @ArgumentsSource(TCLTestCaseArgumentsProvider.class)
-    void assertNewInstanceWithTCL(final String sql, final Class<? extends 
ProxyBackendHandler> proxyBackendHandlerClass) throws SQLException {
+    @MethodSource("newInstanceWithTCLArguments")
+    void assertNewInstanceWithTCL(final String caseName, final String sql, 
final Class<? extends ProxyBackendHandler> expectedHandlerType) throws 
SQLException {
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(proxyBackendHandlerClass));
+        assertThat(caseName, actual, isA(expectedHandlerType));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newInstanceWithShowArguments")
+    void assertNewInstanceWithShow(final String caseName, final String sql, 
final Class<? extends ProxyBackendHandler> expectedHandlerType) throws 
SQLException {
+        SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
+        ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
+        assertThat(caseName, actual, isA(expectedHandlerType));
+    }
+    
+    @Test
+    void assertNewInstanceWithAdminQuery() throws SQLException {
+        QueryContext queryContext = mock(QueryContext.class);
+        SQLStatementContext sqlStatementContext = 
mock(SQLStatementContext.class);
+        ProxyBackendHandler expected = mock(ProxyBackendHandler.class);
+        
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+        when(queryContext.getSql()).thenReturn("sql");
+        when(queryContext.getParameters()).thenReturn(Collections.emptyList());
+        
when(sqlStatementContext.getSqlStatement()).thenReturn(mock(SQLStatement.class));
+        when(DatabaseAdminProxyBackendHandlerFactory.newInstance(databaseType, 
sqlStatementContext, connectionSession, "sql", 
Collections.emptyList())).thenReturn(Optional.of(expected));
+        ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, 
connectionSession, false);
+        assertThat(actual, is(expected));
     }
     
     @Test
-    void assertNewInstanceWithShow() throws SQLException {
-        String sql = "SHOW VARIABLES LIKE '%x%'";
+    void assertNewInstanceWithDatabaseNameFromTablesContext() throws 
SQLException {
+        QueryContext queryContext = mock(QueryContext.class);
+        SQLStatementContext sqlStatementContext = 
mock(SQLStatementContext.class, RETURNS_DEEP_STUBS);
+        ShardingSphereMetaData metaData = mock(ShardingSphereMetaData.class);
+        DatabaseProxyBackendHandler expected = 
mock(DatabaseProxyBackendHandler.class);
+        
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+        when(queryContext.getMetaData()).thenReturn(metaData);
+        when(queryContext.getSql()).thenReturn("sql");
+        when(queryContext.getParameters()).thenReturn(Collections.emptyList());
+        
when(sqlStatementContext.getSqlStatement()).thenReturn(mock(SQLStatement.class));
+        
when(sqlStatementContext.getTablesContext().getDatabaseName()).thenReturn(Optional.of("db"));
+        
when(metaData.getDatabase("db")).thenReturn(mock(ShardingSphereDatabase.class));
+        try (
+                MockedStatic<ShardingSphereServiceLoader> serviceLoader = 
mockStatic(ShardingSphereServiceLoader.class);
+                MockedStatic<DatabaseProxyBackendHandlerFactory> 
databaseFactory = mockStatic(DatabaseProxyBackendHandlerFactory.class)) {
+            serviceLoader.when(() -> 
ShardingSphereServiceLoader.getServiceInstances(SQLExecutionChecker.class)).thenReturn(Collections.emptyList());
+            databaseFactory.when(() -> 
DatabaseProxyBackendHandlerFactory.newInstance(queryContext, connectionSession, 
false)).thenReturn(expected);
+            ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, 
connectionSession, false);
+            assertThat(actual, is(expected));
+        }
+    }
+    
+    @Test
+    void assertNewInstanceWithNullDatabaseName() throws SQLException {
+        QueryContext queryContext = mock(QueryContext.class);
+        SQLStatementContext sqlStatementContext = 
mock(SQLStatementContext.class, RETURNS_DEEP_STUBS);
+        DatabaseProxyBackendHandler expected = 
mock(DatabaseProxyBackendHandler.class);
+        
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+        when(queryContext.getSql()).thenReturn("sql");
+        when(queryContext.getParameters()).thenReturn(Collections.emptyList());
+        
when(sqlStatementContext.getSqlStatement()).thenReturn(mock(SQLStatement.class));
+        
when(sqlStatementContext.getTablesContext().getDatabaseName()).thenReturn(Optional.empty());
+        when(connectionSession.getUsedDatabaseName()).thenReturn(null);
+        when(DatabaseAdminProxyBackendHandlerFactory.newInstance(databaseType, 
sqlStatementContext, connectionSession, "sql", 
Collections.emptyList())).thenReturn(Optional.empty());
+        try (MockedStatic<DatabaseProxyBackendHandlerFactory> databaseFactory 
= mockStatic(DatabaseProxyBackendHandlerFactory.class)) {
+            databaseFactory.when(() -> 
DatabaseProxyBackendHandlerFactory.newInstance(queryContext, connectionSession, 
false)).thenReturn(expected);
+            ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, 
connectionSession, false);
+            assertThat(actual, is(expected));
+        }
+    }
+    
+    @Test
+    void assertNewInstanceWithCreateDatabaseStatement() throws SQLException {
+        String sql = "CREATE DATABASE foo_db";
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(UnicastDatabaseProxyBackendHandler.class));
-        sql = "SHOW VARIABLES WHERE Variable_name ='language'";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(UnicastDatabaseProxyBackendHandler.class));
-        sql = "SHOW CHARACTER SET";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(UnicastDatabaseProxyBackendHandler.class));
-        sql = "SHOW COLLATION";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(UnicastDatabaseProxyBackendHandler.class));
+        assertThat(actual, isA(CreateDatabaseProxyBackendHandler.class));
     }
     
-    // TODO
-    @Disabled("FIXME")
     @Test
-    void assertNewInstanceWithQuery() throws SQLException {
-        String sql = "SELECT * FROM t_order limit 1";
-        ProxyContext proxyContext = ProxyContext.getInstance();
-        
when(proxyContext.getContextManager().getAllDatabaseNames()).thenReturn(new 
HashSet<>(Collections.singletonList("db")));
-        
when(proxyContext.getContextManager().getDatabase("db").containsDataSource()).thenReturn(true);
+    void assertNewInstanceWithDropDatabaseStatement() throws SQLException {
+        String sql = "DROP DATABASE foo_db";
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(DatabaseProxyConnector.class));
-        sql = "SELECT * FROM information_schema.schemata LIMIT 1";
-        sqlStatement = ProxySQLComQueryParser.parse(sql, databaseType, 
connectionSession);
-        actual = ProxyBackendHandlerFactory.newInstance(databaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
-        assertThat(actual, isA(DatabaseAdminQueryProxyBackendHandler.class));
+        assertThat(actual, isA(DropDatabaseProxyBackendHandler.class));
     }
     
     @Test
@@ -204,58 +258,121 @@ class ProxyBackendHandlerFactoryTest {
     }
     
     @Test
-    void assertNewInstanceWithUnsupportedNonQueryDistSQLInTransaction() {
-        
when(connectionSession.getTransactionStatus().isInTransaction()).thenReturn(true);
-        String sql = "CREATE SHARDING TABLE RULE t_order 
(STORAGE_UNITS(ms_group_0,ms_group_1), SHARDING_COLUMN=order_id, 
TYPE(NAME='hash_mod', PROPERTIES('sharding-count'='4')));";
-        SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
-        assertThrows(UnsupportedSQLOperationException.class, () -> 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext()));
+    void assertNewInstanceWithEmptyStatementInQueryContext() throws 
SQLException {
+        QueryContext queryContext = mock(QueryContext.class);
+        SQLStatementContext sqlStatementContext = 
mock(SQLStatementContext.class);
+        
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+        when(sqlStatementContext.getSqlStatement()).thenReturn(new 
EmptyStatement(databaseType));
+        ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, 
connectionSession, false);
+        assertThat(actual, isA(SkipProxyBackendHandler.class));
     }
     
     @Test
-    void assertNewInstanceWithQueryableRALStatementInTransaction() throws 
SQLException {
-        
when(connectionSession.getTransactionStatus().isInTransaction()).thenReturn(true);
-        String sql = "SHOW TRANSACTION RULE;";
-        SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
-        ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLQueryProxyBackendHandler.class));
+    void assertNewInstanceWithUnsupportedStandardSQLStatement() {
+        QueryContext queryContext = mock(QueryContext.class);
+        SQLStatementContext sqlStatementContext = 
mock(SQLStatementContext.class);
+        
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+        when(sqlStatementContext.getSqlStatement()).thenReturn(new 
RenameTableStatement(databaseType, Collections.emptyList()));
+        assertThrows(UnsupportedSQLOperationException.class, () -> 
ProxyBackendHandlerFactory.newInstance(databaseType, queryContext, 
connectionSession, false));
+    }
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newInstanceWhenTransactionFailedWithPostgreSQLArguments")
+    void assertNewInstanceWhenTransactionFailedWithPostgreSQL(final String 
caseName, final String sql,
+                                                              final 
Optional<Class<? extends ProxyBackendHandler>> expectedHandlerType) throws 
SQLException {
+        
when(connectionSession.getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+        SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
postgreSQLDatabaseType, connectionSession);
+        if (expectedHandlerType.isPresent()) {
+            ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(postgreSQLDatabaseType, sql, 
sqlStatement, connectionSession, new HintValueContext());
+            assertThat(caseName, actual, isA(expectedHandlerType.get()));
+            return;
+        }
+        assertThrows(SQLFeatureNotSupportedException.class,
+                () -> 
ProxyBackendHandlerFactory.newInstance(postgreSQLDatabaseType, sql, 
sqlStatement, connectionSession, new HintValueContext()), caseName);
     }
     
     @Test
-    void assertNewInstanceWithRQLStatementInTransaction() throws SQLException {
-        
when(connectionSession.getTransactionStatus().isInTransaction()).thenReturn(true);
-        String sql = "SHOW DEFAULT SINGLE TABLE STORAGE UNIT";
+    void 
assertNewInstanceWhenTransactionFailedButDatabaseDoesNotRestrictStatement() 
throws SQLException {
+        
when(connectionSession.getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+        String sql = "COMMIT";
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLQueryProxyBackendHandler.class));
+        assertThat(actual, isA(CommitProxyBackendHandler.class));
     }
     
     @Test
-    void assertNewInstanceWithRULStatementInTransaction() throws SQLException {
-        
when(connectionSession.getTransactionStatus().isInTransaction()).thenReturn(true);
-        String sql = "PREVIEW INSERT INTO account VALUES(1, 1, 1)";
+    void assertNewInstanceWithReadOnlyClusterState() throws SQLException {
+        
when(ProxyContext.getInstance().getContextManager().getStateContext().getState()).thenReturn(ShardingSphereState.READ_ONLY);
+        String sql = "SHOW VARIABLES";
         SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
-        sqlStatement.buildAttributes();
         ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
-        assertThat(actual, isA(DistSQLQueryProxyBackendHandler.class));
+        assertThat(actual, isA(UnicastDatabaseProxyBackendHandler.class));
     }
     
-    private static final class TCLTestCaseArgumentsProvider implements 
ArgumentsProvider {
-        
-        @Override
-        public Stream<? extends Arguments> provideArguments(final 
ParameterDeclarations parameters, final ExtensionContext context) {
-            return Stream.of(
-                    Arguments.of("BEGIN", 
BeginTransactionProxyBackendHandler.class),
-                    Arguments.of("START TRANSACTION", 
BeginTransactionProxyBackendHandler.class),
-                    Arguments.of("SET AUTOCOMMIT=0", 
SetAutoCommitProxyBackendHandler.class),
-                    Arguments.of("SET @@SESSION.AUTOCOMMIT = OFF", 
SetAutoCommitProxyBackendHandler.class),
-                    Arguments.of("SET AUTOCOMMIT=1", 
SetAutoCommitProxyBackendHandler.class),
-                    Arguments.of("SET @@SESSION.AUTOCOMMIT = ON", 
SetAutoCommitProxyBackendHandler.class),
-                    Arguments.of("COMMIT", CommitProxyBackendHandler.class),
-                    Arguments.of("ROLLBACK", 
RollbackProxyBackendHandler.class),
-                    Arguments.of("SAVEPOINT foo_point", 
SetSavepointProxyBackendHandler.class),
-                    Arguments.of("RELEASE SAVEPOINT foo_point", 
ReleaseSavepointProxyBackendHandler.class),
-                    Arguments.of("ROLLBACK TO foo_point", 
RollbackSavepointProxyBackendHandler.class),
-                    Arguments.of("SET TRANSACTION READ ONLY, ISOLATION LEVEL 
REPEATABLE READ", SetTransactionProxyBackendHandler.class));
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("newInstanceWithDistSQLInTransactionArguments")
+    void assertNewInstanceWithDistSQLInTransaction(final String caseName, 
final String sql, final boolean buildAttributes,
+                                                   final Optional<Class<? 
extends ProxyBackendHandler>> expectedHandlerType) throws SQLException {
+        
when(connectionSession.getTransactionStatus().isInTransaction()).thenReturn(true);
+        SQLStatement sqlStatement = ProxySQLComQueryParser.parse(sql, 
databaseType, connectionSession);
+        if (buildAttributes) {
+            sqlStatement.buildAttributes();
+        }
+        if (expectedHandlerType.isPresent()) {
+            ProxyBackendHandler actual = 
ProxyBackendHandlerFactory.newInstance(databaseType, sql, sqlStatement, 
connectionSession, new HintValueContext());
+            assertThat(caseName, actual, isA(expectedHandlerType.get()));
+            return;
         }
+        assertThrows(UnsupportedSQLOperationException.class,
+                () -> ProxyBackendHandlerFactory.newInstance(databaseType, 
sql, sqlStatement, connectionSession, new HintValueContext()), caseName);
+    }
+    
+    private static Stream<Arguments> newInstanceWithDistSQLArguments() {
+        return Stream.of(
+                Arguments.of("DistSQL update statement", "set dist variable 
sql_show='true'", DistSQLUpdateProxyBackendHandler.class),
+                Arguments.of("DistSQL query statement with where", "show dist 
variable where name = sql_show", DistSQLQueryProxyBackendHandler.class),
+                Arguments.of("DistSQL query statement without where", "show 
dist variables", DistSQLQueryProxyBackendHandler.class));
+    }
+    
+    private static Stream<Arguments> newInstanceWithTCLArguments() {
+        return Stream.of(
+                Arguments.of("BEGIN", "BEGIN", 
BeginTransactionProxyBackendHandler.class),
+                Arguments.of("START TRANSACTION", "START TRANSACTION", 
BeginTransactionProxyBackendHandler.class),
+                Arguments.of("SET AUTOCOMMIT=0", "SET AUTOCOMMIT=0", 
SetAutoCommitProxyBackendHandler.class),
+                Arguments.of("SET @@SESSION.AUTOCOMMIT = OFF", "SET 
@@SESSION.AUTOCOMMIT = OFF", SetAutoCommitProxyBackendHandler.class),
+                Arguments.of("SET AUTOCOMMIT=1", "SET AUTOCOMMIT=1", 
SetAutoCommitProxyBackendHandler.class),
+                Arguments.of("SET @@SESSION.AUTOCOMMIT = ON", "SET 
@@SESSION.AUTOCOMMIT = ON", SetAutoCommitProxyBackendHandler.class),
+                Arguments.of("COMMIT", "COMMIT", 
CommitProxyBackendHandler.class),
+                Arguments.of("ROLLBACK", "ROLLBACK", 
RollbackProxyBackendHandler.class),
+                Arguments.of("SAVEPOINT foo_point", "SAVEPOINT foo_point", 
SetSavepointProxyBackendHandler.class),
+                Arguments.of("RELEASE SAVEPOINT foo_point", "RELEASE SAVEPOINT 
foo_point", ReleaseSavepointProxyBackendHandler.class),
+                Arguments.of("ROLLBACK TO foo_point", "ROLLBACK TO foo_point", 
RollbackSavepointProxyBackendHandler.class),
+                Arguments.of("SET TRANSACTION READ ONLY, ISOLATION LEVEL 
REPEATABLE READ",
+                        "SET TRANSACTION READ ONLY, ISOLATION LEVEL REPEATABLE 
READ", SetTransactionProxyBackendHandler.class));
+    }
+    
+    private static Stream<Arguments> newInstanceWithShowArguments() {
+        return Stream.of(
+                Arguments.of("SHOW VARIABLES with LIKE", "SHOW VARIABLES LIKE 
'%x%'", UnicastDatabaseProxyBackendHandler.class),
+                Arguments.of("SHOW VARIABLES with WHERE", "SHOW VARIABLES 
WHERE Variable_name ='language'", UnicastDatabaseProxyBackendHandler.class),
+                Arguments.of("SHOW CHARACTER SET", "SHOW CHARACTER SET", 
UnicastDatabaseProxyBackendHandler.class),
+                Arguments.of("SHOW COLLATION", "SHOW COLLATION", 
UnicastDatabaseProxyBackendHandler.class));
+    }
+    
+    private static Stream<Arguments> 
newInstanceWhenTransactionFailedWithPostgreSQLArguments() {
+        return Stream.of(
+                Arguments.of("transaction failed with SELECT", "SELECT 1", 
Optional.<Class<? extends ProxyBackendHandler>>empty()),
+                Arguments.of("transaction failed with COMMIT", "COMMIT", 
Optional.of(CommitProxyBackendHandler.class)),
+                Arguments.of("transaction failed with ROLLBACK", "ROLLBACK", 
Optional.of(RollbackProxyBackendHandler.class)));
+    }
+    
+    private static Stream<Arguments> 
newInstanceWithDistSQLInTransactionArguments() {
+        return Stream.of(
+                Arguments.of("non-query DistSQL in transaction", "CREATE 
SHARDING TABLE RULE t_order (STORAGE_UNITS(ms_group_0,ms_group_1),"
+                        + " SHARDING_COLUMN=order_id, TYPE(NAME='hash_mod', 
PROPERTIES('sharding-count'='4')));", false,
+                        Optional.<Class<? extends 
ProxyBackendHandler>>empty()),
+                Arguments.of("queryable RAL in transaction", "SHOW DIST 
VARIABLES", false, Optional.of(DistSQLQueryProxyBackendHandler.class)),
+                Arguments.of("RQL in transaction", "SHOW DEFAULT SINGLE TABLE 
STORAGE UNIT", false, Optional.of(DistSQLQueryProxyBackendHandler.class)),
+                Arguments.of("RUL in transaction", "PREVIEW INSERT INTO 
account VALUES(1, 1, 1)", true, 
Optional.of(DistSQLQueryProxyBackendHandler.class)));
     }
 }

Reply via email to