2018-09-04 21:08:00 +00:00
package http
import (
"context"
"encoding/json"
2019-01-24 00:15:42 +00:00
"errors"
2018-09-04 21:08:00 +00:00
"fmt"
2019-04-10 21:08:22 +00:00
"io"
2019-01-17 18:36:05 +00:00
"mime"
2018-09-04 21:08:00 +00:00
"net/http"
2018-09-14 04:01:07 +00:00
"time"
2018-09-04 21:08:00 +00:00
"unicode/utf8"
2018-09-06 18:09:52 +00:00
"github.com/influxdata/flux"
2018-09-14 04:01:07 +00:00
"github.com/influxdata/flux/ast"
2018-09-06 18:09:52 +00:00
"github.com/influxdata/flux/csv"
2018-09-11 22:56:51 +00:00
"github.com/influxdata/flux/lang"
2020-04-03 17:39:20 +00:00
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/jsonweb"
"github.com/influxdata/influxdb/v2/query"
2021-04-07 18:42:55 +00:00
"github.com/influxdata/influxdb/v2/query/fluxlang"
2018-09-04 21:08:00 +00:00
)
// QueryRequest is a flux query request.
type QueryRequest struct {
2020-02-07 22:20:50 +00:00
Type string ` json:"type" `
Query string ` json:"query" `
// Flux fields
2020-09-17 00:59:15 +00:00
Extern json . RawMessage ` json:"extern,omitempty" `
AST json . RawMessage ` json:"ast,omitempty" `
Dialect QueryDialect ` json:"dialect" `
Now time . Time ` json:"now" `
2018-09-04 21:08:00 +00:00
2019-02-21 21:11:50 +00:00
Org * influxdb . Organization ` json:"-" `
2019-12-30 17:06:51 +00:00
// PreferNoContent specifies if the Response to this request should
// contain any result. This is done for avoiding unnecessary
// bandwidth consumption in certain cases. For example, when the
// query produces side effects and the results do not matter. E.g.:
// from(...) |> ... |> to()
// For example, tasks do not use the results of queries, but only
// care about their side effects.
// To obtain a QueryRequest with no result, add the header
// `Prefer: return-no-content` to the HTTP request.
PreferNoContent bool
2020-01-15 15:03:40 +00:00
// PreferNoContentWithError is the same as above, but it forces the
// Response to contain an error if that is a Flux runtime error encoded
// in the response body.
// To obtain a QueryRequest with no result but runtime errors,
// add the header `Prefer: return-no-content-with-error` to the HTTP request.
PreferNoContentWithError bool
2018-09-04 21:08:00 +00:00
}
// QueryDialect is the formatting options for the query response.
type QueryDialect struct {
Header * bool ` json:"header" `
Delimiter string ` json:"delimiter" `
CommentPrefix string ` json:"commentPrefix" `
DateTimeFormat string ` json:"dateTimeFormat" `
Annotations [ ] string ` json:"annotations" `
}
// WithDefaults adds default values to the request.
func ( r QueryRequest ) WithDefaults ( ) QueryRequest {
if r . Type == "" {
r . Type = "flux"
}
if r . Dialect . Delimiter == "" {
r . Dialect . Delimiter = ","
}
if r . Dialect . DateTimeFormat == "" {
r . Dialect . DateTimeFormat = "RFC3339"
}
if r . Dialect . Header == nil {
header := true
r . Dialect . Header = & header
}
return r
}
// Validate checks the query request and returns an error if the request is invalid.
func ( r QueryRequest ) Validate ( ) error {
2020-03-09 18:30:43 +00:00
if r . Query == "" && r . AST == nil {
2019-04-09 14:35:42 +00:00
return errors . New ( ` request body requires either query or AST ` )
2018-09-04 21:08:00 +00:00
}
2022-01-31 16:34:37 +00:00
if r . Type != "flux" {
2018-09-04 21:08:00 +00:00
return fmt . Errorf ( ` unknown query type: %s ` , r . Type )
}
if len ( r . Dialect . CommentPrefix ) > 1 {
return fmt . Errorf ( "invalid dialect comment prefix: must be length 0 or 1" )
}
if len ( r . Dialect . Delimiter ) != 1 {
return fmt . Errorf ( "invalid dialect delimeter: must be length 1" )
}
rune , size := utf8 . DecodeRuneInString ( r . Dialect . Delimiter )
if rune == utf8 . RuneError && size == 1 {
return fmt . Errorf ( "invalid dialect delimeter character" )
}
for _ , a := range r . Dialect . Annotations {
switch a {
case "group" , "datatype" , "default" :
default :
return fmt . Errorf ( ` unknown dialect annotation type: %s ` , a )
}
}
switch r . Dialect . DateTimeFormat {
case "RFC3339" , "RFC3339Nano" :
default :
return fmt . Errorf ( ` unknown dialect date time format: %s ` , r . Dialect . DateTimeFormat )
}
return nil
}
2018-12-05 18:13:26 +00:00
// QueryAnalysis is a structured response of errors.
type QueryAnalysis struct {
Errors [ ] queryParseError ` json:"errors" `
}
type queryParseError struct {
Line int ` json:"line" `
Column int ` json:"column" `
Character int ` json:"character" `
Message string ` json:"message" `
}
// Analyze attempts to parse the query request and returns any errors
// encountered in a structured way.
2021-04-07 18:42:55 +00:00
func ( r QueryRequest ) Analyze ( l fluxlang . FluxLanguageService ) ( * QueryAnalysis , error ) {
2018-12-05 18:13:26 +00:00
switch r . Type {
case "flux" :
2020-03-05 16:32:17 +00:00
return r . analyzeFluxQuery ( l )
2018-12-05 18:13:26 +00:00
}
return nil , fmt . Errorf ( "unknown query request type %s" , r . Type )
}
2021-04-07 18:42:55 +00:00
func ( r QueryRequest ) analyzeFluxQuery ( l fluxlang . FluxLanguageService ) ( * QueryAnalysis , error ) {
2018-12-05 18:13:26 +00:00
a := & QueryAnalysis { }
2020-03-05 20:36:58 +00:00
pkg , err := query . Parse ( l , r . Query )
2020-03-05 16:32:17 +00:00
if pkg == nil {
return nil , err
}
2019-01-04 18:08:07 +00:00
errCount := ast . Check ( pkg )
if errCount == 0 {
2018-12-05 18:13:26 +00:00
a . Errors = [ ] queryParseError { }
return a , nil
}
2019-01-04 18:08:07 +00:00
a . Errors = make ( [ ] queryParseError , 0 , errCount )
ast . Walk ( ast . CreateVisitor ( func ( node ast . Node ) {
loc := node . Location ( )
for _ , err := range node . Errs ( ) {
a . Errors = append ( a . Errors , queryParseError {
Line : loc . Start . Line ,
Column : loc . Start . Column ,
Message : err . Msg ,
} )
2018-12-05 18:13:26 +00:00
}
2019-01-04 18:08:07 +00:00
} ) , pkg )
2018-12-05 18:13:26 +00:00
return a , nil
}
2018-09-06 18:09:52 +00:00
// ProxyRequest returns a request to proxy from the flux.
2018-09-11 22:56:51 +00:00
func ( r QueryRequest ) ProxyRequest ( ) ( * query . ProxyRequest , error ) {
2018-09-14 04:01:07 +00:00
return r . proxyRequest ( time . Now )
}
func ( r QueryRequest ) proxyRequest ( now func ( ) time . Time ) ( * query . ProxyRequest , error ) {
2018-09-05 14:33:10 +00:00
if err := r . Validate ( ) ; err != nil {
return nil , err
}
2020-04-07 16:42:13 +00:00
n := r . Now
if n . IsZero ( ) {
n = now ( )
}
2019-04-09 14:35:42 +00:00
// Query is preferred over AST
2018-09-06 18:09:52 +00:00
var compiler flux . Compiler
2018-09-04 21:08:00 +00:00
if r . Query != "" {
2020-02-07 22:20:50 +00:00
switch r . Type {
case "flux" :
fallthrough
default :
compiler = lang . FluxCompiler {
2020-04-07 16:42:13 +00:00
Now : n ,
2020-02-07 22:20:50 +00:00
Extern : r . Extern ,
Query : r . Query ,
}
2019-02-21 21:11:50 +00:00
}
2020-09-17 00:59:15 +00:00
} else if len ( r . AST ) > 0 {
2019-02-21 21:11:50 +00:00
c := lang . ASTCompiler {
2020-09-17 00:59:15 +00:00
Extern : r . Extern ,
AST : r . AST ,
Now : n ,
2019-02-21 21:11:50 +00:00
}
compiler = c
2018-09-04 21:08:00 +00:00
}
delimiter , _ := utf8 . DecodeRuneInString ( r . Dialect . Delimiter )
noHeader := false
if r . Dialect . Header != nil {
noHeader = ! * r . Dialect . Header
}
2019-12-30 17:06:51 +00:00
var dialect flux . Dialect
if r . PreferNoContent {
dialect = & query . NoContentDialect { }
} else {
2022-01-31 16:34:37 +00:00
// TODO(nathanielc): Use commentPrefix and dateTimeFormat
// once they are supported.
encConfig := csv . ResultEncoderConfig {
NoHeader : noHeader ,
Delimiter : delimiter ,
Annotations : r . Dialect . Annotations ,
}
if r . PreferNoContentWithError {
dialect = & query . NoContentWithErrorDialect {
ResultEncoderConfig : encConfig ,
2020-02-07 22:20:50 +00:00
}
2022-01-31 16:34:37 +00:00
} else {
dialect = & csv . Dialect {
ResultEncoderConfig : encConfig ,
2020-01-15 15:03:40 +00:00
}
2019-12-30 17:06:51 +00:00
}
}
return & query . ProxyRequest {
Request : query . Request {
OrganizationID : r . Org . ID ,
Compiler : compiler ,
2018-09-04 21:08:00 +00:00
} ,
2019-12-30 17:06:51 +00:00
Dialect : dialect ,
2018-09-05 14:33:10 +00:00
} , nil
2018-09-04 21:08:00 +00:00
}
2018-09-12 21:10:09 +00:00
// QueryRequestFromProxyRequest converts a query.ProxyRequest into a QueryRequest.
// The ProxyRequest must contain supported compilers and dialects otherwise an error occurs.
func QueryRequestFromProxyRequest ( req * query . ProxyRequest ) ( * QueryRequest , error ) {
qr := new ( QueryRequest )
switch c := req . Request . Compiler . ( type ) {
case lang . FluxCompiler :
qr . Type = "flux"
qr . Query = c . Query
2019-06-27 21:33:22 +00:00
qr . Extern = c . Extern
2020-06-18 16:10:44 +00:00
qr . Now = c . Now
2019-02-21 21:11:50 +00:00
case lang . ASTCompiler :
qr . Type = "flux"
qr . AST = c . AST
2020-04-07 16:42:13 +00:00
qr . Now = c . Now
2018-09-12 21:10:09 +00:00
default :
return nil , fmt . Errorf ( "unsupported compiler %T" , c )
}
switch d := req . Dialect . ( type ) {
case * csv . Dialect :
var header = ! d . ResultEncoderConfig . NoHeader
qr . Dialect . Header = & header
qr . Dialect . Delimiter = string ( d . ResultEncoderConfig . Delimiter )
qr . Dialect . CommentPrefix = "#"
qr . Dialect . DateTimeFormat = "RFC3339"
qr . Dialect . Annotations = d . ResultEncoderConfig . Annotations
2019-12-30 17:06:51 +00:00
case * query . NoContentDialect :
qr . PreferNoContent = true
2020-01-15 15:03:40 +00:00
case * query . NoContentWithErrorDialect :
qr . PreferNoContentWithError = true
2018-09-12 21:10:09 +00:00
default :
return nil , fmt . Errorf ( "unsupported dialect %T" , d )
}
return qr , nil
}
2021-09-22 13:35:06 +00:00
const fluxContentType = "application/vnd.flux"
2019-04-10 21:08:22 +00:00
func decodeQueryRequest ( ctx context . Context , r * http . Request , svc influxdb . OrganizationService ) ( * QueryRequest , int , error ) {
2018-09-04 21:08:00 +00:00
var req QueryRequest
2019-04-10 21:08:22 +00:00
body := & countReader { Reader : r . Body }
2019-01-17 18:36:05 +00:00
var contentType = "application/json"
if ct := r . Header . Get ( "Content-Type" ) ; ct != "" {
contentType = ct
}
mt , _ , err := mime . ParseMediaType ( contentType )
if err != nil {
2019-04-10 21:08:22 +00:00
return nil , body . bytesRead , err
2019-01-17 18:36:05 +00:00
}
switch mt {
2021-09-22 13:35:06 +00:00
case fluxContentType :
2022-04-13 20:24:27 +00:00
octets , err := io . ReadAll ( body )
2019-01-17 18:36:05 +00:00
if err != nil {
2019-04-10 21:08:22 +00:00
return nil , body . bytesRead , err
2019-01-02 19:36:16 +00:00
}
2019-04-10 21:08:22 +00:00
req . Query = string ( octets )
2019-01-17 18:36:05 +00:00
case "application/json" :
fallthrough
default :
2019-04-10 21:08:22 +00:00
if err := json . NewDecoder ( body ) . Decode ( & req ) ; err != nil {
2021-09-22 13:35:06 +00:00
return nil , body . bytesRead ,
fmt . Errorf ( "failed parsing request body as JSON; if sending a raw Flux script, set 'Content-Type: %s' in your request headers: %w" , fluxContentType , err )
2019-01-02 19:36:16 +00:00
}
2018-09-04 21:08:00 +00:00
}
2020-01-16 13:38:43 +00:00
switch hv := r . Header . Get ( query . PreferHeaderKey ) ; hv {
case query . PreferNoContentHeaderValue :
2019-12-30 17:06:51 +00:00
req . PreferNoContent = true
2020-01-16 13:38:43 +00:00
case query . PreferNoContentWErrHeaderValue :
2020-01-15 15:03:40 +00:00
req . PreferNoContentWithError = true
2019-12-30 17:06:51 +00:00
}
2018-09-04 21:08:00 +00:00
req = req . WithDefaults ( )
2019-01-17 18:36:05 +00:00
if err := req . Validate ( ) ; err != nil {
2019-04-10 21:08:22 +00:00
return nil , body . bytesRead , err
2018-09-04 21:08:00 +00:00
}
2018-11-07 16:31:42 +00:00
req . Org , err = queryOrganization ( ctx , r , svc )
2019-04-10 21:08:22 +00:00
return & req , body . bytesRead , err
}
type countReader struct {
bytesRead int
io . Reader
}
func ( r * countReader ) Read ( p [ ] byte ) ( n int , err error ) {
n , err = r . Reader . Read ( p )
r . bytesRead += n
return n , err
2018-09-04 21:08:00 +00:00
}
2019-04-10 21:08:22 +00:00
func decodeProxyQueryRequest ( ctx context . Context , r * http . Request , auth influxdb . Authorizer , svc influxdb . OrganizationService ) ( * query . ProxyRequest , int , error ) {
req , n , err := decodeQueryRequest ( ctx , r , svc )
2018-09-04 21:08:00 +00:00
if err != nil {
2019-04-10 21:08:22 +00:00
return nil , n , err
2018-09-04 21:08:00 +00:00
}
2018-09-13 15:39:08 +00:00
pr , err := req . ProxyRequest ( )
if err != nil {
2019-04-10 21:08:22 +00:00
return nil , n , err
2018-09-13 15:39:08 +00:00
}
2019-03-07 21:32:48 +00:00
var token * influxdb . Authorization
switch a := auth . ( type ) {
case * influxdb . Authorization :
token = a
case * influxdb . Session :
token = a . EphemeralAuth ( req . Org . ID )
2019-11-22 09:12:36 +00:00
case * jsonweb . Token :
token = a . EphemeralAuth ( req . Org . ID )
2019-03-07 21:32:48 +00:00
default :
2019-04-10 21:08:22 +00:00
return pr , n , influxdb . ErrAuthorizerNotSupported
2018-11-20 23:20:51 +00:00
}
2019-03-07 21:32:48 +00:00
pr . Request . Authorization = token
2019-04-10 21:08:22 +00:00
return pr , n , nil
2018-09-04 21:08:00 +00:00
}