Skip to content

sql_mode: reject unsupported modes at every layer, neutralize them on every connection - #20883

Merged
arthurschreiber merged 18 commits into
mainfrom
arthur/sql-mode-reject-and-neutralize
Sep 3, 2026
Merged

sql_mode: reject unsupported modes at every layer, neutralize them on every connection#20883
arthurschreiber merged 18 commits into
mainfrom
arthur/sql-mode-reject-and-neutralize

Conversation

@arthurschreiber

@arthurschreiber arthurschreiber commented Aug 20, 2026

Copy link
Copy Markdown
Member

Description

The Vitess parser doesn't support (or doesn't honor) the sql_modes that change how SQL text is interpreted: ANSI_QUOTES, NO_BACKSLASH_ESCAPES, PIPES_AS_CONCAT, REAL_AS_FLOAT, IGNORE_SPACE, HIGH_NOT_PRECEDENCE, and the ANSI combination. Today they can still sneak in at several layers. This PR makes sure none of them can take effect anywhere in a cluster:

  • A new go/mysql/sqlmode package implements MySQL 8.x's sql_mode value semantics, verified against a live MySQL 8.0.46 — name lists, numeric bitmasks, combination-mode expansion, and MySQL's own error codes (1231 / 3899).
  • vtgate validates every SET sql_mode with it, closing the existing bypasses: no-op assignments matching the backend's mode, numeric bitmasks, and the ANSI combination all now error. Constants (including CONCAT over literals) fail at plan time, evaluated expressions at execution time. IGNORE_SPACE and HIGH_NOT_PRECEDENCE join the rejected set. The shard-targeted SET path (USE ks:-80, then SET), which previously skipped validation entirely and stored the raw expression before executing, is judged the same way and stores only values the shard accepted.
  • vttablet mirrors the validation with identical errors, for traffic that bypasses vtgate (older vtgates in mixed-version clusters, direct query-service clients): connection settings, SET statements (non-constants via verify-after-execute with restore-on-violation), and ApplySchema --session-variable values. SET_VAR optimizer hints are deliberately not judged: a hint applies to its statement's execution only and can't change how that statement's own text is lexed, which is vttablet's only stake in sql_mode, so the hint is forwarded verbatim and MySQL warns about and ignores invalid values exactly as it does for direct clients.
  • Every MySQL connection Vitess creates strips the lexer modes from its session sql_mode at the dbconfigs connector choke point, working from the session's own value so that anything init_connect applied survives. MySQL lexes each statement under the pre-statement session mode — a SET_VAR hint can't influence the parsing of its own statement (verified live) — so Vitess-generated SQL should always be parsed under the same lexer rules it was serialized with, regardless of how the backend happens to be configured. Runtime modes (strict, zero-date, ...) are preserved. The settings-pool reset still sources from the global, matching what SET sql_mode = DEFAULT does on main. The choke-point placement follows a full audit of every connection-creation path — per-site placement kept leaving gaps (vreplication/vdiff clients, schema preflight/apply, vstreamer helpers).

Rejecting these modes is not the end state: with this in place, supporting a lexer mode for incoming queries later becomes a pure parser change plus removing it from the rejected set — outbound query serialization never has to account for them.

Related Issue(s)

Part of #20984.

Follow-ups raised in review, tracked separately:

A follow-up PR builds on this to make sql_mode a vtgate-session-owned setting (default via a --sql-mode flag, sent with every query).

Checklist

  • "Backport to:" labels have been added if this change should be back-ported to release branches
  • If this change is to be back-ported to previous releases, a justification is included in the PR description (not intended for backport — behavior change)
  • Tests were added or are not required
  • Did the new or modified tests pass consistently locally and on CI? (all touched packages incl. the real-MySQL vstreamer/vreplication suites pass locally; CI pending)
  • Documentation was added or is not required (changelog sections for the vtgate rejection, the vttablet mirror + connection neutralization, and ApplySchema session-variable validation)

Deployment Notes

  • Clients that SET sql_mode with an unsupported mode now get an error — even as a no-op matching the backend's configured mode. Such sessions were already unreliable, since vtgate parses queries without honoring these modes.
  • During a rolling upgrade (vttablets before vtgates), sessions that set a now-rejected mode through a not-yet-upgraded vtgate start erroring against upgraded vttablets.
  • Vitess connections now run with the server's lexer modes stripped from their session (session-scoped; the global is never touched). This also applies to ExecuteFetchAsDba-style admin RPC connections (an explicit in-batch SET still wins) and to external MySQL servers used as vreplication/PITR sources. The guarantee is scoped to MySQL; MariaDB (deprecated, migration-source only) is documented.

AI Disclosure

This PR was written primarily by Claude Code — I provided direction and review.

🤖 Generated with Claude Code

Copilot AI balanced review requested due to automatic review settings August 20, 2026 17:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vitess-bot vitess-bot Bot added NeedsWebsiteDocsUpdate What it says NeedsDescriptionUpdate The description is not clear or comprehensive enough, and needs work NeedsIssue A linked issue is missing for this Pull Request NeedsBackportReason If backport labels have been applied to a PR, a justification is required labels Aug 20, 2026
@github-actions github-actions Bot added this to the v25.0.0 milestone Aug 20, 2026
@vitess-bot

vitess-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Review Checklist

Hello reviewers! 👋 Please follow this checklist when reviewing this Pull Request.

General

  • Ensure that the Pull Request has a descriptive title.
  • Ensure there is a link to an issue (except for internal cleanup and flaky test fixes), new features should have an RFC that documents use cases and test cases.

Tests

  • Bug fixes should have at least one unit or end-to-end test, enhancement and new features should have a sufficient number of tests.

Documentation

  • Apply the release notes (needs details) label if users need to know about this change.
  • New features should be documented.
  • There should be some code comments as to why things are implemented the way they are.
  • There should be a comment at the top of each new or modified test to explain what the test does.

New flags

  • Is this flag really necessary?
  • Flag names must be clear and intuitive, use dashes (-), and have a clear help text.

If a workflow is added or modified:

  • Each item in Jobs should be named in order to mark it as required.
  • If the workflow needs to be marked as required, the maintainer team must be notified.

Backward compatibility

  • Protobuf changes should be wire-compatible.
  • Changes to _vt tables and RPCs need to be backward compatible.
  • RPC changes should be compatible with vitess-operator
  • If a flag is removed, then it should also be removed from vitess-operator and arewefastyet, if used there.
  • vtctl command output order should be stable and awk-able.

Copilot AI review requested due to automatic review settings August 20, 2026 18:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.12195% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.03%. Comparing base (70c7a72) to head (70770ab).
⚠️ Report is 529 commits behind head on main.

Files with missing lines Patch % Lines
go/vt/vtgate/engine/set.go 94.23% 3 Missing ⚠️
go/vt/vttablet/tabletserver/query_executor.go 89.65% 3 Missing ⚠️
go/vt/dbconfigs/dbconfigs.go 81.81% 2 Missing ⚠️
...o/vt/vttablet/tabletserver/planbuilder/sql_mode.go 94.87% 2 Missing ⚠️
go/vt/mysqlctl/query.go 83.33% 1 Missing ⚠️
go/vt/vttablet/tabletserver/planbuilder/plan.go 88.88% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main   #20883       +/-   ##
===========================================
+ Coverage   69.67%   77.03%    +7.36%     
===========================================
  Files        1614      553     -1061     
  Lines      216793    84317   -132476     
===========================================
- Hits       151044    64952    -86092     
+ Misses      65749    19365    -46384     
Flag Coverage Δ
partial 77.03% <95.12%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

arthurschreiber and others added 2 commits August 20, 2026 18:21
… every connection

The Vitess parser does not support or honor the sql_modes that change
how SQL text is interpreted: ANSI_QUOTES, NO_BACKSLASH_ESCAPES,
PIPES_AS_CONCAT, REAL_AS_FLOAT, IGNORE_SPACE, HIGH_NOT_PRECEDENCE, and
the ANSI combination. This change makes sure none of them can take
effect anywhere in a Vitess cluster.

The new go/mysql/sqlmode package implements MySQL 8.x's sql_mode value
semantics, verified against a live MySQL 8.0.46: name lists, numeric
bitmasks, combination-mode expansion, canonical formatting, and MySQL's
own error codes (ER_WRONG_VALUE_FOR_VAR 1231, ER_UNSUPPORTED_SQL_MODE
3899, a new vterrors state).

vtgate validates every SET sql_mode assignment with it: constants —
including constant expressions like CONCAT over literals — at planning
time regardless of --enable-system-settings, and evaluated values at
execution time before the change detection, closing the previous
bypasses (no-op assignments matching the backend's mode, numeric
bitmasks, the ANSI combination). IGNORE_SPACE and HIGH_NOT_PRECEDENCE
join the rejected set.

