603 lines
16 KiB
Go
603 lines
16 KiB
Go
package query_test
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/influxdata/influxdb/query"
|
|
"github.com/influxdata/influxql"
|
|
)
|
|
|
|
var errUnexpected = errors.New("unexpected error")
|
|
|
|
type StatementExecutor struct {
|
|
ExecuteStatementFn func(stmt influxql.Statement, ctx *query.ExecutionContext) error
|
|
}
|
|
|
|
func (e *StatementExecutor) ExecuteStatement(ctx *query.ExecutionContext, stmt influxql.Statement) error {
|
|
return e.ExecuteStatementFn(stmt, ctx)
|
|
}
|
|
|
|
type StatementNormalizerExecutor struct {
|
|
StatementExecutor
|
|
NormalizeStatementFn func(stmt influxql.Statement, database, retentionPolicy string) error
|
|
}
|
|
|
|
func (e *StatementNormalizerExecutor) NormalizeStatement(stmt influxql.Statement, database, retentionPolicy string) error {
|
|
return e.NormalizeStatementFn(stmt, database, retentionPolicy)
|
|
}
|
|
|
|
func NewQueryExecutor() *query.Executor {
|
|
return query.NewExecutor()
|
|
}
|
|
|
|
func TestQueryExecutor_AttachQuery(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
if ctx.QueryID != 1 {
|
|
t.Errorf("incorrect query id: exp=1 got=%d", ctx.QueryID)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
}
|
|
|
|
func TestQueryExecutor_KillQuery(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
qid := make(chan uint64)
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
switch stmt.(type) {
|
|
case *influxql.KillQueryStatement:
|
|
return e.TaskManager.ExecuteStatement(ctx, stmt)
|
|
}
|
|
|
|
qid <- ctx.QueryID
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("killing the query did not close the channel after 100 milliseconds")
|
|
return errUnexpected
|
|
}
|
|
},
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
q, err = influxql.ParseQuery(fmt.Sprintf("KILL QUERY %d", <-qid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
|
|
result := <-results
|
|
if result.Err != query.ErrQueryInterrupted {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_KillQuery_Zombie(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
qid := make(chan uint64)
|
|
done := make(chan struct{})
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
switch stmt.(type) {
|
|
case *influxql.KillQueryStatement, *influxql.ShowQueriesStatement:
|
|
return e.TaskManager.ExecuteStatement(ctx, stmt)
|
|
}
|
|
|
|
qid <- ctx.QueryID
|
|
select {
|
|
case <-ctx.Done():
|
|
select {
|
|
case <-done:
|
|
// Keep the query running until we run SHOW QUERIES.
|
|
case <-time.After(100 * time.Millisecond):
|
|
// Ensure that we don't have a lingering goroutine.
|
|
}
|
|
return query.ErrQueryInterrupted
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("killing the query did not close the channel after 100 milliseconds")
|
|
return errUnexpected
|
|
}
|
|
},
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
q, err = influxql.ParseQuery(fmt.Sprintf("KILL QUERY %d", <-qid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
|
|
// Display the queries and ensure that the original is still in there.
|
|
q, err = influxql.ParseQuery("SHOW QUERIES")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tasks := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
|
|
// The killed query should still be there.
|
|
task := <-tasks
|
|
if len(task.Series) != 1 {
|
|
t.Errorf("expected %d series, got %d", 1, len(task.Series))
|
|
} else if len(task.Series[0].Values) != 2 {
|
|
t.Errorf("expected %d rows, got %d", 2, len(task.Series[0].Values))
|
|
}
|
|
close(done)
|
|
|
|
// The original query should return.
|
|
result := <-results
|
|
if result.Err != query.ErrQueryInterrupted {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_KillQuery_CloseTaskManager(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
qid := make(chan uint64)
|
|
|
|
// Open a channel to stall the statement executor forever. This keeps the statement executor
|
|
// running even after we kill the query which can happen with some queries. We only close it once
|
|
// the test has finished running.
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
switch stmt.(type) {
|
|
case *influxql.KillQueryStatement, *influxql.ShowQueriesStatement:
|
|
return e.TaskManager.ExecuteStatement(ctx, stmt)
|
|
}
|
|
|
|
qid <- ctx.QueryID
|
|
<-done
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// Kill the query. This should switch it into a zombie state.
|
|
go discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
q, err = influxql.ParseQuery(fmt.Sprintf("KILL QUERY %d", <-qid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
|
|
// Display the queries and ensure that the original is still in there.
|
|
q, err = influxql.ParseQuery("SHOW QUERIES")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tasks := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
|
|
// The killed query should still be there.
|
|
task := <-tasks
|
|
if len(task.Series) != 1 {
|
|
t.Errorf("expected %d series, got %d", 1, len(task.Series))
|
|
} else if len(task.Series[0].Values) != 2 {
|
|
t.Errorf("expected %d rows, got %d", 2, len(task.Series[0].Values))
|
|
}
|
|
|
|
// Close the task manager to ensure it doesn't cause a panic.
|
|
if err := e.TaskManager.Close(); err != nil {
|
|
t.Errorf("unexpected error: %s", err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_KillQuery_AlreadyKilled(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
qid := make(chan uint64)
|
|
|
|
// Open a channel to stall the statement executor forever. This keeps the statement executor
|
|
// running even after we kill the query which can happen with some queries. We only close it once
|
|
// the test has finished running.
|
|
done := make(chan struct{})
|
|
defer close(done)
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
switch stmt.(type) {
|
|
case *influxql.KillQueryStatement, *influxql.ShowQueriesStatement:
|
|
return e.TaskManager.ExecuteStatement(ctx, stmt)
|
|
}
|
|
|
|
qid <- ctx.QueryID
|
|
<-done
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// Kill the query. This should switch it into a zombie state.
|
|
go discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
q, err = influxql.ParseQuery(fmt.Sprintf("KILL QUERY %d", <-qid))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
|
|
// Now attempt to kill it again. We should get an error.
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if got, want := result.Err, query.ErrAlreadyKilled; got != want {
|
|
t.Errorf("unexpected error: got=%v want=%v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Interrupt(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("killing the query did not close the channel after 100 milliseconds")
|
|
return errUnexpected
|
|
}
|
|
},
|
|
}
|
|
|
|
closing := make(chan struct{})
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, closing)
|
|
close(closing)
|
|
result := <-results
|
|
if result.Err != query.ErrQueryInterrupted {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Abort(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ch1 := make(chan struct{})
|
|
ch2 := make(chan struct{})
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
<-ch1
|
|
if err := ctx.Send(&query.Result{Err: errUnexpected}); err != query.ErrQueryAborted {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
close(ch2)
|
|
return nil
|
|
},
|
|
}
|
|
|
|
done := make(chan struct{})
|
|
close(done)
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{AbortCh: done}, nil)
|
|
close(ch1)
|
|
|
|
<-ch2
|
|
discardOutput(results)
|
|
}
|
|
|
|
func TestQueryExecutor_ShowQueries(t *testing.T) {
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
switch stmt.(type) {
|
|
case *influxql.ShowQueriesStatement:
|
|
return e.TaskManager.ExecuteStatement(ctx, stmt)
|
|
}
|
|
|
|
t.Errorf("unexpected statement: %s", stmt)
|
|
return errUnexpected
|
|
},
|
|
}
|
|
|
|
q, err := influxql.ParseQuery(`SHOW QUERIES`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if len(result.Series) != 1 {
|
|
t.Errorf("expected %d series, got %d", 1, len(result.Series))
|
|
} else if len(result.Series[0].Values) != 1 {
|
|
t.Errorf("expected %d row, got %d", 1, len(result.Series[0].Values))
|
|
}
|
|
if result.Err != nil {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Limit_Timeout(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(time.Second):
|
|
t.Errorf("timeout has not killed the query")
|
|
return errUnexpected
|
|
}
|
|
},
|
|
}
|
|
e.TaskManager.QueryTimeout = time.Nanosecond
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if result.Err == nil || !strings.Contains(result.Err.Error(), "query-timeout") {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Limit_ConcurrentQueries(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
qid := make(chan uint64)
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
qid <- ctx.QueryID
|
|
<-ctx.Done()
|
|
return ctx.Err()
|
|
},
|
|
}
|
|
e.TaskManager.MaxConcurrentQueries = 1
|
|
defer e.Close()
|
|
|
|
// Start first query and wait for it to be executing.
|
|
go discardOutput(e.ExecuteQuery(q, query.ExecutionOptions{}, nil))
|
|
<-qid
|
|
|
|
// Start second query and expect for it to fail.
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
|
|
select {
|
|
case result := <-results:
|
|
if len(result.Series) != 0 {
|
|
t.Errorf("expected %d rows, got %d", 0, len(result.Series))
|
|
}
|
|
if result.Err == nil || !strings.Contains(result.Err.Error(), "max-concurrent-queries") {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
case <-qid:
|
|
t.Errorf("unexpected statement execution for the second query")
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Close(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ch1 := make(chan struct{})
|
|
ch2 := make(chan struct{})
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
close(ch1)
|
|
<-ctx.Done()
|
|
return ctx.Err()
|
|
},
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
go func(results <-chan *query.Result) {
|
|
result := <-results
|
|
if result.Err != query.ErrQueryEngineShutdown {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
close(ch2)
|
|
}(results)
|
|
|
|
// Wait for the statement to start executing.
|
|
<-ch1
|
|
|
|
// Close the query executor.
|
|
e.Close()
|
|
|
|
// Check that the statement gets interrupted and finishes.
|
|
select {
|
|
case <-ch2:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Fatal("closing the query manager did not kill the query after 100 milliseconds")
|
|
}
|
|
|
|
results = e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if len(result.Series) != 0 {
|
|
t.Errorf("expected %d rows, got %d", 0, len(result.Series))
|
|
}
|
|
if result.Err != query.ErrQueryEngineShutdown {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_Panic(t *testing.T) {
|
|
q, err := influxql.ParseQuery(`SELECT count(value) FROM cpu`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
panic("test error")
|
|
},
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if len(result.Series) != 0 {
|
|
t.Errorf("expected %d rows, got %d", 0, len(result.Series))
|
|
}
|
|
if result.Err == nil || result.Err.Error() != "SELECT count(value) FROM cpu [panic:test error]" {
|
|
t.Errorf("unexpected error: %s", result.Err)
|
|
}
|
|
}
|
|
|
|
const goodStatement = `SELECT count(value) FROM cpu`
|
|
|
|
func TestQueryExecutor_NotExecuted(t *testing.T) {
|
|
var executorFailIndex int
|
|
var executorCallCount int
|
|
queryStatements := []string{goodStatement, goodStatement, goodStatement, goodStatement, goodStatement}
|
|
queryStr := strings.Join(queryStatements, ";")
|
|
var closing chan struct{}
|
|
|
|
q, err := influxql.ParseQuery(queryStr)
|
|
if err != nil {
|
|
t.Fatalf("parsing %s: %v", queryStr, err)
|
|
}
|
|
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
defer func() { executorCallCount++ }()
|
|
if executorFailIndex == executorCallCount {
|
|
closing <- struct{}{}
|
|
close(closing)
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil
|
|
}
|
|
} else {
|
|
return ctx.Send(&query.Result{Err: nil})
|
|
}
|
|
},
|
|
}
|
|
testFn := func(testName string, i int) {
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, closing)
|
|
checkNotExecutedResults(t, results, testName, i, len(q.Statements))
|
|
}
|
|
for i := 0; i < len(q.Statements); i++ {
|
|
closing = make(chan struct{})
|
|
executorFailIndex = i
|
|
executorCallCount = 0
|
|
testFn("executor", i)
|
|
}
|
|
}
|
|
|
|
func checkNotExecutedResults(t *testing.T, results <-chan *query.Result, testName string, failIndex int, lenQuery int) {
|
|
notExecutedIndex := failIndex + 1
|
|
for result := range results {
|
|
if result.Err == query.ErrNotExecuted {
|
|
if result.StatementID != notExecutedIndex {
|
|
t.Fatalf("StatementID for ErrNotExecuted in wrong order - expected: %d, got: %d", notExecutedIndex, result.StatementID)
|
|
} else {
|
|
notExecutedIndex++
|
|
}
|
|
}
|
|
}
|
|
if notExecutedIndex != lenQuery {
|
|
t.Fatalf("wrong number of results from %s with fail index of %d - got: %d, expected: %d", testName, failIndex, notExecutedIndex-(1+failIndex), lenQuery-(1+failIndex))
|
|
}
|
|
}
|
|
|
|
func TestQueryExecutor_InvalidSource(t *testing.T) {
|
|
e := NewQueryExecutor()
|
|
e.StatementExecutor = &StatementExecutor{
|
|
ExecuteStatementFn: func(stmt influxql.Statement, ctx *query.ExecutionContext) error {
|
|
return errors.New("statement executed unexpectedly")
|
|
},
|
|
}
|
|
|
|
for i, tt := range []struct {
|
|
q string
|
|
err string
|
|
}{
|
|
{
|
|
q: `SELECT fieldKey, fieldType FROM _fieldKeys`,
|
|
err: `unable to use system source '_fieldKeys': use SHOW FIELD KEYS instead`,
|
|
},
|
|
{
|
|
q: `SELECT "name" FROM _measurements`,
|
|
err: `unable to use system source '_measurements': use SHOW MEASUREMENTS instead`,
|
|
},
|
|
{
|
|
q: `SELECT "key" FROM _series`,
|
|
err: `unable to use system source '_series': use SHOW SERIES instead`,
|
|
},
|
|
{
|
|
q: `SELECT tagKey FROM _tagKeys`,
|
|
err: `unable to use system source '_tagKeys': use SHOW TAG KEYS instead`,
|
|
},
|
|
{
|
|
q: `SELECT "key", value FROM _tags`,
|
|
err: `unable to use system source '_tags': use SHOW TAG VALUES instead`,
|
|
},
|
|
} {
|
|
q, err := influxql.ParseQuery(tt.q)
|
|
if err != nil {
|
|
t.Errorf("%d. unable to parse: %s", i, tt.q)
|
|
continue
|
|
}
|
|
|
|
results := e.ExecuteQuery(q, query.ExecutionOptions{}, nil)
|
|
result := <-results
|
|
if len(result.Series) != 0 {
|
|
t.Errorf("%d. expected %d rows, got %d", 0, i, len(result.Series))
|
|
}
|
|
if result.Err == nil || result.Err.Error() != tt.err {
|
|
t.Errorf("%d. unexpected error: %s", i, result.Err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func discardOutput(results <-chan *query.Result) {
|
|
for range results {
|
|
// Read all results and discard.
|
|
}
|
|
}
|