Add MySQL client protocol (text protocol, transactions, prepared statements) on clean-room auth (#2093)#3330
Conversation
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>
b598971 to
a292dbc
Compare
…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>
d3989c8 to
be457a0
Compare
… 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>
be457a0 to
0bb55ee
Compare
|
@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.
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. |
There was a problem hiding this comment.
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_MYSQLand 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
#endifcomment refers to COUCHBASE, which doesn’t match this header guard and is confusing when grepping/include-debugging. Update it toBRPC_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.
…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>
| // 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), |
There was a problem hiding this comment.
It seems that this _fd_version doesn't need to be atomic, since the static fd_version is atomic.
There was a problem hiding this comment.
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_versionon 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 viaSocket::Address(), which only gates on theSocketIdversion — andResetFileDescriptordoes not bump that version — soAddress()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.
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>
|
@wwbmmm @chenBright — pushed a revision addressing the review feedback and fixing the integration-test setup. Summary: Review comments
Integration tests now run against a real MySQL server
Local verification CI is running on the latest revision. Could you take another look when you have a chance? Thanks for the reviews. |
|
@wwbmmm @chenBright — heads-up on CI: the two
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 |
Implements a MySQL client protocol for brpc built on a clean-room authentication codec (no GPL lineage), addressing #2093.
Scope
mysql_native_passwordandcaching_sha2_password(fast-auth + full-auth RSA / secure-transport cleartext), derived from the public MySQL protocol documentation.stmt_idcaching and transparent re-prepare when a statement lands on a fresh connection.Shared-core footprint (kept redis-like / minimal)
The only generic
Controller/Socketadditions are: a socket reserve/use hook andfd_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 sharedController(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