Skip to content

Commit

Permalink
Go: Implement Time Command (#2963)
Browse files Browse the repository at this point in the history
* Implemented Time Command

Signed-off-by: Niharika Bhavaraju <[email protected]>
  • Loading branch information
niharikabhavaraju authored Jan 30, 2025
1 parent 1cae913 commit b4e28e2
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 1 deletion.
21 changes: 21 additions & 0 deletions go/api/base_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6849,3 +6849,24 @@ func (client *baseClient) BitFieldRO(key string, commands []options.BitFieldROCo
}
return handleIntOrNilArrayResponse(result)
}

// Returns the server time.
//
// Return value:
// The current server time as a String array with two elements:
// A UNIX TIME and the amount of microseconds already elapsed in the current second.
// The returned array is in a [UNIX TIME, Microseconds already elapsed] format.
//
// For example:
//
// result, err := client.Time()
// result: [{1737051660} {994688}]
//
// [valkey.io]: https://valkey.io/commands/time/
func (client *baseClient) Time() ([]string, error) {
result, err := client.executeCommand(C.Time, []string{})
if err != nil {
return nil, err
}
return handleStringArrayResponse(result)
}
32 changes: 32 additions & 0 deletions go/api/glide_cluster_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,35 @@ func (client *GlideClusterClient) PingWithOptions(pingOptions options.ClusterPin

return handleStringResponse(response)
}

// Returns the server time.
// The command will be routed to a random node, unless Route in opts is provided.
//
// See [valkey.io] for details.
//
// Parameters:
//
// options - The TimeOptions type.
//
// Return value:
//
// The current server time as a String array with two elements: A UNIX TIME and the amount
// of microseconds already elapsed in the current second.
// The returned array is in a [UNIX TIME, Microseconds already elapsed] format.
//
// Example:
//
// route := config.Route(config.RandomRoute)
// opts := options.ClusterTimeOptions{
// Route: &route,
// }
// fmt.Println(clusterResponse.SingleValue()) // Output: [1737994354 547816]
//
// [valkey.io]: https://valkey.io/commands/time/
func (client *GlideClusterClient) TimeWithOptions(opts options.RouteOption) (ClusterValue[[]string], error) {
result, err := client.executeCommandWithRoute(C.Time, []string{}, opts.Route)
if err != nil {
return createEmptyClusterValue[[]string](), err
}
return handleTimeClusterResponse(result)
}
9 changes: 9 additions & 0 deletions go/api/options/route_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0

package options

import "github.com/valkey-io/valkey-glide/go/glide/api/config"

type RouteOption struct {
Route config.Route
}
50 changes: 50 additions & 0 deletions go/api/response_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1003,3 +1003,53 @@ func handleXPendingDetailResponse(response *C.struct_CommandResponse) ([]XPendin

return pendingDetails, nil
}

func handleRawStringArrayMapResponse(response *C.struct_CommandResponse) (map[string][]string, error) {
defer C.free_command_response(response)
typeErr := checkResponseType(response, C.Map, false)
if typeErr != nil {
return nil, typeErr
}

data, err := parseMap(response)
if err != nil {
return nil, err
}

result, err := mapConverter[[]string]{
next: arrayConverter[string]{},
canBeNil: false,
}.convert(data)
if err != nil {
return nil, err
}
mapResult, ok := result.(map[string][]string)
if !ok {
return nil, &errors.RequestError{Msg: "Unexpected conversion result type"}
}

return mapResult, nil
}

func handleTimeClusterResponse(response *C.struct_CommandResponse) (ClusterValue[[]string], error) {
// Handle multi-node response
if err := checkResponseType(response, C.Map, true); err == nil {
mapData, err := handleRawStringArrayMapResponse(response)
if err != nil {
return createEmptyClusterValue[[]string](), err
}
multiNodeTimes := make(map[string][]string)
for nodeName, nodeTimes := range mapData {
multiNodeTimes[nodeName] = nodeTimes
}

return createClusterMultiValue(multiNodeTimes), nil
}

// Handle single node response
data, err := handleStringArrayResponse(response)
if err != nil {
return createEmptyClusterValue[[]string](), err
}
return createClusterSingleValue(data), nil
}
6 changes: 5 additions & 1 deletion go/api/server_management_cluster_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

package api

// ServerManagementCommands supports commands for the "Server Management" group for a cluster client.
import "github.com/valkey-io/valkey-glide/go/glide/api/options"

// ServerManagementClusterCommands supports commands for the "Server Management Commands" group for cluster client.
//
// See [valkey.io] for details.
//
Expand All @@ -11,4 +13,6 @@ type ServerManagementClusterCommands interface {
Info() (map[string]string, error)

InfoWithOptions(options ClusterInfoOptions) (ClusterValue[string], error)

TimeWithOptions(routeOption options.RouteOption) (ClusterValue[[]string], error)
}
2 changes: 2 additions & 0 deletions go/api/server_management_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ type ServerManagementCommands interface {
InfoWithOptions(options InfoOptions) (string, error)

DBSize() (int64, error)

Time() ([]string, error)
}
58 changes: 58 additions & 0 deletions go/integTest/cluster_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,61 @@ func (suite *GlideTestSuite) TestPingWithOptions_InvalidRoute() {
assert.NotNil(suite.T(), err)
assert.Empty(suite.T(), result)
}

func (suite *GlideTestSuite) TestTimeWithoutRoute() {
client := suite.defaultClusterClient()
options := options.RouteOption{Route: nil}
result, err := client.TimeWithOptions(options)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), result)
assert.False(suite.T(), result.IsEmpty())
assert.True(suite.T(), result.IsSingleValue())
assert.NotEmpty(suite.T(), result.SingleValue())
assert.IsType(suite.T(), "", result.SingleValue()[0])
assert.Equal(suite.T(), 2, len(result.SingleValue()))
}

func (suite *GlideTestSuite) TestTimeWithAllNodesRoute() {
client := suite.defaultClusterClient()
route := config.Route(config.AllNodes)
options := options.RouteOption{Route: route}
result, err := client.TimeWithOptions(options)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), result)
assert.False(suite.T(), result.IsEmpty())
assert.True(suite.T(), result.IsMultiValue())

multiValue := result.MultiValue()
assert.Greater(suite.T(), len(multiValue), 1)

for nodeName, timeStrings := range multiValue {
assert.NotEmpty(suite.T(), timeStrings, "Node %s should have time values", nodeName)
for _, timeStr := range timeStrings {
assert.IsType(suite.T(), "", timeStr)
}
}
}

func (suite *GlideTestSuite) TestTimeWithRandomRoute() {
client := suite.defaultClusterClient()
route := config.Route(config.RandomRoute)
options := options.RouteOption{Route: route}
result, err := client.TimeWithOptions(options)
assert.NoError(suite.T(), err)
assert.NotNil(suite.T(), result)
assert.False(suite.T(), result.IsEmpty())
assert.True(suite.T(), result.IsSingleValue())
assert.NotEmpty(suite.T(), result.SingleValue())
assert.IsType(suite.T(), "", result.SingleValue()[0])
assert.Equal(suite.T(), 2, len(result.SingleValue()))
}

func (suite *GlideTestSuite) TestTimeWithInvalidRoute() {
client := suite.defaultClusterClient()
invalidRoute := config.Route(config.NewByAddressRoute("invalidHost", 9999))
options := options.RouteOption{Route: invalidRoute}
result, err := client.TimeWithOptions(options)
assert.NotNil(suite.T(), err)
assert.True(suite.T(), result.IsEmpty())
assert.Empty(suite.T(), result.SingleValue())
}
33 changes: 33 additions & 0 deletions go/integTest/standalone_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ package integTest

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/google/uuid"
"github.com/valkey-io/valkey-glide/go/glide/api"
Expand Down Expand Up @@ -483,3 +485,34 @@ func (suite *GlideTestSuite) TestPingWithOptions_ClosedClient() {
assert.Equal(suite.T(), "", result)
assert.IsType(suite.T(), &errors.ClosingError{}, err)
}

func (suite *GlideTestSuite) TestTime_Success() {
client := suite.defaultClient()
results, err := client.Time()

assert.Nil(suite.T(), err)
assert.Len(suite.T(), results, 2)

now := time.Now().Unix() - 1

timestamp, err := strconv.ParseInt(results[0], 10, 64)
assert.Nil(suite.T(), err)
assert.Greater(suite.T(), timestamp, now)

microseconds, err := strconv.ParseInt(results[1], 10, 64)
assert.Nil(suite.T(), err)
assert.Less(suite.T(), microseconds, int64(1000000))
}

func (suite *GlideTestSuite) TestTime_Error() {
client := suite.defaultClient()

// Disconnect the client or simulate an error condition
client.Close()

results, err := client.Time()

assert.NotNil(suite.T(), err)
assert.Nil(suite.T(), results)
assert.IsType(suite.T(), &errors.ClosingError{}, err)
}

0 comments on commit b4e28e2

Please sign in to comment.