Skip to content

Add MySQL client protocol (text protocol, transactions, prepared statements) on clean-room auth (#2093)#3330

Open
rajvarun77 wants to merge 13 commits into
apache:masterfrom
rajvarun77:mysql-text-protocol-clean-room
Open

Add MySQL client protocol (text protocol, transactions, prepared statements) on clean-room auth (#2093)#3330
rajvarun77 wants to merge 13 commits into
apache:masterfrom
rajvarun77:mysql-text-protocol-clean-room

Conversation

@rajvarun77

Copy link
Copy Markdown
Contributor

Implements a MySQL client protocol for brpc built on a clean-room authentication codec (no GPL lineage), addressing #2093.

Scope

  • Clean-room connection-phase authmysql_native_password and caching_sha2_password (fast-auth + full-auth RSA / secure-transport cleartext), derived from the public MySQL protocol documentation.
  • COM_QUERY text protocol — OK / ERR / EOF / text result-set parsing.
  • Interactive transactions via connection affinity (reserve/use a pooled connection; SINGLE rejected, auto-rollback on drop).
  • Prepared statements (binary protocol) with per-connection stmt_id caching and transparent re-prepare when a statement lands on a fresh connection.

Shared-core footprint (kept redis-like / minimal)

The only generic Controller/Socket additions are: a socket reserve/use hook and fd_version (ABA guard) for transaction/prepared-statement affinity, an auth empty-write guard for MySQL's server-greets-first handshake, and protocol registration. No protocol-specific type lives on the shared Controller (the prepared-statement pointer rides a generic opaque per-RPC slot).

Tests

Clean-room integration tests (transactions, prepared statements, pooled-connection concurrency, connection-type boundary) run against a self-spawned mysqld; 19/19 passing locally. Concurrency coverage includes per-transaction connection pinning under overlap, abort/auto-rollback under contention, and prepared re-prepare when a connection is stolen by a transaction.

🤖 Generated with Claude Code

rajvarun77 and others added 3 commits June 4, 2026 13:07
Clean-room implementation of the MySQL connection-phase authentication handshake,
derived from the public MySQL protocol documentation with no GPL lineage:
mysql_native_password and caching_sha2_password scrambles, HandshakeV10/
HandshakeResponse41 codec, and length-encoded integer/string plus packet-header
wire helpers. Handles the lenenc NULL (0xFB) marker and rejects an oversize
auth_response.
…statements

Port the MySQL protocol client (issue apache#2093) onto the clean-room auth codec and
protobuf 3.21 (NonreflectableMessage): COM_QUERY text protocol, interactive
transactions via connection affinity, and prepared statements. Wire
caching_sha2_password (fast-auth, full-auth RSA, and secure-transport cleartext)
into the live client. Fix the lenenc 9-byte length marker in pack_encode_length
(0xFD -> 0xFE) per the MySQL protocol spec.
… Controller cleanup

- Add clean-room integration tests (transactions, prepared statements, pooled
  connection concurrency, connection-type) run against a self-spawned mysqld.
- Fix: a failed COM_STMT_PREPARE now returns the ERR packet to the caller and keeps
  the connection alive, instead of closing the socket.
- Warn when a prepared statement runs on a 'short' connection (re-prepares on every
  execute; prefer 'pooled').
- Replace Controller's mysql-specific _mysql_stmt with a generic opaque per-RPC slot
  so no protocol type leaks onto the shared Controller.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rajvarun77 rajvarun77 force-pushed the mysql-text-protocol-clean-room branch from b598971 to a292dbc Compare June 4, 2026 19:37
…ted images

Move all MySQL sources (mysql.*, mysql_command/reply/common/transaction/
statement*, mysql_protocol.*, mysql_authenticator.*) into src/brpc/policy/mysql/
alongside the clean-room auth codec; update all includes, build globs, and
install rules. Remove three benchmark images inherited from the apache#2093 port and
the doc section referencing them.

No behavior change: full build green; all 19 mysql unit/integration tests pass;
a 30-case standalone end-to-end run was independently verified against mysqld.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rajvarun77 rajvarun77 force-pushed the mysql-text-protocol-clean-room branch 5 times, most recently from d3989c8 to be457a0 Compare June 4, 2026 22:09
… cases) + ASF headers

- Binary DATETIME/TIME: gate the microsecond bytes on the packet length, not the
  column's declared decimals (over-read / result-set desync).
- COM_STMT_SEND_LONG_DATA: frame stmt_id/param_id inside the packet; fix chunk offset.
- COM_STMT_EXECUTE: emit the trailing 0-length packet for 16MiB-aligned payloads.
- OK/EOF status & warnings: decode via mysql_uint2korr (big-endian safe).
- Row NULL-bitmap: arena-allocate instead of a stack VLA; cap column_count.
- Auth: bounds-check the parsed auth string; size-bound StringPiece uses.
- Prepared stmt: prune stale per-socket stmt_id map entries; count only real '?'
  placeholders (skip quotes/comments).
- MysqlResponse::Clear and MysqlRequest copy/Swap: reset/copy all members.
- Controller::ResetPods: release _bind_sock on controller reuse.
- Standardize mysql file license headers to the ASF form; clarify auth comment.

All 19 unit/integration tests still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rajvarun77 rajvarun77 force-pushed the mysql-text-protocol-clean-room branch from be457a0 to 0bb55ee Compare June 4, 2026 22:16
@rajvarun77 rajvarun77 marked this pull request as ready for review June 4, 2026 22:20
@rajvarun77

Copy link
Copy Markdown
Contributor Author

@chenBright @wwbmmm — marking this ready for review.

Full MySQL client protocol for brpc — COM_QUERY text protocol, interactive transactions, and prepared statements — built on a clean-room authentication codec (no GPL lineage), addressing #2093.

  • Auth (clean-room): mysql_native_password + caching_sha2_password (fast-auth + full-auth RSA / secure-transport cleartext), derived from the public MySQL protocol docs.
  • Minimal shared-core footprint (redis-like): the only generic Controller/Socket additions are a socket reserve/use hook + fd_version (ABA guard) for transaction/prepared-statement affinity, an auth empty-write guard for the server-greets-first handshake, and protocol registration. No protocol-specific type lives on the shared Controller.
  • Tests: clean-room integration tests (transactions, prepared statements, pooled-connection concurrency, connection-type, binary TIME/DATETIME) run against a self-spawned mysqld — 20/20 passing locally; a standalone 30-case end-to-end run was independently verified.

The clean-room auth codec is also up as a smaller, focused PR (#3310, Stage 1a) if you'd prefer to review that foundation first.

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.

Pull request overview

This PR adds a first-class MySQL client protocol implementation to brpc, including clean-room authentication, text protocol querying, transaction connection-affinity, and prepared statements support, along with new integration/unit tests and examples.

Changes:

  • Introduce PROTOCOL_MYSQL and register a MySQL client protocol stack (pack/parse/process) with connection-phase authentication handling.
  • Add MySQL client APIs (MysqlRequest, MysqlResponse, prepared statements, transactions) and supporting wire/protocol utilities.
  • Add MySQL-focused tests, docs, and examples; extend build/test integration (CMake/Bazel/Makefile).

Reviewed changes

Copilot reviewed 50 out of 50 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/mysql/README.md Documents auth-handshake integration test modes and setup.
test/mysql/brpc_mysql_connection_type_unittest.cpp Adds integration test for SHORT connection behavior with prepared statements and plain queries.
test/mysql/brpc_mysql_auth_packet_unittest.cpp Adds unit tests for MySQL auth packet wire helpers (lenenc ints/strings, headers).
test/CMakeLists.txt Ensures MySQL unit tests are included in the CMake test glob.
test/BUILD.bazel Adds a Bazel cc_test target covering MySQL unit tests.
src/brpc/socket.h Exposes fd_version() for ABA-guarding per-connection statement caching.
src/brpc/socket.cpp Increments fd version on fd reset; allows empty writes for auth-handshake flows.
src/brpc/policy/mysql/mysql.h Introduces MySQL request/response message types and statement stub plumbing.
src/brpc/policy/mysql/mysql.cpp Implements MysqlRequest/MysqlResponse serialization and parsing entrypoints.
src/brpc/policy/mysql/mysql_transaction.h Adds MySQL transaction API and options for connection-affinity usage.
src/brpc/policy/mysql/mysql_transaction.cpp Implements transaction start/commit/rollback with socket reserve/use semantics.
src/brpc/policy/mysql/mysql_statement.h Adds prepared statement API and statement-id caching interface.
src/brpc/policy/mysql/mysql_statement.cpp Implements stmt-id cache keyed by socket id + fd version; placeholder counting.
src/brpc/policy/mysql/mysql_statement_inl.h Provides DBD map helpers for stmt-id caching and related gflag.
src/brpc/policy/mysql/mysql_protocol.h Declares MySQL protocol functions (parse/process/serialize/pack).
src/brpc/policy/mysql/mysql_protocol.cpp Implements client-side MySQL protocol codec, including auth handshake and prepare/execute flows.
src/brpc/policy/mysql/mysql_common.h Adds MySQL constants/types and collation mapping used by the client implementation.
src/brpc/policy/mysql/mysql_common.cpp Defines common MySQL defaults and type-to-string helper.
src/brpc/policy/mysql/mysql_command.h Declares MySQL command packet builders for query/prepare/execute/long-data.
src/brpc/policy/mysql/mysql_command.cpp Implements MySQL packetization and prepared statement wire builders.
src/brpc/policy/mysql/mysql_authenticator.h Adds MysqlAuthenticator carrying user/password/schema/params/collation.
src/brpc/policy/mysql/mysql_authenticator.cpp Implements credential serialization and clean-room scrambles (native + caching_sha2 fast path).
src/brpc/policy/mysql/mysql_auth_scramble.h Declares clean-room auth scramble and RSA/cleartext helpers.
src/brpc/policy/mysql/mysql_auth_scramble.cpp Implements native and caching_sha2 scrambles + RSA-OAEP encryption with OpenSSL EVP.
src/brpc/policy/mysql/mysql_auth_packet.h Adds wire helpers for lenenc ints/strings, packet headers, NUL strings.
src/brpc/policy/mysql/mysql_auth_packet.cpp Implements the MySQL wire-format helpers used by handshake parsing/building.
src/brpc/policy/mysql/mysql_auth_handshake.h Declares clean-room handshake packet codecs (greeting/response/switch/more-data).
src/brpc/policy/mysql/mysql_auth_handshake.cpp Implements parsing/building for connection-phase handshake payloads.
src/brpc/options.proto Adds PROTOCOL_MYSQL enum value.
src/brpc/global.cpp Registers the MySQL protocol with the global protocol registry.
src/brpc/details/controller_private_accessor.h Adds private controller hooks for socket reserve/use and opaque session data.
src/brpc/controller.h Introduces bind-socket actions/state to support MySQL transaction affinity and session data.
src/brpc/controller.cpp Implements socket reserve/use behavior across RPCs for pooled/short connection types.
Makefile Includes src/brpc/policy/mysql in the build directories list.
example/mysql_c++/mysqlclient_press.cpp Adds libmysqlclient-based press tool (comparison baseline).
example/mysql_c++/mysql_tx.cpp Adds brpc MySQL transaction example.
example/mysql_c++/mysql_stmt.cpp Adds brpc MySQL prepared statement example.
example/mysql_c++/mysql_press.cpp Adds brpc MySQL press example for CRUD.
example/mysql_c++/mysql_go_press.go Adds Go MySQL press example (comparison baseline).
example/mysql_c++/mysql_cli.cpp Adds brpc-based interactive MySQL CLI example.
example/mysql_c++/CMakeLists.txt Adds CMake build for MySQL examples and dependency discovery.
docs/cn/mysql_client.md Adds/updates Chinese documentation describing brpc MySQL client usage.
Comments suppressed due to low confidence (1)

src/brpc/policy/mysql/mysql_authenticator.h:92

  • The trailing #endif comment refers to COUCHBASE, which doesn’t match this header guard and is confusing when grepping/include-debugging. Update it to BRPC_POLICY_MYSQL_AUTHENTICATOR_H.
}  // namespace policy
}  // namespace brpc

#endif  // BRPC_POLICY_COUCHBASE_AUTHENTICATOR_H


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/brpc/policy/mysql/mysql_common.h Outdated
Comment thread src/brpc/policy/mysql/mysql.cpp
Comment thread test/mysql/brpc_mysql_connection_type_unittest.cpp Outdated
Comment thread docs/cn/mysql_client.md Outdated
Comment thread src/brpc/policy/mysql/mysql_statement.cpp Outdated
rajvarun77 and others added 2 commits June 5, 2026 22:01
…er-global, doc/comment drift

- example/mysql_c++/mysql_go_press.go: add ASF Apache-2.0 license header
  (the last file failing the License Check; other 6 already had headers).
- mysql_common.{h,cpp}: move the MysqlCollations map definition out of the
  header into the .cpp behind an `extern` declaration, so each translation
  unit no longer gets its own copy (C++11-safe; avoids the header-defined
  global flagged in review).
- mysql_statement.{cpp,inl.h}: rename the misspelled gflag
  mysql_statment_map_size -> mysql_statement_map_size (user-facing name).
- docs/cn/mysql_client.md: prepared statements ARE supported now — drop the
  stale "不支持Prepared statement".
- brpc_mysql_connection_type_unittest.cpp: rewrite the stale header comment
  that described a removed "MustError" test; the prepared-statement path now
  transparently re-prepares under CONNECTION_TYPE_SHORT and succeeds, matching
  the actual PreparedStatementUnderShortRePreparesAndSucceeds test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iles

The new MySQL example sources and brpc_mysql_unittest.cpp carried the old
"Copyright (c) Baidu, Inc." Apache-2.0 header, which skywalking-eyes (the
repo's License Check, configured copyright-owner = Apache Software Foundation)
does not accept — so all 7 files failed the gate. Replace with the canonical
ASF header used by the other 549 sources in the tree, and drop the stale
Baidu copyright/date attribution lines. Verified locally with
`license-eye -c .licenserc.yaml header check`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread src/brpc/policy/mysql/mysql.h Outdated
Comment thread src/brpc/policy/mysql/mysql.h Outdated
Comment thread src/brpc/socket.cpp
// MUST store `_fd' before adding itself into epoll device to avoid
// race conditions with the callback function inside epoll
static butil::atomic<uint64_t> BAIDU_CACHELINE_ALIGNMENT fd_version(0);
_fd_version.store(fd_version.fetch_add(1, butil::memory_order_relaxed),

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.

It seems that this _fd_version doesn't need to be atomic, since the static fd_version is atomic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks — you're right that the uniqueness of the value comes from the static fd_version atomic. But the per-Socket _fd_version still needs to be atomic for a different reason: it is written and read from different threads with no lock between them.

  • Writer: ResetFileDescriptor() stores _fd_version on the reconnect path (CheckConnectedAndKeepWrite -> ResetFileDescriptor), with no socket mutex held.
  • Reader: the mysql prepared-statement path reads socket->fd_version() on the RPC bthread (MysqlStatement::StatementId). It obtains the socket via Socket::Address(), which only gates on the SocketId version — and ResetFileDescriptor does not bump that version — so Address() succeeds even while the fd is being reset. The read can therefore overlap the write on the same Socket.

A concurrent non-atomic read/write on the same object is a data race (UB), and a 64-bit load is not tear-free on 32-bit targets, so the member has to be atomic. This mirrors _fd itself, which is a butil::atomic<int> written in the same ResetFileDescriptor and read lock-free (memory_order_relaxed) all over the IO path. I kept _fd_version on the same relaxed-atomic pattern. Happy to add a brief comment to that effect if it helps.

Comment thread src/brpc/controller.h Outdated
Comment thread src/brpc/policy/mysql/mysql_protocol.cpp
rajvarun77 and others added 6 commits June 8, 2026 21:20
Extend the failure-path logging from the auth codec to the full client
protocol. Parsers and request/response handlers returned false/0/nullptr
silently on malformed wire data, truncated packets, bad state, or missing
prepared statements, giving no clue why a query/connection failed. Add a
LOG(ERROR) at each silent failure path naming the function and the concrete
cause.

- mysql_auth_handshake/packet.cpp: handshake + lenenc codec failure paths
  (truncated <field>, pre-4.1 server, reserved 0xFF marker, length mismatch).
- mysql.cpp: request command/param guards.
- mysql_reply.cpp: result-set parse (column/row/error packet truncation,
  arena alloc failure, bad binary-row header).
- mysql_protocol.cpp: serialize/process type + serialization failures.
- mysql_statement.cpp: statement-id lookup misses (not-prepared / stale conn).

Logic unchanged — logs inserted only. Normal control-flow returns
(need-more-data, lenenc NULL 0xFB, short-connection no-cache) are not logged.
mysql_authenticator.cpp and mysql_transaction.cpp already logged every
failure path and were left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tune the severity of the failure-path logs. LOG(ERROR) is reserved for
total failures — handshake/connection corruption, authentication failure,
out-of-memory, and fundamental request/response type or serialization
misuse. Operational and statement-data failures are demoted to
LOG(WARNING):

- mysql_auth_packet.cpp: lenenc int/string/header decode failures (these
  fire during normal resultset parsing).
- mysql.cpp: request command/param API guards.
- mysql_reply.cpp: column/row/field/ERR-packet truncation (statement data).
  The arena out-of-memory and Auth::Parse auth-plugin failures stay ERROR.
- mysql_statement.cpp: prepared-statement-id lookup misses (recoverable,
  trigger a re-prepare).

handshake parsing and the protocol serialize/process type checks remain
LOG(ERROR). Severity-only change; messages and logic unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI compiles unittests with -std=c++0x (C++11), where a class with a default
member initializer is not an aggregate, so g_auth_cases.push_back({...})
fails to compile. Drop the default member initializer on AuthCase::use_ssl;
all init sites pass it explicitly. Mirrors the same fix on the apache#3310 codec
branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sqld

The MySQL integration tests were not exercising a live server in CI:

- Merge master to pick up apache#3323, which installs mysql-server in the
  install-essential-dependencies action used by the unittest lanes, so the
  mysqld binary is present for the self-spawning test/mysql/* suites.
- Delete the legacy test/brpc_mysql_unittest.cpp: it hardcoded an external
  host (db4free.net) with embedded credentials and asserted on connect
  failure (no skip), so it timed out and hard-failed in CI. The
  test/mysql/*_integration suites supersede it, spawning a throwaway local
  mysqld and GTEST_SKIP-ing when none is available (the redis precedent).
- Build the test/mysql/* suites in the make lane: extend test/Makefile's
  source glob and add a mysql/-prefixed link rule; add the subdir to
  run_tests.sh with nullglob so it runs them.
- Bazel: build one cc_test per mysql test file via generate_unittests
  instead of globbing them all into a single brpc_mysql_test target, which
  duplicated main() and the FLAGS_mysql_* definitions (ld: duplicate
  symbol). Drop the per-file main()s so every suite relies on gtest_main,
  matching brpc_redis_unittest; tag the server-spawning suites
  external+local so bazel runs them unsandboxed with the real mysqld.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Controller: store the mysql-transaction BindSockAction in two bits of
  _flags (FLAGS_BIND_SOCK_RESERVE / FLAGS_BIND_SOCK_USE) via
  set_bind_sock_action()/bind_sock_action(), instead of a dedicated member,
  per review. No behavior change.
- MysqlResponse: make it explicitly non-copyable (= delete copy ctor and
  assignment) and turn the previously no-op MergeFrom into a hard
  CHECK-failure, so an accidental copy/CopyFrom is caught instead of
  silently dropping parsed replies.
- MysqlRequest: rename the trivial getters get_tx()/get_stmt() to
  tx()/stmt() per review.
- ControllerPrivateAccessor: add set_mysql_statement_type() as a clearly
  named alias over the pipelined_count slot the mysql protocol reuses, and
  call it from the mysql protocol instead of set_pipelined_count() directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@rajvarun77

Copy link
Copy Markdown
Contributor Author

@wwbmmm @chenBright — pushed a revision addressing the review feedback and fixing the integration-test setup. Summary:

Review comments

  • Stored the mysql-transaction BindSockAction in two Controller::_flags bits instead of a dedicated member.
  • Made MysqlResponse explicitly non-copyable and turned the previously no-op MergeFrom into a hard CHECK-failure (so an accidental copy is caught instead of silently dropping replies).
  • Renamed get_tx()/get_stmt() to tx()/stmt().
  • Added a clearly named ControllerPrivateAccessor::set_mysql_statement_type() alias over the pipelined_count slot the mysql protocol reuses.
  • The MysqlCollations ODR note, the gflag spelling, and the two doc/comment mismatches were already addressed in the current revision.
  • On _fd_version: I left a reply explaining why it needs to stay atomic — it is written in ResetFileDescriptor (reconnect path, no lock) and read lock-free from another thread via fd_version() in the prepared-statement path, so a non-atomic field would be a data race. It mirrors how _fd itself is handled. Happy to discuss further.

Integration tests now run against a real MySQL server

  • The suites previously did not exercise a live server in CI: a legacy test pointed at an external host and hard-failed, and the make/bazel wiring built it as a single globbed target. I removed the legacy test, switched the test/mysql/* suites to the redis-style self-spawning + skip-if-absent pattern, build one cc_test per file, and merged master so the CI image installs mysql-server. The suites now spawn a local throwaway mysqld and run.

Local verification
I built and ran all seven mysql suites locally against a live MySQL 9.6 server: 110 tests, 0 skipped, 0 failed — covering caching_sha2_password auth, the text and binary (prepared-statement) protocols, transactions, and connection pooling. (MySQL 9.x removed mysql_native_password, so this also exercises the caching_sha2 path end-to-end.)

CI is running on the latest revision. Could you take another look when you have a chance? Thanks for the reviews.

@rajvarun77

Copy link
Copy Markdown
Contributor Author

@wwbmmm @chenBright — heads-up on CI: the two *-unittest-with-bazel lanes show one failure, and it is an unrelated flake, not a regression from this PR.

  • The failing target is //test:brpc_channel_unittest, and within it a single timing-sensitive case, ChannelTest.backup_request, hung and blew the 300s test timeout (the timeout stack trace points at ChannelTest::TestBackupRequest()). It is a timeout, not an assertion failure.
  • The same commit passes that test in the make lanes (clang-unittest and clang-unittest-asan are green), and all seven //test:mysql/* targets pass in the bazel lanes.
  • It is independent of this change: the new flags use _flags bits 14/15 while backup-request uses FLAGS_BACKUP_REQUEST (bit 5) — no collision — and the backup-request path never touches bind_sock_action().

A re-run of the failed jobs should clear it (I cannot trigger re-runs as an outside contributor).

This PR is ready for another look whenever you have time: all review threads are addressed (I left one reply on _fd_version explaining why it stays atomic), the mysql integration suites now run against a real CI-provisioned mysqld, and I verified all seven suites locally against a live MySQL 9.6 (110 tests, 0 skipped, 0 failed). Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants