Repository navigation
Conversation
…ailing 2PC Defect (issue #158 follow-up): a MySQL client that named a nonexistent database at handshake, COM_INIT_DB or USE was accepted silently. Its first write then died inside PREPARE with "prepare quorum not achieved: got 0 acks" (1105), because the "database not found" PrepareResult was neither Rejected nor ConflictDetected and so counted as a missing ack. LLDAP hit this on a single node while creating its base tables. Fix - protocol.DatabaseEnsurer: handshake and COM_INIT_DB call EnsureDatabase before the OK packet; USE delegates to it. Connection is registered for drain before the call so GracefulDrain can close a parked connect. - coordinator.EnsureDatabase: exists -> nil; reserved system schemas (information_schema, performance_schema, mysql, sys; case-insensitive) are virtual: accepted, never created; knob off -> MySQL 1049 (42000) "Unknown database"; else CREATE DATABASE IF NOT EXISTS through the normal replicated DDL path. Retry only on typed transient contention (1213 write-write conflict, ErrDDLLockHeld), capped backoff, deadline from the existing DDL validation timeout; everything else fails fast with the underlying error. - HandleQuery fails fast with 1049 for statements naming a nonexistent real database; reserved names are exempt and reach the same path as before. - Config: [mysql] auto_create_database (default true), documented in reference.mdx, config.toml and every example config. - replica.ReadOnlyHandler implements DatabaseEnsurer symmetrically (never creates). - protocol.ErrUnknownDatabase typed error; HandleUseDatabase returns it. Pre-existing defects found by the new concurrency test and fixed - Binary intent keys (0xFF/0xFE-prefixed) were formatted with %s into proto3 string fields and zerolog fields; invalid UTF-8 made the gRPC TransactionResponse unmarshalable, so a conflict on one node was reported as a missing ack. Conflict messages now use %q and log sites use one UTF-8-safe helper (db/meta_store_pebble.go, db/replication_engine.go). - PREPARE "database not found" is now Rejected:true; a database-op intent conflict is ConflictDetected:true (client sees 1213, retryable). - An idempotent no-op CREATE/DROP DATABASE IF [NOT] EXISTS no longer bumps the schema version (CommitResult.IdempotentNoOp -> WriteTransaction returns whether the schema changed). - DDLLockManager comments claimed cluster-wide serialization; it is per-node. Cross-node exclusion is the Pebble intent-key conflict. Tests - Unit: coordinator/auto_create_database_test.go, coordinator/ensure_database_classify_test.go, protocol/database_ensurer_test.go, protocol/handlers/metadata_test.go, db/replication_engine_test.go, db/meta_store_pebble_test.go, replica/handler_test.go, cfg/config_test.go. Every assertion carries a mutation proof re-executed by an independent review. - Integration: TestClusterAutoCreateDatabaseOnConnect, TestClusterAutoCreateDatabaseConcurrentSafety (two nodes, same name, simultaneous connect: exactly one database, equal schema versions), TestClusterAutoCreateDatabaseDisabled. Evidence (tree digest a7df0a3d986bd17f, 504 files) - Unit set 25/25 ok; integration suite 33/33 ok; -race clean on touched packages except two pre-existing races in db/concurrent_conflict_test.go and db/heartbeat_gc_test.go (outside this change). - Reporter's exact flow (LLDAP 0.6.3, no pre-created database) run three times against a binary built from this tree: single node, 3-node cluster, and auto_create_database=false: 3/3 runs, 27/27 assertions each (migrations reach v11, zero quorum errors, exactly one lldap database on every node, 13 tables, knob off yields 1049 and creates nothing). - Two fresh-context reviews closed (9 findings + 4 follow-ups); no CRITICAL/HIGH found. Out of scope, noted: pre-existing vet warnings in db/vector_index_manager_test.go and grpc/forward_session.go; the two db races above; TestInsertReplication is load-sensitive (verified identical timing on master); test-only t.Logf %s on IntentKey in db/insert_or_replace_test.go; SHOW DATABASES LIKE ignores its pattern (unchanged from master). After this change LLDAP proceeds to issue #170 (AUTO_INCREMENT ids exceed i32), which is by design. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SABpZUuBQPtum3EGAhUzrj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #158 / #171. A MySQL client that named a nonexistent database at handshake,
COM_INIT_DBorUSEwas accepted silently; its first write then failed inside PREPARE withprepare quorum not achieved: got 0 acks(1105), because the "database not found" prepare result was neitherRejectednorConflictDetectedand so counted as a missing ack. LLDAP hit this on a single node while creating its base tables.Fix
EnsureDatabase(protocol.DatabaseEnsurer): handshake andCOM_INIT_DBensure the database before the OK packet;USEdelegates to it. The connection is registered for drain before the call soGracefulDraincan close a parked connect.information_schema,performance_schema,mysql,sys, case-insensitive) are virtual: accepted, never created. Knob off → MySQL 1049 (42000)Unknown database. OtherwiseCREATE DATABASE IF NOT EXISTSthrough the normal replicated DDL path.ErrDDLLockHeld) with capped backoff and a deadline from the existing DDL validation timeout; everything else fails fast with the underlying error.HandleQueryfails fast with 1049 for statements naming a nonexistent real database; reserved names are exempt.[mysql] auto_create_database(defaulttrue), documented inreference.mdx,config.tomland every example config.replica.ReadOnlyHandlerimplementsDatabaseEnsurersymmetrically (never creates).Pre-existing defects found by the new concurrency test, fixed here
0xFF/0xFE-prefixed) were formatted with%sinto proto3 string fields and zerolog fields; invalid UTF-8 made the gRPCTransactionResponseunmarshalable, so a conflict on one node surfaced as a missing ack. Conflict messages now use%q; log sites use one UTF-8-safe helper.Rejected:true; a database-op intent conflict isConflictDetected:true(client sees 1213, retryable).CREATE/DROP DATABASE IF [NOT] EXISTSno longer bumps the schema version.DDLLockManagercomments and four doc lines claimed cluster-wide serialization; the lock is per-node. Cross-node exclusion is the Pebble write-intent conflict in PREPARE.Test plan
coordinator/auto_create_database_test.go,coordinator/ensure_database_classify_test.go,protocol/database_ensurer_test.go,protocol/handlers/metadata_test.go,db/replication_engine_test.go,db/meta_store_pebble_test.go,replica/handler_test.go,cfg/config_test.go. Every assertion carries a mutation proof that was re-executed by an independent review.TestClusterAutoCreateDatabaseOnConnect,TestClusterAutoCreateDatabaseConcurrentSafety(two nodes, same name, simultaneous connect: exactly one database, equal schema versions),TestClusterAutoCreateDatabaseDisabled. Full suite 33/33 ok.auto_create_database = false. 3/3 runs, 27/27 assertions each: migrations reach v11, zero quorum errors, exactly onelldapdatabase on every node, 13 tables, knob off yields 1049 and creates nothing. LLDAP then stops onFailed to unpack last_insert_id, which is Generated AUTO_INCREMENT ids overflow 32-bit application id types (blocks LLDAP and similar apps) #170.-raceclean on touched packages except two pre-existing races indb/concurrent_conflict_test.goanddb/heartbeat_gc_test.go(outside this change).Out of scope, noted
Pre-existing vet warnings in
db/vector_index_manager_test.goandgrpc/forward_session.go; the two db races above;TestInsertReplicationis load-sensitive (identical timing on master); test-onlyt.Logf %sonIntentKeyindb/insert_or_replace_test.go;SHOW DATABASES LIKEignores its pattern (unchanged from master).🤖 Generated with Claude Code
https://claude.ai/code/session_01SABpZUuBQPtum3EGAhUzrj