[tikv] TiDB omits SQL mode from TiKV DAG requests, letting strict DELETE silently remove rows
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Bug Report
### 1. Minimal reproduce step (Required)
**Environment**
TiDB master 05b396fb66; current TiKV source 91ccfb2126; real TiKV; default strict SQL mode; MDL ON; one TiDB
**Steps**
Create matched tables with rows (1,100.00,0,protected-zero-qty) and (2,100.00,4,valid-control). Keep default sql_mode and MDL. EXPLAIN DELETE WHERE (total / quantity) IS NULL and prove Selection runs in cop[tikv]. Run that DELETE on one table. On the other, add the always-false OR RAND()<0 barrier so the identical division is evaluated at the TiDB root. Compare terminal status, affected rows, and complete surviving primary-key sets. As a second family, repeat with VARCHAR expires_on values 0000-00-00 and 2024-01-15 using CAST(expires_on AS DATE) < DATE 2024-01-01.
### 2. What did you expect to see? (Required)
The DAG request carries the session SQL mode. TiKV therefore applies ERROR_FOR_DIVISION_BY_ZERO plus strict DML handling, returns error 1365, and preserves both rows, matching root evaluation. NO_ZERO_DATE similarly returns error 1292 and preserves the never-expires row.
### 3. What did you see instead? (Required)
On exact TiDB master 05b396fb66 with real TiKV, the pushed division DELETE succeeds, affects one row, and removes protected-zero-qty. The root-forced DELETE returns 1365 and preserves both rows. The pushed zero-date DELETE likewise succeeds and removes never-expires, while root evaluation returns 1292. Setting DAGRequest.SqlMode from SessionVars.SQLMode makes both pushed statements return their expected errors and preserve all rows.
### 4. What is your TiDB version? (Required)
TiDB master 05b396fb66; current TiKV source 91ccfb2126; real TiKV; default strict SQL mode; MDL ON; one TiDB
Likely root cause and fix direction
**Likely root cause**
ConstructDAGReq serializes statement flags, time zone, and other evaluator inputs but leaves the existing optional DAGRequest.sql_mode field unset. TiKV EvalConfig::from_request only installs SQL mode when that field is present, so pushed evaluation uses SqlMode::empty(). This disables strict ERROR_FOR_DIVISION_BY_ZERO, NO_ZERO_DATE, and NO_ZERO_IN_DATE behavior before the storage-layer Selection chooses rows for DML.
**Fix direction**
Populate DAGRequest.sql_mode from the session SQL mode on every TiKV/TiFlash DAG construction path, preferably through one centralized request-context builder. Add a request-envelope contract test plus pushed/root strict DML regressions for division by zero and zero dates. Audit every protobuf evaluation-context field for sender/receiver closure.
Contributor guide
Research direction
Trace ConstructDAGReq and the DAGRequest.sql_mode field, then compare SessionVars.SQLMode with TiKV's EvalConfig::from_request handling. Audit every TiKV/TiFlash DAG construction path and add the proposed request-envelope contract test plus pushed/root strict-DML regressions for division by zero and zero dates. Done means pushed statements return errors 1365 or 1292 and preserve the protected rows.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design, databases, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100