vttablet mirrors the validation with identical errors for clients that
bypass vtgate — older vtgates in mixed-version clusters and direct
query-service clients: connection settings (settings pool and true
reservations), SET_VAR hints at plan build, SET statements (constants at
plan time; non-constants via verify-after-execute: the applied
@@sql_mode is read back, validated, and the previous mode restored on
violation), and ApplySchema --session-variable values on both the vtctld
and tablet sides.

The server's own configuration cannot re-introduce these modes either:
MySQL lexes each statement under the pre-statement session sql_mode — a
SET_VAR hint cannot influence the parsing of its own statement — so
every connection Vitess creates strips the lexer modes from the session
sql_mode inherited from the global value, at the dbconfigs connector
choke point all components dial through. The statement is a
nested-REPLACE chain (MySQL offers no numeric arithmetic on the sql_mode
system variable); runtime modes are preserved, the settings-pool reset
restores the neutralized value instead of `default`, and backup init
SQL runs through a new ExecuteSuperQueryListTainted that discards its
connection so operator statements cannot leak session state into pooled
connections. The guarantee is scoped to MySQL; MariaDB (a deprecated
migration source) is documented and the verify-after-execute read-back
only judges values that parse as MySQL modes.

Legacy engine tests that characterized the old textual comparison with
made-up mode names now use real modes; fakesqldb answers the
connection-setup statement automatically, counting it for
GetQueryCalledNum while keeping it out of QueryLog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
…ording it

The neutralization statement every Vitess-created connection runs is
connection state, not query execution; recording it made explain output
depend on connection-creation timing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
@arthurschreiber
arthurschreiber force-pushed the arthur/sql-mode-reject-and-neutralize branch from 9159cff to 059abbf Compare August 20, 2026 18:21
Copilot AI review requested due to automatic review settings August 20, 2026 18:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@maxenglander maxenglander left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disclaimer: findings and writing generated by AI.

Residual risk: Reserve preQueries still ignore non-constant sql_mode expressions (verify flag discarded in ValidateSettingsSQLMode), which matches the “settings are constants from VTGate” assumption but leaves a small hole for direct queryservice clients.

if err != nil {
return nil, err
}
applied, err := qre.readSQLMode(conn)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In execSet, when VerifySQLMode is set, the SET is applied, then readSQLMode runs. If that readback fails, the function returns the error immediately and never restores prev or closes the connection. Session sql_mode is not undone by transaction rollback, so a reserved/transactional connection can keep an unsupported lexer mode after a failed verify. That breaks the invariant this PR adds (and that the restore-failure path already enforces by closing the connection). After a successful SET under VerifySQLMode, any failure before a successful validation verdict should restore prev or close the connection.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good catch! I'll take a look.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 91f2a26.

Review feedback on the sql_mode verify-after-execute path, two gaps in
its failure handling:

A multi-assignment SET whose non-constant sql_mode value failed the
post-execution verification had only its sql_mode assignment restored —
the statement had already executed on MySQL, so its other assignments
stayed applied on the connection while the client saw an error. MySQL
applies none of a SET's assignments when the statement fails, and the
executor has no snapshot of the other variables to restore, so the
connection is now closed instead of being returned with some
assignments applied. Single-assignment SETs keep the exact restore.

A failed read-back of the applied value returned the error without any
restore, leaving the connection in whatever mode the statement set. The
read-back failure now takes the same undo path: restore for a
single-assignment SET, close for a multi-assignment one, close if the
restore fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Copilot AI review requested due to automatic review settings August 20, 2026 22:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Review feedback, two classes of gaps.

The settings paths accepted values they could not judge. Connection
settings — the settings pool and reservation pre-queries — are applied
with no verification afterwards, yet a non-constant sql_mode value
(e.g. CONCAT over literals) passed through both unjudged, and the
reservation path additionally skipped settings that did not parse or
were not SET statements, which the settings pool already rejects. All
of it is now rejected upfront: settings must parse as SET statements
carrying constant sql_mode values. vtgates only ever render constants
into settings, so this concerns direct query-service clients.

Several verification states failed open and now fail closed:

- execSet accepted an applied sql_mode that did not decode as MySQL
  8.x modes. A constant with an unknown mode name is rejected at plan
  time, and an expression producing one must not fare better just
  because it cannot be judged — the statement is now undone and the
  error returned. This drops the tolerance for foreign mode
  vocabularies: the tablet's own database is MySQL.
- sqlModeChangedValue treated an unexpected verification result shape
  as "no change", reporting success for an assignment that was never
  validated; it now returns an error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Copilot AI review requested due to automatic review settings August 20, 2026 22:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

