From 3e4ab7621a37851eea8ab4a63e467e40480f4333 Mon Sep 17 00:00:00 2001 From: Jeffrey Lovitz Date: Tue, 31 Aug 2021 17:47:33 -0400 Subject: [PATCH 1/4] Add Query endpoints with QueryOptions support --- client_test.go | 16 +++++++++++++++ graph.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/client_test.go b/client_test.go index 44ed0a3..c169e45 100644 --- a/client_test.go +++ b/client_test.go @@ -473,3 +473,19 @@ func TestNodeMapDatatype(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 1, res.RelationshipsDeleted(), "Expecting 1 relationships deleted") } + +func TestTimeout(t *testing.T) { + // Instantiate a new QueryOptions struct with a 1-second timeout + options := QueryOptionsNew(1) + + // Issue a long-running query with a 1-millisecond timeout. + res, err := graph.QueryWithOptions("UNWIND range(0, 1000000) AS v RETURN v", options) + assert.Nil(t, res) + assert.NotNil(t, err) + + params := make(map[string]interface{}) + params["ub"] = 1000000 + res, err = graph.ParameterizedQueryWithOptions("UNWIND range(0, $ub) AS v RETURN v", params, options); + assert.Nil(t, res) + assert.NotNil(t, err) +} diff --git a/graph.go b/graph.go index 0b80183..e62bddb 100644 --- a/graph.go +++ b/graph.go @@ -8,6 +8,11 @@ import ( "github.com/gomodule/redigo/redis" ) +// QueryOptions are a set of additional arguments to be emitted with a query. +type QueryOptions struct { + timeout int +} + // Graph represents a graph, which is a collection of nodes and edges. type Graph struct { Id string @@ -97,9 +102,16 @@ func (g *Graph) Commit() (*QueryResult, error) { return g.Query(q) } +// QueryOptionsNew instantiates a new QueryOptions struct. +func QueryOptionsNew(timeout int) QueryOptions { + options := QueryOptions{ + timeout: timeout, + } + return options +} + // Query executes a query against the graph. func (g *Graph) Query(q string) (*QueryResult, error) { - r, err := g.Conn.Do("GRAPH.QUERY", g.Id, q, "--compact") if err != nil { return nil, err @@ -126,6 +138,46 @@ func (g *Graph) ParameterizedQuery(q string, params map[string]interface{}) (*Qu return g.Query(q); } +// QueryWithOptions issues a query with the given timeout +func (g *Graph) QueryWithOptions(q string, options QueryOptions) (*QueryResult, error) { + var r interface{} + var err error + if(options.timeout >= 0) { + r, err = g.Conn.Do("GRAPH.QUERY", g.Id, q, "--compact", "timeout", options.timeout) + } else { + r, err = g.Conn.Do("GRAPH.QUERY", g.Id, q, "--compact") + } + if err != nil { + return nil, err + } + + return QueryResultNew(g, r) +} + +// ParameterizedQueryWithOptions issues a parameterized query with the given timeout +func (g *Graph) ParameterizedQueryWithOptions(q string, params map[string]interface{}, options QueryOptions) (*QueryResult, error) { + if(params != nil){ + q = BuildParamsHeader(params) + q + } + return g.QueryWithOptions(q, options); +} + +// ROQueryWithOptions issues a read-only query with the given timeout +func (g *Graph) ROQueryWithOptions(q string, options QueryOptions) (*QueryResult, error) { + var r interface{} + var err error + if(options.timeout >= 0) { + r, err = g.Conn.Do("GRAPH.RO_QUERY", g.Id, q, "--compact", "timeout", options.timeout) + } else { + r, err = g.Conn.Do("GRAPH.RO_QUERY", g.Id, q, "--compact") + } + if err != nil { + return nil, err + } + + return QueryResultNew(g, r) +} + // Merge pattern func (g *Graph) Merge(p string) (*QueryResult, error) { q := fmt.Sprintf("MERGE %s", p) From 264dad8031a88ca3d3ab5080b298359c792f27b8 Mon Sep 17 00:00:00 2001 From: Jeffrey Lovitz Date: Wed, 1 Sep 2021 09:28:44 -0400 Subject: [PATCH 2/4] Use getter/setter framework for QueryOptions --- client_test.go | 3 ++- graph.go | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client_test.go b/client_test.go index c169e45..073cb9f 100644 --- a/client_test.go +++ b/client_test.go @@ -476,7 +476,8 @@ func TestNodeMapDatatype(t *testing.T) { func TestTimeout(t *testing.T) { // Instantiate a new QueryOptions struct with a 1-second timeout - options := QueryOptionsNew(1) + options := QueryOptionsNew() + options = QueryOptionsSetTimeout(options, 1) // Issue a long-running query with a 1-millisecond timeout. res, err := graph.QueryWithOptions("UNWIND range(0, 1000000) AS v RETURN v", options) diff --git a/graph.go b/graph.go index e62bddb..235ac04 100644 --- a/graph.go +++ b/graph.go @@ -103,13 +103,24 @@ func (g *Graph) Commit() (*QueryResult, error) { } // QueryOptionsNew instantiates a new QueryOptions struct. -func QueryOptionsNew(timeout int) QueryOptions { +func QueryOptionsNew() QueryOptions { options := QueryOptions{ - timeout: timeout, + timeout: -1, } return options } +// QueryOptionsSetTimeout sets the timeout member of the QueryOptions struct +func QueryOptionsSetTimeout(options QueryOptions, timeout int) QueryOptions { + options.timeout = timeout + return options +} + +// QueryOptionsGetTimeout retrieves the timeout of the QueryOptions struct +func QueryOptionsGetTimeout(options QueryOptions) int { + return options.timeout +} + // Query executes a query against the graph. func (g *Graph) Query(q string) (*QueryResult, error) { r, err := g.Conn.Do("GRAPH.QUERY", g.Id, q, "--compact") From e920460280257409b9f097e2d1f1b9bdd5fb63da Mon Sep 17 00:00:00 2001 From: Jeffrey Lovitz Date: Wed, 1 Sep 2021 10:05:44 -0400 Subject: [PATCH 3/4] Address PR comments --- client_test.go | 6 ++++-- graph.go | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/client_test.go b/client_test.go index 073cb9f..85effbe 100644 --- a/client_test.go +++ b/client_test.go @@ -476,8 +476,10 @@ func TestNodeMapDatatype(t *testing.T) { func TestTimeout(t *testing.T) { // Instantiate a new QueryOptions struct with a 1-second timeout - options := QueryOptionsNew() - options = QueryOptionsSetTimeout(options, 1) + options := NewQueryOptions().SetTimeout(1) + + // Verify that the timeout was set properly + assert.Equal(t, 1, options.GetTimeout()) // Issue a long-running query with a 1-millisecond timeout. res, err := graph.QueryWithOptions("UNWIND range(0, 1000000) AS v RETURN v", options) diff --git a/graph.go b/graph.go index 235ac04..1f7ef4e 100644 --- a/graph.go +++ b/graph.go @@ -102,22 +102,21 @@ func (g *Graph) Commit() (*QueryResult, error) { return g.Query(q) } -// QueryOptionsNew instantiates a new QueryOptions struct. -func QueryOptionsNew() QueryOptions { - options := QueryOptions{ +// NewQueryOptions instantiates a new QueryOptions struct. +func NewQueryOptions() *QueryOptions { + return &QueryOptions{ timeout: -1, } - return options } -// QueryOptionsSetTimeout sets the timeout member of the QueryOptions struct -func QueryOptionsSetTimeout(options QueryOptions, timeout int) QueryOptions { +// SetTimeout sets the timeout member of the QueryOptions struct +func (options *QueryOptions) SetTimeout(timeout int) *QueryOptions { options.timeout = timeout return options } -// QueryOptionsGetTimeout retrieves the timeout of the QueryOptions struct -func QueryOptionsGetTimeout(options QueryOptions) int { +// GetTimeout retrieves the timeout of the QueryOptions struct +func (options *QueryOptions) GetTimeout() int { return options.timeout } @@ -150,7 +149,7 @@ func (g *Graph) ParameterizedQuery(q string, params map[string]interface{}) (*Qu } // QueryWithOptions issues a query with the given timeout -func (g *Graph) QueryWithOptions(q string, options QueryOptions) (*QueryResult, error) { +func (g *Graph) QueryWithOptions(q string, options *QueryOptions) (*QueryResult, error) { var r interface{} var err error if(options.timeout >= 0) { @@ -166,7 +165,7 @@ func (g *Graph) QueryWithOptions(q string, options QueryOptions) (*QueryResult, } // ParameterizedQueryWithOptions issues a parameterized query with the given timeout -func (g *Graph) ParameterizedQueryWithOptions(q string, params map[string]interface{}, options QueryOptions) (*QueryResult, error) { +func (g *Graph) ParameterizedQueryWithOptions(q string, params map[string]interface{}, options *QueryOptions) (*QueryResult, error) { if(params != nil){ q = BuildParamsHeader(params) + q } @@ -174,7 +173,7 @@ func (g *Graph) ParameterizedQueryWithOptions(q string, params map[string]interf } // ROQueryWithOptions issues a read-only query with the given timeout -func (g *Graph) ROQueryWithOptions(q string, options QueryOptions) (*QueryResult, error) { +func (g *Graph) ROQueryWithOptions(q string, options *QueryOptions) (*QueryResult, error) { var r interface{} var err error if(options.timeout >= 0) { From 09f03498ccb3a89a2d4854a1c6e519b7480ced1f Mon Sep 17 00:00:00 2001 From: Jeffrey Lovitz Date: Wed, 1 Sep 2021 10:18:35 -0400 Subject: [PATCH 4/4] Update documentation --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 20a95b7..fa15929 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,17 @@ Name: John Doe Age: 33 ``` +## Running queries with timeouts + +Queries can be run with a millisecond-level timeout as described in [the module documentation](https://oss.redis.com/redisgraph/configuration/#timeout). To take advantage of this feature, the `QueryOptions` struct should be used: + +```go +options := NewQueryOptions().SetTimeout(10) // 10-millisecond timeout +res, err := graph.QueryWithOptions("MATCH (src {name: 'John Doe'})-[*]->(dest) RETURN dest", options) +``` + +`ParameterizedQueryWithOptions` and `ROQueryWithOptions` endpoints are also exposed by the client. + ## Running tests A simple test suite is provided, and can be run with: