Add top-level "results" key

This brings the API output into line with the API specification.
pull/1385/head
Philip O'Toole 2015-01-27 12:27:29 -08:00
parent 155bc9a531
commit a197d16ac8
6 changed files with 74 additions and 58 deletions

View File

@ -44,7 +44,7 @@ func NewClient(c Config) (*Client, error) {
return &client, nil
}
func (c *Client) Query(q Query) (influxdb.Results, error) {
func (c *Client) Query(q Query) (*influxdb.Results, error) {
u := c.url
u.Path = "query"
@ -64,10 +64,10 @@ func (c *Client) Query(q Query) (influxdb.Results, error) {
if err != nil {
return nil, err
}
return results, nil
return &results, nil
}
func (c *Client) Write(writes ...Write) (influxdb.Results, error) {
func (c *Client) Write(writes ...Write) (*influxdb.Results, error) {
c.url.Path = "write"
type data struct {
Points []influxdb.Point `json:"points"`
@ -94,7 +94,7 @@ func (c *Client) Write(writes ...Write) (influxdb.Results, error) {
if err != nil {
return nil, err
}
return results, nil
return &results, nil
}
func (c *Client) Ping() (time.Duration, string, error) {

View File

@ -111,7 +111,7 @@ func (c *cli) executeQuery(query string) {
if err != nil {
fmt.Printf("ERR: %s\n", err)
}
for _, r := range results {
for _, r := range results.Results {
var i interface{}
if r.Err != nil {
i = r.Err

View File

@ -331,12 +331,9 @@ func httpResults(w http.ResponseWriter, results influxdb.Results, pretty bool) {
// httpError writes an error to the client in a standard format.
func httpError(w http.ResponseWriter, error string, code int) {
var results influxdb.Results
results = append(results, &influxdb.Result{Err: errors.New(error)})
w.Header().Add("content-type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(results)
_ = json.NewEncoder(w).Encode(influxdb.Results{Err: errors.New(error)})
}
// Filters and filter helpers

View File

@ -30,7 +30,7 @@ func TestHandler_Databases(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "SHOW DATABASES"}, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"rows":[{"columns":["Name"],"values":[["bar"],["foo"]]}]}]` {
} else if body != `{"results":[{"rows":[{"columns":["Name"],"values":[["bar"],["foo"]]}]}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -45,25 +45,27 @@ func TestHandler_DatabasesPrettyPrinted(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "SHOW DATABASES", "pretty": "true"}, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[
{
"rows": [
{
"columns": [
"Name"
],
"values": [
[
"bar"
} else if body != `{
"results": [
{
"rows": [
{
"columns": [
"Name"
],
[
"foo"
"values": [
[
"bar"
],
[
"foo"
]
]
]
}
]
}
]` {
}
]
}
]
}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -76,7 +78,7 @@ func TestHandler_CreateDatabase(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "CREATE DATABASE foo"}, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -101,7 +103,7 @@ func TestHandler_CreateDatabase_Conflict(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "CREATE DATABASE foo"}, nil, "")
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"database exists"}]` {
} else if body != `{"results":[{"error":"database exists"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -115,7 +117,7 @@ func TestHandler_DeleteDatabase(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "DROP DATABASE foo"}, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -128,7 +130,7 @@ func TestHandler_DeleteDatabase_NotFound(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, map[string]string{"q": "DROP DATABASE bar"}, nil, "")
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"database not found"}]` {
} else if body != `{"results":[{"error":"database not found"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -144,7 +146,7 @@ func TestHandler_RetentionPolicies(t *testing.T) {
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"rows":[{"columns":["Name"],"values":[["bar"]]}]}]` {
} else if body != `{"results":[{"rows":[{"columns":["Name"],"values":[["bar"]]}]}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -158,7 +160,7 @@ func TestHandler_RetentionPolicies_DatabaseNotFound(t *testing.T) {
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"database not found"}]` {
} else if body != `{"results":[{"error":"database not found"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -174,7 +176,7 @@ func TestHandler_CreateRetentionPolicy(t *testing.T) {
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -302,7 +304,7 @@ func TestHandler_DeleteRetentionPolicy(t *testing.T) {
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -317,7 +319,7 @@ func TestHandler_DeleteRetentionPolicy_DatabaseNotFound(t *testing.T) {
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"database not found"}]` {
} else if body != `{"results":[{"error":"database not found"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -333,7 +335,7 @@ func TestHandler_DeleteRetentionPolicy_NotFound(t *testing.T) {
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"retention policy not found"}]` {
} else if body != `{"results":[{"error":"retention policy not found"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -360,7 +362,7 @@ func TestHandler_Users_NoUsers(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"rows":[{"columns":["User"]}]}]` {
} else if body != `{"results":[{"rows":[{"columns":["User"]}]}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -375,7 +377,7 @@ func TestHandler_Users_OneUser(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"rows":[{"columns":["User"],"values":[["jdoe"]]}]}]` {
} else if body != `{"results":[{"rows":[{"columns":["User"],"values":[["jdoe"]]}]}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -392,7 +394,7 @@ func TestHandler_Users_MultipleUsers(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"rows":[{"columns":["User"],"values":[["csmith"],["jdoe"],["mclark"]]}]}]` {
} else if body != `{"results":[{"rows":[{"columns":["User"],"values":[["csmith"],["jdoe"],["mclark"]]}]}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -406,7 +408,7 @@ func TestHandler_CreateUser(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -420,7 +422,7 @@ func TestHandler_CreateUser_BadRequest(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusBadRequest {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"error parsing query: found 0, expected identifier at line 1, char 13"}]` {
} else if body != `{"error":"error parsing query: found 0, expected identifier at line 1, char 13"}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -434,7 +436,7 @@ func TestHandler_CreateUser_BadRequest_NoName(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusBadRequest {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"error parsing query: found WITH, expected identifier at line 1, char 13"}]` {
} else if body != `{"error":"error parsing query: found WITH, expected identifier at line 1, char 13"}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -448,7 +450,7 @@ func TestHandler_CreateUser_BadRequest_NoPassword(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusBadRequest {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"error parsing query: found EOF, expected WITH at line 1, char 18"}]` {
} else if body != `{"error":"error parsing query: found EOF, expected WITH at line 1, char 18"}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -499,7 +501,7 @@ func TestHandler_DeleteUser(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusOK {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{}]` {
} else if body != `{"results":[{}]}` {
t.Fatalf("unexpected body: %s", body)
}
}
@ -513,7 +515,7 @@ func TestHandler_DeleteUser_UserNotFound(t *testing.T) {
status, body := MustHTTP("GET", s.URL+`/query`, query, nil, "")
if status != http.StatusInternalServerError {
t.Fatalf("unexpected status: %d", status)
} else if body != `[{"error":"user not found"}]` {
} else if body != `{"results":[{"error":"user not found"}]}` {
t.Fatalf("unexpected body: %s", body)
}
}

View File

@ -1517,13 +1517,11 @@ func (s *Server) ReadSeries(database, retentionPolicy, name string, tags map[str
func (s *Server) ExecuteQuery(q *influxql.Query, database string, user *User) Results {
// Authorize user to execute the query.
if err := Authorize(user, q, database); err != nil {
return Results{
{Err: err},
}
return Results{Err: err}
}
// Build empty resultsets.
results := make(Results, len(q.Statements))
results := Results{Results: make([]*Result, len(q.Statements))}
// Execute each statement.
for i, stmt := range q.Statements {
@ -1580,16 +1578,16 @@ func (s *Server) ExecuteQuery(q *influxql.Query, database string, user *User) Re
}
// If an error occurs then stop processing remaining statements.
results[i] = res
results.Results[i] = res
if res.Err != nil {
break
}
}
// Fill any empty results after error.
for i, res := range results {
for i, res := range results.Results {
if res == nil {
results[i] = &Result{Err: ErrNotExecuted}
results.Results[i] = &Result{Err: ErrNotExecuted}
}
}
@ -1936,12 +1934,31 @@ func (r *Result) MarshalJSON() ([]byte, error) {
}
// Results represents a list of statement results.
type Results []*Result
type Results struct {
Results []*Result
Err error
}
func (r Results) MarshalJSON() ([]byte, error) {
// Define a struct that outputs "error" as a string.
var o struct {
Results []*Result `json:"results,omitempty"`
Err string `json:"error,omitempty"`
}
// Copy fields to output struct.
o.Results = r.Results
if r.Err != nil {
o.Err = r.Err.Error()
}
return json.Marshal(&o)
}
// Error returns the first error from any statement.
// Returns nil if no errors occurred on any statements.
func (a Results) Error() error {
for _, r := range a {
for _, r := range a.Results {
if r.Err != nil {
return r.Err
}

View File

@ -712,10 +712,10 @@ func TestServer_ExecuteQuery(t *testing.T) {
// Select data from the server.
results := s.ExecuteQuery(MustParseQuery("SELECT sum(value) FROM cpu"), "foo", nil)
if len(results) != 1 {
t.Fatalf("unexpected result count: %d", len(results))
if len(results.Results) != 1 {
t.Fatalf("unexpected result count: %d", len(results.Results))
}
if res := results[0]; res.Err != nil {
if res := results.Results[0]; res.Err != nil {
t.Fatalf("unexpected error: %s", res.Err)
} else if len(res.Rows) != 1 {
t.Fatalf("unexpected row count: %d", len(res.Rows))