Repository: pg-sharding/spqr
Pull request #1168: spqr-ping
Author: login: Denchick
Created: 2025-05-05T16:02:32Z | Merged: 2025-05-12T11:55:14Z

The main idea is to add a fast ping handler without tls requirements.

```
➜  spqr git:(ping-idea) ✗ psql "host=localhost port=8432 dbname=spqr-ping user=spqr-ping sslmode=disable"
psql (14.17 (Homebrew), server 0.0.0)
Type "help" for help.

spqr-ping=> --fweq;
spqr-ping=> ;
spqr-ping=?> ;
no connection to the server
The connection to the server was lost. Attempting reset: Succeeded.
psql (14.17 (Homebrew), server 0.0.0)
spqr-ping=> qwef;
spqr-ping=?> fweqf;
no connection to the server
The connection to the server was lost. Attempting reset: Succeeded.
psql (14.17 (Homebrew), server 0.0.0)
spqr-ping=> ;fqwe
spqr-ping-?> ;fqw
no connection to the server
The connection to the server was lost. Attempting reset: Succeeded.
psql (14.17 (Homebrew), server 0.0.0)
spqr-ping-> 
```


Diff:
diff --git a/pkg/config/router.go b/pkg/config/router.go
index e6ea55f81..0c8364cfc 100644
--- a/pkg/config/router.go
+++ b/pkg/config/router.go
@@ -23,6 +23,7 @@ type DefaultRouteBehaviour string
 const (
 	PoolModeSession     = PoolMode("SESSION")
 	PoolModeTransaction = PoolMode("TRANSACTION")
+	PoolModeVirtual     = PoolMode("VIRTUAL")
 
 	DataShard  = ShardType("DATA")
 	WorldShard = ShardType("WORLD")
diff --git a/pkg/config/tls.go b/pkg/config/tls.go
index 7fbcbfeee..e50d8625f 100644
--- a/pkg/config/tls.go
+++ b/pkg/config/tls.go
@@ -125,7 +125,7 @@ func (c *TLSConfig) Init(host string) (*tls.Config, error) {
 		spqrlog.Zero.Debug().
 			Str("cert_file", c.CertFile).
 			Str("key_file", c.KeyFile).
-			Msg("loading tls ")
+			Msg("loading tls")
 		cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
 		if err != nil {
 			return nil, fmt.Errorf("unable to X509 key pair: %w", err)
diff --git a/router/client/client.go b/router/client/client.go
index 209c57844..b7f3e14a0 100644
--- a/router/client/client.go
+++ b/router/client/client.go
@@ -681,6 +681,8 @@ func (cl *PsqlClient) AssignRule(rule *config.FrontendRule) error {
 	return nil
 }
 
+const pingRoute = "spqr-ping"
+
 // startup + ssl/cancel
 func (cl *PsqlClient) Init(tlsconfig *tls.Config) error {
 	for {
@@ -793,6 +795,10 @@ func (cl *PsqlClient) Init(tlsconfig *tls.Config) error {
 			Uint32("cancel_key", cl.cancel_key).
 			Uint32("cancel_pid", cl.cancel_pid)
 
+		if cl.DB() == pingRoute && cl.Usr() == pingRoute {
+			return nil
+		}
+
 		if tlsconfig != nil && protoVer != conn.SSLREQ {
 			if err := cl.Send(
 				&pgproto3.ErrorResponse{
@@ -841,19 +847,21 @@ func (cl *PsqlClient) Auth(rt *route.Route) error {
 		Str("db", cl.DB()).
 		Msg("client connection for rule accepted")
 
-	ps, err := rt.Params()
-	if err != nil {
-		spqrlog.Zero.Error().Err(err).Msg("")
-		return err
-	}
-
-	for key, msg := range ps {
-		if err := cl.Send(&pgproto3.ParameterStatus{
-			Name:  key,
-			Value: msg,
-		}); err != nil {
+	/* XXX: generate defaults for virtual pool too */
+	if cl.Rule().PoolMode != config.PoolModeVirtual {
+		ps, err := rt.Params()
+		if err != nil {
+			spqrlog.Zero.Error().Err(err).Msg("")
 			return err
 		}
+		for key, msg := range ps {
+			if err := cl.Send(&pgproto3.ParameterStatus{
+				Name:  key,
+				Value: msg,
+			}); err != nil {
+				return err
+			}
+		}
 	}
 
 	if err := cl.Send(&pgproto3.BackendKeyData{
diff --git a/router/poolmgr/pool_mgr.go b/router/poolmgr/pool_mgr.go
index 773773243..e62839f88 100644
--- a/router/poolmgr/pool_mgr.go
+++ b/router/poolmgr/pool_mgr.go
@@ -153,6 +153,34 @@ func NewSessConnManager() *SessConnManager {
 	return &SessConnManager{}
 }
 
+type VirtualConnManager struct {
+}
+
+// ConnectionActive implements PoolMgr.
+func (v *VirtualConnManager) ConnectionActive(rst ConnectionKeeper) bool {
+	return true
+}
+
+// TXEndCB implements PoolMgr.
+func (v *VirtualConnManager) TXEndCB(rst ConnectionKeeper) error {
+	return nil
+}
+
+// UnRouteCB implements PoolMgr.
+func (v *VirtualConnManager) UnRouteCB(client client.RouterClient, sh []kr.ShardKey) error {
+	return nil
+}
+
+// UnRouteWithError implements PoolMgr.
+func (v *VirtualConnManager) UnRouteWithError(client client.RouterClient, sh []kr.ShardKey, errmsg error) error {
+	return unRouteWithError(v, client, sh, errmsg)
+}
+
+// ValidateReRoute implements PoolMgr.
+func (v *VirtualConnManager) ValidateReRoute(rst ConnectionKeeper) bool {
+	return false
+}
+
 // TODO : unit tests
 func MatchConnectionPooler(client client.RouterClient) (PoolMgr, error) {
 	switch client.Rule().PoolMode {
@@ -160,10 +188,12 @@ func MatchConnectionPooler(client client.RouterClient) (PoolMgr, error) {
 		return NewSessConnManager(), nil
 	case config.PoolModeTransaction:
 		return NewTxConnManager(), nil
+	case config.PoolModeVirtual:
+		return &VirtualConnManager{}, nil
 	default:
 		for _, msg := range []pgproto3.BackendMessage{
 			&pgproto3.ErrorResponse{
-				Message:  fmt.Sprintf("unknown pool mode for route %v", client.ID()),
+				Message:  fmt.Sprintf("unknown route pool mode for client %v", client.ID()),
 				Severity: "ERROR",
 			},
 		} {
diff --git a/router/relay/qstate.go b/router/relay/qstate.go
index 30f565575..9859fe860 100644
--- a/router/relay/qstate.go
+++ b/router/relay/qstate.go
@@ -97,6 +97,8 @@ func ProcQueryAdvancedTx(rst RelayStateMgr, query string, binderQ func() error,
 
 	/* outer function will complete relay here */
 
+	spqrlog.Zero.Debug().Err(err).Uint("client-id", rst.Client().ID()).Msg("completing client relay with error")
+
 	switch err {
 	case io.ErrUnexpectedEOF:
 		fallthrough
diff --git a/router/relay/relay.go b/router/relay/relay.go
index 016d7d0e0..7e4f225f9 100644
--- a/router/relay/relay.go
+++ b/router/relay/relay.go
@@ -572,7 +572,9 @@ func (rst *RelayStateImpl) expandRoutes(routes []*kr.ShardKey) error {
 		return nil
 	}
 
-	if rst.Client().Server().TxStatus() == txstatus.TXERR {
+	serv := rst.Client().Server()
+
+	if serv == nil || serv.TxStatus() == txstatus.TXERR {
 		/* should never happen */
 		return fmt.Errorf("unexpected server expand request")
 	}
@@ -662,6 +664,16 @@ func (rst *RelayStateImpl) Reroute() ([]*kr.ShardKey, plan.Plan, error) {
 
 	rst.routingState = queryPlan
 
+	if rst.Client().Rule().PoolMode == config.PoolModeVirtual {
+		/* never try to get connection */
+		switch queryPlan.(type) {
+		case plan.VirtualPlan:
+			return nil, queryPlan, nil
+		default:
+			return nil, nil, fmt.Errorf("query processing for this client is disabled")
+		}
+	}
+
 	switch v := queryPlan.(type) {
 	case plan.VirtualPlan:
 		/* XXX: connect to routes in subplan */
@@ -1585,7 +1597,16 @@ func (rst *RelayStateImpl) PrepareRelayStepOnAnyRoute() (func() error, error) {
 
 // TODO : unit tests
 func (rst *RelayStateImpl) ProcessMessageBuf(waitForResp, replyCl bool) error {
+
+	/* XXX: tx management is handled outside this call, so do not complete relay here */
+
+	spqrlog.Zero.Debug().
+		Uint("client", rst.Client().ID()).
+		Msg("relay step: process message buf for client")
 	if _, err := rst.PrepareRelayStep(); err != nil {
+		/* some critical connection issue, client processing cannot be competed.
+		* empty our msg buf */
+		rst.msgBuf = nil
 		return err
 	}
 
@@ -1603,6 +1624,10 @@ func (rst *RelayStateImpl) ProcessMessage(
 		Uint("client", rst.Client().ID()).
 		Msg("relay step: process message for client")
 	if _, err := rst.PrepareRelayStep(); err != nil {
+		if err := rst.CompleteRelay(replyCl); err != nil {
+			spqrlog.Zero.Error().Err(err).Uint("client", rst.Client().ID()).Msg("failed to complete relay")
+			return err
+		}
 		return err
 	}
 
@@ -1610,7 +1635,7 @@ func (rst *RelayStateImpl) ProcessMessage(
 
 	if _, err := rst.RelayStep(msg, waitForResp, replyCl); err != nil {
 		if err := rst.CompleteRelay(replyCl); err != nil {
-			spqrlog.Zero.Error().Err(err).Msg("failed to complete relay")
+			spqrlog.Zero.Error().Err(err).Uint("client", rst.Client().ID()).Msg("failed to complete relay")
 			return err
 		}
 		return err
diff --git a/router/rulerouter/rulerouter.go b/router/rulerouter/rulerouter.go
index 839e1900a..0a2dc8e43 100644
--- a/router/rulerouter/rulerouter.go
+++ b/router/rulerouter/rulerouter.go
@@ -182,6 +182,20 @@ func (r *RuleRouterImpl) PreRoute(conn net.Conn, pt port.RouterPortType) (rclien
 		return cl, nil
 	}
 
+	if cl.Usr() == "spqr-ping" && cl.DB() == "spqr-ping" {
+		// TODO : unit tests
+		rule := &config.FrontendRule{
+			Usr:      "spqr-ping",
+			DB:       "spqr-ping",
+			AuthRule: &config.AuthCfg{Method: config.AuthOK},
+			PoolMode: config.PoolModeVirtual,
+		}
+		if err := cl.AssignRule(rule); err != nil {
+			_ = cl.ReplyErrMsg("failed to assign rule", spqrerror.SPQR_ROUTING_ERROR, txstatus.TXIDLE)
+			return nil, err
+		}
+	}
+
 	if pt == port.ADMRouterPortType || cl.DB() == "spqr-console" {
 		return r.preRouteInitializedClientAdm(cl)
 	}


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a fast ping handler that bypasses TLS requirements for quick health checks.  
- Added ping detection logic in the router to route “spqr-ping” requests to a dedicated ping handler.  
- Introduced a new preRoutePingPong function to assign the ping rule and process ping requests.  
- Implemented a Ping method in the client interactor to send back a “pong” response.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

| File                                   | Description                                                          |
| -------------------------------------- | -------------------------------------------------------------------- |
| router/rulerouter/rulerouter.go         | Added debug logging and ping detection logic along with a dedicated preRoutePingPong function.       |
| router/instance/instance.go             | Extended connection handling to process ping requests and send appropriate backend messages.         |
| pkg/clientinteractor/interactor.go       | Implemented the Ping method to handle ping requests by writing header and data row messages.           |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T16:03:19Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces a fast ping handler for connections with "spqr-ping" that bypasses TLS checks. Key changes include:
- Routing logic updates to detect "spqr-ping" in both the RuleRouter and Instance implementations.
- Implementation of a new preRoutePingPong function to assign rules and handle ping-specific authentication.
- An added ReadyForQuery method in the PSQLInteractor for sending backend messages during ping.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

| File                                  | Description                                                     |
| ------------------------------------- | --------------------------------------------------------------- |
| router/rulerouter/rulerouter.go       | Adds spqr-ping routing support via preRoutePingPong function.    |
| router/instance/instance.go           | Integrates spqr-ping handling by sending ping messages and processing responses. |
| pkg/clientinteractor/interactor.go    | Introduces a new ReadyForQuery method for ping response handling. |




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:40:02Z
user: login: diPhantxm
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-07T19:30:52Z
user: login: Denchick
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T10:20:29Z
user: login: Denchick
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T10:43:22Z
user: login: Denchick
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-08T10:44:01Z
user: login: reshke
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T07:23:12Z

Review comments:
diff_hunk: @@ -1568,6 +1568,17 @@ func (pi *PSQLInteractor) DropSequence(_ context.Context, name string) error {
 	return pi.CompleteMsg(0)
 }
 
+func (pi *PSQLInteractor) Ping(ctx context.Context) error {
+	if err := pi.WriteHeader("ping"); err != nil {
+		spqrlog.Zero.Error().Err(err).Msg("")
path: pkg/clientinteractor/interactor.go
user: login: Copilot
body: The error returned by WriteHeader is logged but not returned, which may lead to inconsistent behavior. Consider returning the error immediately if WriteHeader fails.
```suggestion
		spqrlog.Zero.Error().Err(err).Msg("")
		return err
```
created_at: 2025-05-05T16:03:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +178,60 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+		}
+
+		params := []string{"client_encoding", "standard_conforming_strings"}
+		for _, p := range params {
+			if v, ok := routerClient.Params()[p]; ok {
+				msgs = append(msgs, &pgproto3.ParameterStatus{Name: p, Value: v})
+			}
+		}
+
+		msgs = append(msgs, []pgproto3.BackendMessage{
+			&pgproto3.ParameterStatus{Name: "integer_datetimes", Value: "on"},
+			&pgproto3.ParameterStatus{Name: "client_encoding", Value: "UTF8"},
+			&pgproto3.ParameterStatus{Name: "DateStyle", Value: "ISO"},
+			&pgproto3.ParameterStatus{Name: "server_version", Value: "console"},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}...)
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("")
path: router/instance/instance.go
user: login: Copilot
body: The error log message is empty; a descriptive message should be added to help with debugging.
```suggestion
				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient")
```
created_at: 2025-05-05T16:03:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -182,6 +182,10 @@ func (r *RuleRouterImpl) PreRoute(conn net.Conn, pt port.RouterPortType) (rclien
 		return cl, nil
 	}
 
+	if cl.Usr() == "spqr-ping" && cl.DB() == "spqr-ping" {
path: router/rulerouter/rulerouter.go
user: login: Copilot
body: [nitpick] Consider defining a constant for "spqr-ping" to avoid repeated literals, which would improve maintainability and reduce the risk of typos.
```suggestion
	if cl.Usr() == spqrPingLiteral && cl.DB() == spqrPingLiteral {
```
created_at: 2025-05-05T17:40:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
path: router/instance/instance.go
user: login: Copilot
body: [nitpick] This condition repeats the literal "spqr-ping"; consider refactoring to a shared helper or constant to reduce duplication in the code.
```suggestion
	if routerClient.Usr() == SPQRPing && routerClient.DB() == SPQRPing {
```
created_at: 2025-05-05T17:40:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
+
+		if err != nil {
+			return routerClient.ID(), err
+		}
+
+		switch v := msg.(type) {
+		case *pgproto3.Query:
path: router/instance/instance.go
user: login: Copilot
body: [nitpick] Ensure that the handling of unexpected message types is robust; it might be beneficial to provide a fallback error response or additional logging if a message other than Query or Terminate is received.
created_at: 2025-05-05T17:40:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
path: router/instance/instance.go
user: login: diPhantxm
body: We should also send Parameters. Some drivers expect them, for example, client_encoding or specific DateStyle. [click](https://github.com/pg-sharding/spqr/blob/0fb0e7c25feeb6f9dd35ab52f4f7d57bf457217e/router/console/console.go#L132)
created_at: 2025-05-07T19:23:26Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
+
+		if err != nil {
+			return routerClient.ID(), err
+		}
+
+		switch v := msg.(type) {
+		case *pgproto3.Query:
+			if err := clientinteractor.NewPSQLInteractor(routerClient).ReadyForQuery(); err != nil {
path: router/instance/instance.go
user: login: diPhantxm
body: We should first mark sql command successful by sending CommandComplete and then rfq
created_at: 2025-05-07T19:26:14Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
+
+		if err != nil {
+			return routerClient.ID(), err
+		}
+
+		switch v := msg.(type) {
path: router/instance/instance.go
user: login: diPhantxm
body: Do we really need this switch? Maybe we can just receive any message and respond that router is alive?
created_at: 2025-05-07T19:28:51Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
path: router/instance/instance.go
user: login: diPhantxm
body: Can we say that router is alive without receiving a query? If we can connect to the router and it responds with RFQ, doesn't it mean that router is alive?
created_at: 2025-05-07T19:30:48Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
+
+		if err != nil {
+			return routerClient.ID(), err
+		}
+
+		switch v := msg.(type) {
path: router/instance/instance.go
user: login: Denchick
body: DATA DELETED
created_at: 2025-05-08T10:20:29Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
+
+		if err != nil {
+			return routerClient.ID(), err
+		}
+
+		switch v := msg.(type) {
+		case *pgproto3.Query:
+			if err := clientinteractor.NewPSQLInteractor(routerClient).ReadyForQuery(); err != nil {
path: router/instance/instance.go
user: login: Denchick
body: Yes, thank you!
created_at: 2025-05-08T10:43:22Z
author_association: COLLABORATOR
diff_hunk: @@ -175,6 +178,44 @@ func (r *InstanceImpl) serv(netconn net.Conn, pt port.RouterPortType) (uint, err
 		return routerClient.ID(), r.RuleRouter.CancelClient(routerClient.CancelMsg())
 	}
 
+	if routerClient.Usr() == "spqr-ping" && routerClient.DB() == "spqr-ping" {
+		msgs := []pgproto3.BackendMessage{
+			&pgproto3.AuthenticationOk{},
+			&pgproto3.ReadyForQuery{
+				TxStatus: byte(txstatus.TXIDLE),
+			},
+		}
+
+		for _, msg := range msgs {
+			if err := routerClient.Send(msg); err != nil {
+				spqrlog.Zero.Error().Err(err).Msg("failed to send message to routerClient during ping")
+				return routerClient.ID(), err
+			}
+		}
+
+		msg, err := routerClient.Receive()
path: router/instance/instance.go
user: login: Denchick
body: Seems like we have to receive a request from a client first
created_at: 2025-05-08T10:44:01Z
author_association: COLLABORATOR

PR comments:
user: login: Denchick
created_at: 2025-05-08T10:46:42Z
body: It works

```
spqr-ping=> gqwe;
SPQR router is alive

spqr-ping=> fwqe;
SPQR router is alive

spqr-ping=> weqf;
SPQR router is alive

spqr-ping=> --qwef;
spqr-ping=> wegf;
SPQR router is alive

spqr-ping=> ;
SPQR router is alive

spqr-ping=> ;
SPQR router is alive

spqr-ping=> qwef;
SPQR router is alive
```
author_association: COLLABORATOR
user: login: Denchick
created_at: 2025-05-08T15:03:19Z
body: Take into account sslmode:

```
root@regress_tests:/regress# psql "host=regress_router port=6432 user=spqr-ping dbname=spqr-ping sslmode=disable"
SPQR router is alive
psql (13.20 (Ubuntu 13.20-1.pgdg20.04+1), server 0.0.0)
Type "help" for help.

spqr-ping=> gqwefrq;
SPQR router is alive

spqr-ping=> \c spqr-console
connection to server at "regress_router" (172.18.0.8), port 6432 failed: ERROR:  SSL IS REQUIRED
Previous connection kept
spqr-ping=> 
```
author_association: COLLABORATOR