arthurschreiber added a commit that referenced this pull request Sep 3, 2026
The targeted SET path is validated by the base PR (#20883) already, so
the clause read as if this PR were what fixed it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
@promptless

promptless Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Promptless documentation updates

  • Document v25 rejection of unsupported sql_mode lexer values expands the 25.0 MySQL-compatibility "SQL Mode" reference to spell out that Vitess now rejects the seven lexer-affecting sql_mode values (ANSI_QUOTES, NO_BACKSLASH_ESCAPES, PIPES_AS_CONCAT, REAL_AS_FLOAT, IGNORE_SPACE, HIGH_NOT_PRECEDENCE, ANSI), that SET sql_mode errors in all forms and reaches direct VTTablet connections and ApplySchema --session-variable, and that runtime modes like STRICT_TRANS_TABLES are unaffected.

arthurschreiber and others added 4 commits September 3, 2026 10:10
On the reserved-connection path the SET sent to the shards carried the
original expression, so a non-constant sql_mode was evaluated twice: once
by the judgment query, whose result the session stores, and again by the
SET itself. A nondeterministic expression could therefore apply a value
the session never judged. The SET now carries the judged literal, on both
the targeted and the untargeted path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
MySQL applies none of a SET's assignments when one of them fails. A
multi-assignment SET whose sql_mode can only be judged after execution
had already applied its other assignments by the time the read-back
rejected the mode; with autocommit among them, a transaction could be
committed by a statement that then fails. Closing the connection
afterwards did not undo that. Such statements are now rejected at plan
time, which leaves the verify-after-execute path to single-assignment
SETs, where restoring the previous mode undoes everything the statement
applied, as MySQL leaves it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
With --enable-system-settings=false a SET is checked and ignored rather
than applied. Constant sql_mode values were already rejected at plan
time on that path, but a non-constant one was never judged, so an
unsupported mode passed silently. The check query now evaluates the
value the same way the reserved-connection path does, and an unsupported
mode is an error there too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
The dba-grants wait, GetMysqlPort and the clone donor check dialed
MySQL directly, bypassing the connector that neutralizes the session's
sql_mode on every Vitess-created connection. They now connect through
it. The socket-wait probe and the external-MySQL ping are left as they
are: neither sends a statement, and the socket wait runs with root
credentials during initialization.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Copilot AI review requested due to automatic review settings September 3, 2026 10:11

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 2 new potential issues.

Devin Review

s := buf.String()
vcursor.Session().SetSysVar(svs.Name, s)
storedValue = buf.String()
vcursor.Session().SetSysVar(svs.Name, storedValue)

@devin-ai-integration devin-ai-integration Bot Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Failed settings persist in sessions

When execSetStatement fails on existing shard sessions, the value was already stored. Later queries replay the rejected setting despite the SET error.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this here. The ordering is the same as on main for every reserved-connection variable; this PR changed it only on the targeted path, where a single shard makes store-after-success unambiguous. On the untargeted path the SET is sent to every existing shard session, and after a partial failure the stored value is what makes the next query replay the setting on the shard that failed; storing only after success would leave a shard that accepted the SET carrying the mode with the session unaware, and nothing to reconcile them. For sql_mode the value is judged before it is stored, so a permanently rejected value cannot get in; what remains are transient failures, which the replay converges. The general problem — a failed SET poisoning the session rather than failing the statement — is tracked in #20893, whose proposed eager verification stores values only once a shard has accepted the whole settings bundle. The reservation flag is set before the SET because the scatter layer decides whether to reserve from it at execution time, so it staying set after a failure is the same as on main and on the targeted path.

Comment on lines +49 to +63
func ValidateSettingsSQLMode(settings []string, parser *sqlparser.Parser) error {
for _, setting := range settings {
stmt, err := parser.Parse(setting)
if err != nil {
return vterrors.Wrapf(err, "failed to parse connection setting: %s", setting)
}
set, ok := stmt.(*sqlparser.Set)
if !ok {
return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "connection setting is not a SET statement: %s", setting)
}
if err := validateConstantSetExprsSQLMode(set.Exprs); err != nil {
return err
}
}
return nil

@devin-ai-integration devin-ai-integration Bot Sep 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Reservation pre-query contract narrows

ValidateSettingsSQLMode now rejects every non-SET pre-query, including SQL unrelated to sql_mode. Review compatibility for direct query-service clients.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the requirement; the reasoning is on the Codex thread at sql_mode.go:57. pre_queries carries vtgate's session settings, which are always SET statements, and the settings-pool path has required that since it was written. Arbitrary statements from a direct query-service client were never a supported interface.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The cross-layer connection and session-state changes require final human validation of compatibility and operational impact.

Review details
  • Files reviewed: 42/42 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@arthurschreiber

Copy link
Copy Markdown
Member Author

The Codex P1 on set.go (the SET re-evaluating the validated expression) is fixed in df2dda8: the reserved-connection SET now carries the judged literal. Details on the Copilot thread at set.go:284.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Copilot AI review requested due to automatic review settings September 3, 2026 14:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mattlord mattlord left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Non-blocking: I think we should validate the result row count before indexing it. In go/vt/vtgate/engine/set.go:397, sqlModeChangedValue reads qr.Rows[0] while checking the result shape. An unexpected successful response with zero rows would panic VTGate rather than fail closed with an INTERNAL error. The scalar query should always return one row from MySQL, but since this helper already defends against malformed results, I think we should require exactly one row and add zero/multiple-row test cases.

  2. Non-blocking: I think we should update the introductory comment in go/vt/vttablet/tabletserver/planbuilder/sql_mode.go:28. It says non-constant expressions pass through and are left to MySQL, but the current implementation rejects them for connection settings and multi-assignment SETs, while a sole dynamic assignment is post-verified and restored if necessary. The stale description could mislead future changes to this safety boundary.

arthurschreiber and others added 2 commits September 3, 2026 19:03
sqlModeChangedValue indexed the first row before checking that there was one, so a
successful result with no rows would panic instead of failing closed. The reserved
connection path also short-circuited on an empty result as "no change" before the
judgment ran, which would let an unjudged value through. The judgment now runs first
for sql_mode and requires one row of two columns, and the empty and multi-row shapes
are covered by tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
The package comment still said non-constant values pass through to MySQL. Connection
settings reject them, multi-assignment SETs reject them upfront, and a sole assignment
is verified after it runs and restored on failure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
@arthurschreiber

arthurschreiber commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

Thanks for the review, Matt! Both follow-ups are in:

  1. 8cb561b makes sqlModeChangedValue require exactly one row of two columns before indexing, and reports fields/rows in the error. While adding the zero-row and multi-row tests I found that the reserved-connection path was short-circuiting on an empty result as "no change" before the judgment ran, so an empty result would have been accepted silently rather than panicking. The judgment now runs first for sql_mode, so that shape fails closed with the INTERNAL error too.
  2. 0a1bac9 rewrites the intro comment in sql_mode.go to describe the three cases: settings reject non-constant values, multi-assignment SETs reject them upfront, and a sole assignment is read back after it runs and restored on failure.

Copilot AI review requested due to automatic review settings September 3, 2026 19:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a1bac9cce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread go/vt/dbconfigs/dbconfigs.go Outdated
Comment thread go/mysql/sqlmode/sqlmode.go
mysql.Connect abandons the dial and the handshake when the context ends, but the
sql_mode neutralization that follows ran with no bound at all, so a backend that
stalls after the handshake would hang every Vitess connection attempt. Closing the
connection when the context ends now fails the pending exchange, and ConnectTimeoutMs
covers the setup as well as the dial.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkhyn4HQQfBbJkmyPe2Gtm
Signed-off-by: Arthur Schreiber <arthur@planetscale.com>
Copilot AI review requested due to automatic review settings September 3, 2026 22:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

func TestConnectorConnectSetupBoundedByContext(t *testing.T) {
t.Run("context deadline", func(t *testing.T) {
_, params := newStallingServer(t)
ctx, cancel := context.WithTimeout(t.Context(), 200*time.Millisecond)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Sub-second deadlines risk flaky tests

These 200 ms deadlines include TCP setup and a MySQL handshake. Resource-starved runners can time out before reaching the stalled query.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70770ab80e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// The migration statements executed under this variable are Vitess-formatted
// SQL, so modes that change how SQL text is interpreted are rejected the same
// way a vtgate session rejects them (see sqlmode.Validate).
if _, err := sqlmode.Validate(sqltypes.NewVarChar(variable.Value)); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the newly invalid ApplySchema example

When users copy the vtctldclient ApplySchema --ddl-strategy help example in go/cmd/vtctldclient/command/schema.go:442, they are told to use vitess --session-variable sql_mode=ANSI, but this new validation rejects that exact value with setting the ANSI sql_mode is unsupported. Update the advertised example to a supported mode so the documented command remains usable.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. This will be fixed anyway once #20884 lands.

@arthurschreiber
arthurschreiber merged commit 5130e1b into main Sep 3, 2026
112 checks passed
@arthurschreiber
arthurschreiber deleted the arthur/sql-mode-reject-and-neutralize branch September 3, 2026 22:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants