MySQL Wire Protocol Server
Duckling includes a MySQL-compatible wire protocol server that allows you to query DuckDB replicas using any MySQL client (mysql CLI, DBeaver, Sequelize, phpMyAdmin, etc.).Overview
The protocol server implements the MySQL wire protocol usingmysql2’s built-in server support. Clients connect to a dedicated TCP port (default: 3307) and run read-only queries against replicated DuckDB data.
Architecture:
Environment Variables
Enable MySQL wire protocol server
TCP port for MySQL protocol server (avoid 3306 conflict with source MySQL)
Username for MySQL authentication
Password for MySQL authentication (uses
mysql_native_password AUTH 41)Maximum concurrent client connections
Default database ID if client doesn’t specify one
Configuration Example
.env
Connecting with MySQL Clients
MySQL CLI
DBeaver
- Create new connection → MySQL
- Host:
localhost, Port:3307 - Username:
duckling, Password:<your-password> - Database:
production(or leave blank for default) - Test Connection → Connect
Node.js (mysql2)
Sequelize ORM
Supported MySQL Features
Handshake & Authentication
- Protocol Version: 10 (MySQL 5.x/8.x compatible)
- Server Version:
8.0.32-Duckling - Authentication:
mysql_native_password(SHA1-based challenge-response) - Character Set:
utf8mb4_general_ci(45)
Capability Flags
The server advertises these MySQL capabilities:Supported Commands
| Command | Support | Notes |
|---|---|---|
COM_QUERY | ✅ Full | Execute SQL queries |
COM_INIT_DB | ✅ Full | USE <database> command |
COM_PING | ✅ Full | Keepalive check |
COM_QUIT | ✅ Full | Close connection |
COM_STMT_PREPARE | ❌ Not supported | Returns error 1295 |
COM_STMT_EXECUTE | ❌ Not supported | Returns error 1295 |
Prepared Statements: Not currently supported. Use text queries instead. Most ORMs fallback to text queries automatically.
Query Routing
The protocol server includes a smart SQL router that intercepts certain queries:Intercepted Queries
These queries are handled by the router (not forwarded to DuckDB):Forwarded to DuckDB
All other queries are forwarded to DuckDB:Multi-Database Support
The protocol server supports switching between databases:USE Command
databases.json:
Connection String Database
Connection Management
Idle Timeout
Connections are automatically closed after 8 hours of inactivity:Connection Limit
When max connections reached:MYSQL_PROTOCOL_MAX_CONNECTIONS environment variable.
Active Connections
Query active connection count:Result Set Formatting
DuckDB results are converted to MySQL wire protocol format:Type Mapping
| DuckDB Type | MySQL Column Type | Notes |
|---|---|---|
INTEGER | MYSQL_TYPE_LONG | 32-bit signed |
BIGINT | MYSQL_TYPE_LONGLONG | 64-bit signed |
DOUBLE | MYSQL_TYPE_DOUBLE | IEEE 754 double |
VARCHAR | MYSQL_TYPE_VAR_STRING | UTF-8 string |
DATE | MYSQL_TYPE_DATE | YYYY-MM-DD |
TIMESTAMP | MYSQL_TYPE_TIMESTAMP | YYYY-MM-DD HH:MM:SS |
BOOLEAN | MYSQL_TYPE_TINY | 0 or 1 |
BLOB | MYSQL_TYPE_BLOB | Binary data |
NULL Handling
NULL values are encoded using MySQL’s NULL bitmap in result packets.Performance
Benchmarks
| Query Type | Latency | Throughput |
|---|---|---|
| Simple SELECT (1 row) | ~5ms | 200 qps/connection |
| Aggregation (1M rows) | ~100ms | 10 qps/connection |
| Complex JOIN | ~500ms | 2 qps/connection |
Performance Tip: The protocol server adds ~2-3ms overhead for result formatting. For high-throughput applications, use the REST API (
/api/query) or WebSocket SDK instead.Security
Authentication
Usesmysql_native_password (SHA1-based challenge-response):
- Server sends 20-byte random challenge
- Client computes:
SHA1(challenge + SHA1(SHA1(password))) - Server verifies token using stored
doubleSha1(password)
Read-Only Mode
All queries are read-only. Write operations return errors:SSL/TLS
SSH Tunnel Example:Compatibility
Tested Clients
| Client | Version | Status | Notes |
|---|---|---|---|
| MySQL CLI | 8.0+ | ✅ Works | Full compatibility |
| DBeaver | 23.0+ | ✅ Works | Minor metadata quirks |
| TablePlus | 5.0+ | ✅ Works | Requires handshake patch |
| Sequelize | 6.0+ | ✅ Works | Use text queries |
| mysql2 (Node) | 3.0+ | ✅ Works | Full compatibility |
| phpMyAdmin | 5.0+ | ⚠️ Partial | Metadata queries fail |
Known Issues
TablePlus Handshake: TablePlus sends unsupported auth flags that crash the parser. A monkey-patch sanitizes the handshake:INFORMATION_SCHEMA tables that don’t exist in DuckDB. These fail gracefully:
Troubleshooting
Connection Refused
- Check
MYSQL_PROTOCOL_ENABLED=truein.env - Restart server:
docker-compose restart duckling-server - Check port is not blocked:
netstat -tuln | grep 3307
Authentication Failed
- Verify password in
.envmatches client password - Check username is correct (
MYSQL_PROTOCOL_USERNAME) - Check server logs:
docker-compose logs -f duckling-server | grep "MySQL protocol"
Sequence Warnings
If you see sequence warnings in logs:Code Reference
Implementation:packages/server/src/services/mysqlProtocolServer.ts
Key Classes:
MySQLProtocolServer- Main server class (singleton)ConnectionState- Per-connection state tracking
start()- Start MySQL protocol server (line 206)handleConnection()- Accept new client (line 267)authenticate()- Verify credentials (line 351)handleQuery()- Execute SQL query (line 413)handleInitDb()- Switch database (line 492)
mysqlQueryRouter.ts- SQL query routing logicmysqlResultFormatter.ts- DuckDB → MySQL type conversionmysqlProtocolUtils.ts- Auth, flags, logging utilities