Skip to content

Commit b9dc8c9

Browse files
committed
Fix PostgreSQL database handling and enhance connection logging in proxy
1 parent bf59a8b commit b9dc8c9

File tree

3 files changed

+58
-25
lines changed

3 files changed

+58
-25
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8282
- Updated test and development scripts to use new configuration format
8383

8484
### Fixed
85+
- **PostgreSQL Database Parameter Handling**: Complete fix for database defaulting logic
86+
- Defaults to "postgres" when client omits database name
87+
- Detects when client auto-fills database with username (common psql behavior)
88+
- Prevents "database does not exist" errors when username contains deployment_id suffix
89+
- Example: `database=postgres.team-1992252154561` now correctly becomes `database=postgres`
90+
- **PostgreSQL Resolver**: Uses correct database type (postgresql) instead of mysql for routing
91+
- **StartupMessage Rebuild**: Simplified logic - now rebuilds on every handshake for consistency
92+
- Every TCP connection gets a fresh StartupMessage
93+
- Eliminates unnecessary optimization that caused edge cases
94+
- Ensures username parsing and database defaulting always work correctly
95+
- **Enhanced Logging**: Added comprehensive logging for connection parameters and username parsing
8596
- Multi-instance TLS certificate creation race conditions
8697
- Kubernetes discovery from non-Kubernetes runtimes
8798
- Certificate lifecycle management issues

cmd/proxy/internal/proxy/postgresql/proxy.go

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,14 @@ func (p *PostgresProxy) handshake(conn net.Conn) (core.RoutingMetadata, net.Conn
213213
value = value[:len(value)-1] // Trim null byte
214214

215215
params[key] = value
216+
logger.Info("StartupMessage param", "key", key, "value", value, "remote_addr", conn.RemoteAddr())
216217
}
217218

218219
// Parse username to extract deployment_id and pool status
220+
// Format: username.deployment_id[.pool]
221+
// Examples:
222+
// alice.db-prod.pool → username=alice, deployment_id=db-prod, pooled=true
223+
// bob.team-1992252154561 → username=bob, deployment_id=team-1992252154561, pooled=false
219224
if user, ok := params["user"]; ok {
220225
logger.Info("Connection requested", "user", user, "remote_addr", conn.RemoteAddr())
221226
parts := strings.Split(user, ".")
@@ -236,28 +241,43 @@ func (p *PostgresProxy) handshake(conn net.Conn) (core.RoutingMetadata, net.Conn
236241
}
237242
}
238243

239-
var rawStartupMsg []byte
244+
// Default database to postgres if not provided OR if it equals the original user
245+
// Some PostgreSQL clients (like psql) automatically use username as database when not specified
246+
// This causes issues when username is "postgres.team-1992252154561" and gets used as database name
247+
// We detect this case and default to "postgres" database instead
248+
originalUser := params["user"]
249+
if dbName, ok := params["database"]; !ok || dbName == "" || dbName == originalUser {
250+
params["database"] = "postgres"
251+
logger.Info("Database defaulted to postgres", "original_db", dbName, "remote_addr", conn.RemoteAddr())
252+
}
240253

241-
if originalUser, ok := params["user"]; ok {
242-
if newUser, ok := params["username"]; ok && newUser != originalUser {
243-
// Rebuild startup message with new username
244-
protocolVersion := binary.BigEndian.Uint32(payload[0:4])
245-
buildParams := make(map[string]string)
246-
for k, v := range params {
247-
if k != "deployment_id" && k != "pooled" && k != "username" {
248-
buildParams[k] = v
249-
}
250-
}
251-
buildParams["user"] = newUser
252-
rawStartupMsg = rebuildStartupMessage(protocolVersion, buildParams)
253-
} else {
254-
// Use original message
255-
rawStartupMsg = make([]byte, len(header)+len(payload))
256-
copy(rawStartupMsg, header)
257-
copy(rawStartupMsg[4:], payload)
254+
// Always rebuild startup message with parsed params
255+
// Every PostgreSQL connection performs a fresh handshake, so we rebuild the StartupMessage
256+
// to send the correct username (without deployment_id/pool suffix) and database to the backend
257+
protocolVersion := binary.BigEndian.Uint32(payload[0:4])
258+
buildParams := make(map[string]string)
259+
260+
// Copy all params except internal metadata (user will be set separately)
261+
// Exclude: deployment_id, pooled, username (internal routing metadata)
262+
// Include: database, client_encoding, application_name, etc.
263+
for k, v := range params {
264+
if k != "deployment_id" && k != "pooled" && k != "username" && k != "user" {
265+
buildParams[k] = v
258266
}
259267
}
260268

269+
// Use parsed username (without deployment_id suffix) or fallback to original
270+
// Backend expects: "alice" not "alice.db-prod.pool"
271+
if username, ok := params["username"]; ok && username != "" {
272+
buildParams["user"] = username
273+
logger.Info("Using parsed username", "username", username, "database", buildParams["database"], "remote_addr", conn.RemoteAddr())
274+
} else if originalUser, ok := params["user"]; ok {
275+
buildParams["user"] = originalUser
276+
logger.Info("Using original username", "user", originalUser, "database", buildParams["database"], "remote_addr", conn.RemoteAddr())
277+
}
278+
279+
// Rebuild the binary StartupMessage packet with modified parameters
280+
rawStartupMsg := rebuildStartupMessage(protocolVersion, buildParams)
261281
return core.RoutingMetadata(params), conn, rawStartupMsg, nil
262282
}
263283

scripts/local-deploy/postgresql.sh

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,20 @@ else
3939
echo "✅ Image should be available locally"
4040
fi
4141

42+
43+
44+
# Delete the DaemonSet to use the new image
45+
echo "D}} Deleting xdatabase-proxy DaemonSet..."
46+
kubectl delete daemonset/xdatabase-proxy -n xdatabase-proxy
47+
48+
sleep 1
49+
4250
# Apply Kubernetes manifests
4351
echo "🚀 Deploying to Kubernetes..."
4452
kubectl apply -f "kubernetes/examples/local-test/postgresql.yaml"
4553

46-
# Wait a moment for the deployment to register
47-
sleep 2
48-
49-
# Restart the DaemonSet to use the new image
50-
echo "🔄 Restarting xdatabase-proxy DaemonSet..."
51-
kubectl rollout restart daemonset/xdatabase-proxy -n xdatabase-proxy
54+
sleep 3
5255

53-
# Wait for rollout to complete
5456
echo "⏳ Waiting for rollout to complete..."
5557
kubectl rollout status daemonset/xdatabase-proxy -n xdatabase-proxy --timeout=120s
5658

0 commit comments

Comments
 (0)