Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement text/csv content encoding for the response writer #7099

Merged
merged 1 commit into from
Aug 10, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- [#7120](https://github.com/influxdata/influxdb/issues/7120): Add additional statistics to query executor.
- [#7135](https://github.com/influxdata/influxdb/pull/7135): Support enable HTTP service over unix domain socket. Thanks @oiooj
- [#3634](https://github.com/influxdata/influxdb/issues/3634): Support mixed duration units.
- [#7099](https://github.com/influxdata/influxdb/pull/7099): Implement text/csv content encoding for the response writer.

### Bugfixes

Expand Down
91 changes: 91 additions & 0 deletions services/httpd/response_writer.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package httpd

import (
"encoding/csv"
"encoding/json"
"io"
"net/http"
"strconv"
"time"

"github.com/influxdata/influxdb/models"
)

// ResponseWriter is an interface for writing a response.
Expand All @@ -19,6 +24,8 @@ type ResponseWriter interface {
func NewResponseWriter(w http.ResponseWriter, r *http.Request) ResponseWriter {
pretty := r.URL.Query().Get("pretty") == "true"
switch r.Header.Get("Accept") {
case "application/csv", "text/csv":
return &csvResponseWriter{statementID: -1, ResponseWriter: w}
case "application/json":
fallthrough
default:
Expand Down Expand Up @@ -62,3 +69,87 @@ func (w *jsonResponseWriter) Flush() {
w.Flush()
}
}

type csvResponseWriter struct {
statementID int
columns []string
http.ResponseWriter
}

func (w *csvResponseWriter) WriteResponse(resp Response) (n int, err error) {
csv := csv.NewWriter(w)
for _, result := range resp.Results {
if result.StatementID != w.statementID {
// If there are no series in the result, skip past this result.
if len(result.Series) == 0 {
continue
}

// Set the statement id and print out a newline if this is not the first statement.
if w.statementID >= 0 {
// Flush the csv writer and write a newline.
csv.Flush()
if err := csv.Error(); err != nil {
return n, err
}

if out, err := io.WriteString(w, "\n"); err != nil {
return n, err
} else {
n += out
}
}
w.statementID = result.StatementID

// Print out the column headers from the first series.
w.columns = make([]string, 2+len(result.Series[0].Columns))
w.columns[0] = "name"
w.columns[1] = "tags"
copy(w.columns[2:], result.Series[0].Columns)
if err := csv.Write(w.columns); err != nil {
return n, err
}
}

for _, row := range result.Series {
w.columns[0] = row.Name
if len(row.Tags) > 0 {
w.columns[1] = string(models.Tags(row.Tags).HashKey()[1:])
} else {
w.columns[1] = ""
}
for _, values := range row.Values {
for i, value := range values {
switch v := value.(type) {
case float64:
w.columns[i+2] = strconv.FormatFloat(v, 'f', -1, 64)
case int64:
w.columns[i+2] = strconv.FormatInt(v, 10)
case string:
w.columns[i+2] = v
case bool:
if v {
w.columns[i+2] = "true"
} else {
w.columns[i+2] = "false"
}
case time.Time:
w.columns[i+2] = strconv.FormatInt(v.UnixNano(), 10)
}
}
csv.Write(w.columns)
}
}
}
csv.Flush()
if err := csv.Error(); err != nil {
return n, err
}
return n, nil
}

func (w *csvResponseWriter) Flush() {
if w, ok := w.ResponseWriter.(http.Flusher); ok {
w.Flush()
}
}