From 86228ba1a03fc7ae1774b868839dc8b3f77edeed Mon Sep 17 00:00:00 2001 From: Jev Forsberg <46619047+baldm0mma@users.noreply.github.com> Date: Tue, 9 May 2023 08:29:02 -0600 Subject: [PATCH] Datasource: Fix missing raw SQL query in Query Inspector when query returns zero rows (#67844) * baldm0mma/issue5799/ add empty frame with all meta data * baldm0mma/issue5799/ add null test * write an integration test around query results with empty rows * baldm0mma/issue5799/ add tests to mssql and postgres * remove use of apparently reserved keyword in mysql8 * baldm0mma/issue5799 * baldm0mma/issue5799/ update tests * baldm0mma/issue5799/ update test structs * baldm0mma/issue5799/ update annotation --------- Co-authored-by: Michael Mandrus --- pkg/tsdb/mssql/mssql_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/mysql/mysql_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/postgres/postgres_test.go | 44 ++++++++++++++++++++++++++++++ pkg/tsdb/sqleng/sql_engine.go | 8 ++++-- 4 files changed, 138 insertions(+), 2 deletions(-) diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 55705b9a73a..a0e76ed5f78 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -1263,6 +1263,50 @@ func TestMSSQL(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT empty_key, empty_val FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := endpoint.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func TestTransformQueryError(t *testing.T) { diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 58142d23190..ef2b61d5ffc 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -1229,6 +1229,50 @@ func TestIntegrationMySQL(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT * FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := exe.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func InitMySQLTestDB(t *testing.T) *xorm.Engine { diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index e48c79c5ef9..ecf9405eabf 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -1331,6 +1331,50 @@ func TestIntegrationPostgres(t *testing.T) { }) }) }) + + t.Run("Given an empty table", func(t *testing.T) { + type emptyObj struct { + EmptyKey string + EmptyVal int64 + } + + exists, err := sess.IsTableExist(emptyObj{}) + require.NoError(t, err) + if exists { + err := sess.DropTable(emptyObj{}) + require.NoError(t, err) + } + err = sess.CreateTable(emptyObj{}) + require.NoError(t, err) + + t.Run("When no rows are returned, should return an empty frame", func(t *testing.T) { + query := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(`{ + "rawSql": "SELECT empty_key, empty_val FROM empty_obj", + "format": "table" + }`), + RefID: "A", + TimeRange: backend.TimeRange{ + From: time.Now(), + To: time.Now().Add(1 * time.Minute), + }, + }, + }, + } + + resp, err := exe.QueryData(context.Background(), query) + require.NoError(t, err) + queryResult := resp.Responses["A"] + + frames := queryResult.Frames + require.Len(t, frames, 1) + require.Equal(t, 0, frames[0].Rows()) + require.NotNil(t, frames[0].Fields) + require.Empty(t, frames[0].Fields) + }) + }) } func InitPostgresTestDB(t *testing.T) *xorm.Engine { diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index aeb93d411d1..02c91b9a424 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -312,9 +312,13 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG frame.Meta.ExecutedQueryString = interpolatedQuery - // If no rows were returned, no point checking anything else. + // If no rows were returned, clear any previously set `Fields` with a single empty `data.Field` slice. + // Then assign `queryResult.dataResponse.Frames` the current single frame with that single empty Field. + // This assures 1) our visualization doesn't display unwanted empty fields, and also that 2) + // additionally-needed frame data stays intact and is correctly passed to our visulization. if frame.Rows() == 0 { - queryResult.dataResponse.Frames = data.Frames{} + frame.Fields = []*data.Field{} + queryResult.dataResponse.Frames = data.Frames{frame} ch <- queryResult return }