Ethan-Xingyue commented on PR #1140:
URL: 
https://github.com/apache/incubator-seata-go/pull/1140#issuecomment-5289540513

   Thanks for the work here — the overall shape is good. Validating the whole 
plan *before* any before-image query or business SQL runs is the right 
ordering, splitting plan construction (`multi_execution_plan.go`) from 
execution reads well, and the table-driven tests for the plan builder cover a 
nice range of cases.
   
   I ran the following locally on `6bb8930` (go1.24.3, darwin/arm64):
   
   - `go vet ./pkg/datasource/sql/...` — clean
   - `gofmt -l` over every changed `.go` file — clean
   - `go test ./pkg/datasource/sql/exec/at/... ./pkg/datasource/sql/util/... 
-count=1` — pass
   
   Comments below, roughly by importance. Nothing here is a hard blocker except 
possibly 1, which I think is worth settling before merge.
   
   ---
   
   ### 1. The two execution paths report `RowsAffected` differently
   
   The aggregate path issues one driver call with the full multi-statement 
string and returns the driver's result (`multi_executor.go:89`). The sequential 
path returns `lastResult` — the result of the **last** statement only 
(`multi_sequential_executor.go:137,140`).
   
   So for the same input SQL, `Result.RowsAffected()` depends on which path was 
selected, and path selection depends on things the caller can't see: whether 
the statements are parameterized, whether they target the same table, and 
whether some unrelated hook happens to be registered.
   
   Was this considered? If the divergence is intended, it would help to 
document it on `ExecContext`. Otherwise, summing affected rows across 
statements on the sequential path would make the two paths agree.
   
   ### 2. Any registered UPDATE/DELETE hook globally disables the aggregate path
   
   ```go
   if plan.useAggregatePath && !hasStatementSpecificHooks(plan) {   // 
multi_executor.go:47
   ```
   
   `hasStatementSpecificHooks` consults the **global** hook registry by SQL 
type (`multi_executor.go:63`), not anything statement-local. So registering a 
single UPDATE hook anywhere in the process silently flips every multi-statement 
UPDATE from one round-trip to N round-trips, process-wide.
   
   That's a config-driven performance cliff with no signal to the operator. Two 
thoughts:
   
   - At minimum, log once when the aggregate path is skipped for this reason, 
so it's diagnosable.
   - Better, if feasible: run the statement-specific hooks on the aggregate 
path rather than abandoning the path entirely.
   
   This coupling is also the only reason `exec.HooksForSQLType` had to become 
exported API in `pkg/` — worth weighing.
   
   ### 3. `HooksForSQLType` allocates a slice copy just to test its length
   
   ```go
   func HooksForSQLType(sqlType types.SQLType) []SQLHook {   // hook.go:75
        hooks := hookSolts[sqlType]
        return append([]SQLHook(nil), hooks...)                // hook.go:77
   }
   ```
   
   The only production caller is `len(hooksForSQLType(...)) != 0` 
(`multi_executor.go:63`), so this copies a slice per statement per execution 
and immediately discards it. An `exec.HasHooksForSQLType(sqlType) bool` would 
avoid the allocation and express the intent better.
   
   ### 4. `rejectATPreparedMultiSQL` fails open, and parses on every `Prepare`
   
   ```go
   parseCtx, err := sqlparser.DoParser(query)
   if err != nil || parseCtx == nil {
        return nil                                             // conn_at.go:64
   }
   ```
   
   Two things:
   
   - **Fails open on parse error.** SQL the parser can't handle is allowed 
straight through to `Prepare`, which is exactly the case where AT interception 
is least likely to behave. Is permitting it deliberate? A comment would help 
either way.
   - **Cost on the hot path.** This adds a full SQL parse to every 
`Prepare`/`PrepareContext` on MySQL. Since a multi-statement query must contain 
an interior `;`, an early `if !strings.Contains(query, ";") { return nil }` 
would skip the parse for the overwhelming majority of statements.
   
   ### 5. `execSequential` re-parses every statement on every execution
   
   Each statement's AST is restored to SQL and then handed back to 
`parser.DoParser` (`multi_sequential_executor.go:63`), with the result used to 
check that the executor type didn't change (`:68`). That's N extra full parses 
per execution, on the hot path.
   
   The child `*types.ParseContext` in `MultiStmt` already carries the AST. Is 
the round-trip check load-bearing in practice, or could it be dropped (or moved 
into plan construction / behind a debug assertion)?
   
   ### 6. `rowsWithStmt` now has three copies
   
   ```
   pkg/datasource/sql/conn_at.go:36
   pkg/datasource/sql/exec/at/insert_executor.go:249
   pkg/datasource/sql/util/ctxutil.go:125   (new in this PR)
   ```
   
   The new one is the best of the three — it nil-guards both fields and joins 
both errors with `errors.Join`. The two older copies nil-deref if either field 
is nil, and silently drop the statement-close error whenever the rows-close 
error is non-nil.
   
   Both other packages already import `util`. Exporting the new implementation 
there and deleting the other two would remove the duplication and fix the 
weaker behavior at the same time. Happy for that to be a follow-up if you'd 
rather keep this PR focused — but three divergent copies of the same wrapper is 
a trap for the next reader.
   
   ### 7. Error wrapping drops the cause
   
   ```go
   return "", fmt.Errorf("%w: get table name for statement %d: %v", 
ErrInvalidMultiSQL, index, err)   // multi_execution_plan.go:120
   ```
   
   `err` is formatted with `%v`, so the underlying error can't be recovered 
with `errors.Is`/`errors.As`. Go 1.20+ supports multiple `%w` in one `Errorf`, 
so `...: %w", ErrInvalidMultiSQL, index, err)` works here.
   
   ### 8. Are the exported error values intended as public API?
   
   `ErrInvalidMultiSQL` and `ErrUnsupportedMultiSQL` 
(`multi_execution_plan.go:28-29`) are exported from a `pkg/` package. If 
they're only consumed internally and by tests, unexporting them keeps the 
public surface smaller.
   
   ---


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to