diff --git a/quesma/quesma/functionality/terms_enum/terms_enum.go b/quesma/quesma/functionality/terms_enum/terms_enum.go index 4b30a3735..0d53a8de4 100644 --- a/quesma/quesma/functionality/terms_enum/terms_enum.go +++ b/quesma/quesma/functionality/terms_enum/terms_enum.go @@ -102,7 +102,7 @@ func handleTermsEnumRequest(ctx context.Context, body types.JSON, qt *queryparse Id: ctx.Value(tracing.RequestIdCtxKey).(string), Path: path, IncomingQueryBody: reqBody, - QueryBodyTranslated: []byte(selectQuery.SelectCommand.String()), + QueryBodyTranslated: [][]byte{[]byte(selectQuery.SelectCommand.String())}, QueryTranslatedResults: result, SecondaryTook: time.Since(startTime), }) diff --git a/quesma/quesma/search.go b/quesma/quesma/search.go index 9fedc5c85..93e16773c 100644 --- a/quesma/quesma/search.go +++ b/quesma/quesma/search.go @@ -134,7 +134,7 @@ func (q *QueryRunner) handleAsyncSearch(ctx context.Context, indexPattern string type AsyncSearchWithError struct { response *model.SearchResp - translatedQueryBody []byte + translatedQueryBody [][]byte err error } @@ -277,14 +277,16 @@ func (q *QueryRunner) handleSearchCommon(ctx context.Context, indexPattern strin }() } else { - queriesBody := "" - for _, query := range queries { - queriesBody += query.SelectCommand.String() + "\n" + queriesBody := make([][]byte, len(queries)) + queriesBodyConcat := "" + for i, query := range queries { + queriesBody[i] = []byte(query.SelectCommand.String()) + queriesBodyConcat += query.SelectCommand.String() + "\n" } responseBody = []byte(fmt.Sprintf("Invalid Queries: %s, err: %v", queriesBody, err)) - logger.ErrorWithCtxAndReason(ctx, "Quesma generated invalid SQL query").Msg(queriesBody) + logger.ErrorWithCtxAndReason(ctx, "Quesma generated invalid SQL query").Msg(queriesBodyConcat) bodyAsBytes, _ := body.Bytes() - pushSecondaryInfo(q.quesmaManagementConsole, id, path, bodyAsBytes, []byte(queriesBody), responseBody, startTime) + pushSecondaryInfo(q.quesmaManagementConsole, id, path, bodyAsBytes, queriesBody, responseBody, startTime) return responseBody, errors.New(string(responseBody)) } @@ -556,9 +558,9 @@ func (q *QueryRunner) runQueryJobs(jobs []QueryJob) ([][]model.QueryResultRow, e func (q *QueryRunner) searchWorkerCommon( ctx context.Context, queries []*model.Query, - table *clickhouse.Table) (translatedQueryBody []byte, hits [][]model.QueryResultRow, err error) { - sqls := "" + table *clickhouse.Table) (translatedQueryBody [][]byte, hits [][]model.QueryResultRow, err error) { + translatedQueryBody = make([][]byte, len(queries)) hits = make([][]model.QueryResultRow, len(queries)) var jobs []QueryJob @@ -573,7 +575,7 @@ func (q *QueryRunner) searchWorkerCommon( sql := query.SelectCommand.String() logger.InfoWithCtx(ctx).Msgf("SQL: %s", sql) - sqls += sql + "\n" + translatedQueryBody[i] = []byte(sql) if q.isInternalKibanaQuery(query) { hits[i] = make([]model.QueryResultRow, 0) @@ -609,7 +611,6 @@ func (q *QueryRunner) searchWorkerCommon( hits[hitsPosition] = dbHits[jobId] } - translatedQueryBody = []byte(sqls) return } @@ -617,7 +618,7 @@ func (q *QueryRunner) searchWorker(ctx context.Context, aggregations []*model.Query, table *clickhouse.Table, doneCh chan<- AsyncSearchWithError, - optAsync *AsyncQuery) (translatedQueryBody []byte, resultRows [][]model.QueryResultRow, err error) { + optAsync *AsyncQuery) (translatedQueryBody [][]byte, resultRows [][]model.QueryResultRow, err error) { if optAsync != nil { if q.reachedQueriesLimit(ctx, optAsync.asyncRequestIdStr, doneCh) { return @@ -674,7 +675,7 @@ func (q *QueryRunner) postProcessResults(table *clickhouse.Table, results [][]mo return geoIpTransformer.Transform(res) } -func pushSecondaryInfo(qmc *ui.QuesmaManagementConsole, Id, Path string, IncomingQueryBody, QueryBodyTranslated, QueryTranslatedResults []byte, startTime time.Time) { +func pushSecondaryInfo(qmc *ui.QuesmaManagementConsole, Id, Path string, IncomingQueryBody []byte, QueryBodyTranslated [][]byte, QueryTranslatedResults []byte, startTime time.Time) { qmc.PushSecondaryInfo(&ui.QueryDebugSecondarySource{ Id: Id, Path: Path, diff --git a/quesma/quesma/ui/asset/head.html b/quesma/quesma/ui/asset/head.html index ab9100c2b..b8eac7b76 100644 --- a/quesma/quesma/ui/asset/head.html +++ b/quesma/quesma/ui/asset/head.html @@ -192,7 +192,8 @@ background-color: rgb(221, 226, 235); } - .debug-body a { + .debug-body a, + .query-body-translated a { text-decoration: none; } @@ -203,7 +204,8 @@ } .right .debug-body a, - .bottom_left .debug-body a { + .bottom_left .debug-body a, + #request-info .query-body-translated a { color: white; display: block; } @@ -214,7 +216,8 @@ } .right .debug-body a:hover, - .bottom_left .debug-body a:hover { + .bottom_left .debug-body a:hover, + #request-info .query-body-translated a:hover { background-color: rgb(40, 40, 40); } diff --git a/quesma/quesma/ui/html_pages_test.go b/quesma/quesma/ui/html_pages_test.go index 2d2c2c017..68c992744 100644 --- a/quesma/quesma/ui/html_pages_test.go +++ b/quesma/quesma/ui/html_pages_test.go @@ -26,7 +26,7 @@ func TestHtmlPages(t *testing.T) { qmc.PushSecondaryInfo(&QueryDebugSecondarySource{Id: id, Path: xss, IncomingQueryBody: xssBytes, - QueryBodyTranslated: xssBytes, + QueryBodyTranslated: [][]byte{xssBytes}, QueryTranslatedResults: xssBytes, }) log := fmt.Sprintf(`{"request_id": "%s", "message": "%s"}`, id, xss) diff --git a/quesma/quesma/ui/live_tail.go b/quesma/quesma/ui/live_tail.go index 4e635ab7b..8b4b56eae 100644 --- a/quesma/quesma/ui/live_tail.go +++ b/quesma/quesma/ui/live_tail.go @@ -3,6 +3,7 @@ package ui import ( + "bytes" "fmt" "quesma/buildinfo" "quesma/quesma/config" @@ -246,7 +247,7 @@ func (qmc *QuesmaManagementConsole) populateQueries(debugKeyValueSlice []queryDe tookStr := fmt.Sprintf(" took %d ms", v.query.SecondaryTook.Milliseconds()) buffer.Html("

UUID:").Text(v.id).Text(tookStr).Html(errorBanner(v.query)).Html("

\n") buffer.Html(`
`)
-		buffer.Text(util.SqlPrettyPrint(v.query.QueryBodyTranslated))
+		buffer.Text(util.SqlPrettyPrint(bytes.Join(v.query.QueryBodyTranslated, []byte{})))
 		buffer.Html("\n
") if withLinks { buffer.Html("\n") diff --git a/quesma/quesma/ui/live_tail_drilldown.go b/quesma/quesma/ui/live_tail_drilldown.go index b63de7d98..3793f08e9 100644 --- a/quesma/quesma/ui/live_tail_drilldown.go +++ b/quesma/quesma/ui/live_tail_drilldown.go @@ -3,6 +3,7 @@ package ui import ( + "encoding/base64" "encoding/json" "fmt" "gopkg.in/yaml.v3" @@ -44,9 +45,21 @@ func (qmc *QuesmaManagementConsole) generateReportForRequestId(requestId string) buffer.Html(`
` + "\n") buffer.Html("

Translated SQL:

\n") - buffer.Html(`
`)
-		buffer.Text(util.SqlPrettyPrint(request.QueryBodyTranslated))
-		buffer.Html("\n
") + for _, queryBody := range request.QueryBodyTranslated { + prettyQueryBody := util.SqlPrettyPrint(queryBody) + if qmc.cfg.ClickHouse.AdminUrl != nil { + // ClickHouse web UI /play expects a base64-encoded query + // in the URL: + base64QueryBody := base64.StdEncoding.EncodeToString([]byte(prettyQueryBody)) + buffer.Html(``) + } + buffer.Html(`
`)
+			buffer.Text(prettyQueryBody)
+			buffer.Html("\n
") + if qmc.cfg.ClickHouse.AdminUrl != nil { + buffer.Html(`
`) + } + } buffer.Html(`
` + "\n") buffer.Html(`
` + "\n") diff --git a/quesma/quesma/ui/management_console.go b/quesma/quesma/ui/management_console.go index 8d9cbf317..c4b74d0cf 100644 --- a/quesma/quesma/ui/management_console.go +++ b/quesma/quesma/ui/management_console.go @@ -3,6 +3,7 @@ package ui import ( + "bytes" "github.com/rs/zerolog" "quesma/elasticsearch" "quesma/schema" @@ -47,7 +48,7 @@ type QueryDebugSecondarySource struct { Path string IncomingQueryBody []byte - QueryBodyTranslated []byte + QueryBodyTranslated [][]byte QueryTranslatedResults []byte SecondaryTook time.Duration } @@ -134,7 +135,7 @@ func (qmc *QuesmaManagementConsole) RecordRequest(typeName string, took time.Dur func (qdi *queryDebugInfo) requestContains(queryStr string) bool { potentialPlaces := [][]byte{qdi.QueryDebugSecondarySource.IncomingQueryBody, - qdi.QueryDebugSecondarySource.QueryBodyTranslated} + bytes.Join(qdi.QueryDebugSecondarySource.QueryBodyTranslated, []byte{})} for _, potentialPlace := range potentialPlaces { if potentialPlace != nil && strings.Contains(string(potentialPlace), queryStr) { return true