github.com/influxdata/influxdb/v2@v2.7.6/influxql/query/response_writer.go (about) 1 package query 2 3 //lint:file-ignore SA1019 Ignore for now 4 5 import ( 6 "context" 7 "encoding/csv" 8 "encoding/json" 9 "io" 10 "strconv" 11 "time" 12 13 "github.com/influxdata/influxdb/v2/influxql" 14 "github.com/influxdata/influxdb/v2/kit/tracing" 15 "github.com/influxdata/influxdb/v2/models" 16 "github.com/tinylib/msgp/msgp" 17 ) 18 19 // ResponseWriter is an interface for writing a response. 20 type ResponseWriter interface { 21 // WriteResponse writes a response. 22 WriteResponse(ctx context.Context, w io.Writer, resp Response) error 23 } 24 25 // NewResponseWriter creates a new ResponseWriter based on the Accept header 26 // in the request that wraps the ResponseWriter. 27 func NewResponseWriter(encoding influxql.EncodingFormat) ResponseWriter { 28 switch encoding { 29 case influxql.EncodingFormatAppCSV, influxql.EncodingFormatTextCSV: 30 return &csvFormatter{statementID: -1} 31 case influxql.EncodingFormatMessagePack: 32 return &msgpFormatter{} 33 case influxql.EncodingFormatJSON: 34 fallthrough 35 default: 36 // TODO(sgc): Add EncodingFormatJSONPretty 37 return &jsonFormatter{Pretty: false} 38 } 39 } 40 41 type jsonFormatter struct { 42 Pretty bool 43 } 44 45 func (f *jsonFormatter) WriteResponse(ctx context.Context, w io.Writer, resp Response) (err error) { 46 span, _ := tracing.StartSpanFromContext(ctx) 47 defer span.Finish() 48 49 var b []byte 50 if f.Pretty { 51 b, err = json.MarshalIndent(resp, "", " ") 52 } else { 53 b, err = json.Marshal(resp) 54 } 55 56 if err != nil { 57 _, err = io.WriteString(w, err.Error()) 58 } else { 59 _, err = w.Write(b) 60 } 61 62 w.Write([]byte("\n")) 63 return err 64 } 65 66 type csvFormatter struct { 67 statementID int 68 columns []string 69 } 70 71 func (f *csvFormatter) WriteResponse(ctx context.Context, w io.Writer, resp Response) (err error) { 72 span, _ := tracing.StartSpanFromContext(ctx) 73 defer span.Finish() 74 75 wr := csv.NewWriter(w) 76 if resp.Err != nil { 77 wr.Write([]string{"error"}) 78 wr.Write([]string{resp.Err.Error()}) 79 wr.Flush() 80 return wr.Error() 81 } 82 83 for _, result := range resp.Results { 84 if result.StatementID != f.statementID { 85 // If there are no series in the result, skip past this result. 86 if len(result.Series) == 0 { 87 continue 88 } 89 90 // Set the statement id and print out a newline if this is not the first statement. 91 if f.statementID >= 0 { 92 // Flush the csv writer and write a newline. 93 wr.Flush() 94 if err := wr.Error(); err != nil { 95 return err 96 } 97 98 if _, err := io.WriteString(w, "\n"); err != nil { 99 return err 100 } 101 } 102 f.statementID = result.StatementID 103 104 // Print out the column headers from the first series. 105 f.columns = make([]string, 2+len(result.Series[0].Columns)) 106 f.columns[0] = "name" 107 f.columns[1] = "tags" 108 copy(f.columns[2:], result.Series[0].Columns) 109 if err := wr.Write(f.columns); err != nil { 110 return err 111 } 112 } 113 114 for i, row := range result.Series { 115 if i > 0 && !stringsEqual(result.Series[i-1].Columns, row.Columns) { 116 // The columns have changed. Print a newline and reprint the header. 117 wr.Flush() 118 if err := wr.Error(); err != nil { 119 return err 120 } 121 122 if _, err := io.WriteString(w, "\n"); err != nil { 123 return err 124 } 125 126 f.columns = make([]string, 2+len(row.Columns)) 127 f.columns[0] = "name" 128 f.columns[1] = "tags" 129 copy(f.columns[2:], row.Columns) 130 if err := wr.Write(f.columns); err != nil { 131 return err 132 } 133 } 134 135 f.columns[0] = row.Name 136 f.columns[1] = "" 137 if len(row.Tags) > 0 { 138 hashKey := models.NewTags(row.Tags).HashKey() 139 if len(hashKey) > 0 { 140 f.columns[1] = string(hashKey[1:]) 141 } 142 } 143 for _, values := range row.Values { 144 for i, value := range values { 145 if value == nil { 146 f.columns[i+2] = "" 147 continue 148 } 149 150 switch v := value.(type) { 151 case float64: 152 f.columns[i+2] = strconv.FormatFloat(v, 'f', -1, 64) 153 case int64: 154 f.columns[i+2] = strconv.FormatInt(v, 10) 155 case uint64: 156 f.columns[i+2] = strconv.FormatUint(v, 10) 157 case string: 158 f.columns[i+2] = v 159 case bool: 160 if v { 161 f.columns[i+2] = "true" 162 } else { 163 f.columns[i+2] = "false" 164 } 165 case time.Time: 166 f.columns[i+2] = strconv.FormatInt(v.UnixNano(), 10) 167 case *float64, *int64, *string, *bool: 168 f.columns[i+2] = "" 169 } 170 } 171 wr.Write(f.columns) 172 } 173 } 174 } 175 wr.Flush() 176 return wr.Error() 177 } 178 179 type msgpFormatter struct{} 180 181 func (f *msgpFormatter) ContentType() string { 182 return "application/x-msgpack" 183 } 184 185 func (f *msgpFormatter) WriteResponse(ctx context.Context, w io.Writer, resp Response) (err error) { 186 span, _ := tracing.StartSpanFromContext(ctx) 187 defer span.Finish() 188 189 enc := msgp.NewWriter(w) 190 defer enc.Flush() 191 192 enc.WriteMapHeader(1) 193 if resp.Err != nil { 194 enc.WriteString("error") 195 enc.WriteString(resp.Err.Error()) 196 return nil 197 } else { 198 enc.WriteString("results") 199 enc.WriteArrayHeader(uint32(len(resp.Results))) 200 for _, result := range resp.Results { 201 if result.Err != nil { 202 enc.WriteMapHeader(1) 203 enc.WriteString("error") 204 enc.WriteString(result.Err.Error()) 205 continue 206 } 207 208 sz := 2 209 if len(result.Messages) > 0 { 210 sz++ 211 } 212 if result.Partial { 213 sz++ 214 } 215 enc.WriteMapHeader(uint32(sz)) 216 enc.WriteString("statement_id") 217 enc.WriteInt(result.StatementID) 218 if len(result.Messages) > 0 { 219 enc.WriteString("messages") 220 enc.WriteArrayHeader(uint32(len(result.Messages))) 221 for _, msg := range result.Messages { 222 enc.WriteMapHeader(2) 223 enc.WriteString("level") 224 enc.WriteString(msg.Level) 225 enc.WriteString("text") 226 enc.WriteString(msg.Text) 227 } 228 } 229 enc.WriteString("series") 230 enc.WriteArrayHeader(uint32(len(result.Series))) 231 for _, series := range result.Series { 232 sz := 2 233 if series.Name != "" { 234 sz++ 235 } 236 if len(series.Tags) > 0 { 237 sz++ 238 } 239 if series.Partial { 240 sz++ 241 } 242 enc.WriteMapHeader(uint32(sz)) 243 if series.Name != "" { 244 enc.WriteString("name") 245 enc.WriteString(series.Name) 246 } 247 if len(series.Tags) > 0 { 248 enc.WriteString("tags") 249 enc.WriteMapHeader(uint32(len(series.Tags))) 250 for k, v := range series.Tags { 251 enc.WriteString(k) 252 enc.WriteString(v) 253 } 254 } 255 enc.WriteString("columns") 256 enc.WriteArrayHeader(uint32(len(series.Columns))) 257 for _, col := range series.Columns { 258 enc.WriteString(col) 259 } 260 enc.WriteString("values") 261 enc.WriteArrayHeader(uint32(len(series.Values))) 262 for _, values := range series.Values { 263 enc.WriteArrayHeader(uint32(len(values))) 264 for _, v := range values { 265 enc.WriteIntf(v) 266 } 267 } 268 if series.Partial { 269 enc.WriteString("partial") 270 enc.WriteBool(series.Partial) 271 } 272 } 273 if result.Partial { 274 enc.WriteString("partial") 275 enc.WriteBool(true) 276 } 277 } 278 } 279 return nil 280 } 281 282 func stringsEqual(a, b []string) bool { 283 if len(a) != len(b) { 284 return false 285 } 286 for i := range a { 287 if a[i] != b[i] { 288 return false 289 } 290 } 291 return true 292 }