@@ -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
0 commit comments