diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1af3992db..0993d70a9 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -13,9 +13,9 @@ jobs: - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.23' - name: Test run: go test -v -race ./... diff --git a/.gitignore b/.gitignore index c07680e61..5b86b6086 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ # High Dynamic Range (HDR) Histogram files *.hdr + +# Compiled binaries +tsbs_* diff --git a/.travis.yml b/.travis.yml index 16bf5534b..49c7b41f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,9 @@ dist: focal jobs: include: - stage: test - name: "Go 1.14" + name: "Go 1.23" go: - - 1.14.x + - 1.23.x install: skip script: - - GO111MODULE=on go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... - - stage: test - name: "Go 1.15" - go: - - 1.15.x - install: skip - script: - - GO111MODULE=on go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... + - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./... diff --git a/cmd/tsbs_generate_queries/databases/questdb/common.go b/cmd/tsbs_generate_queries/databases/questdb/common.go index 0e137405c..dab11644a 100644 --- a/cmd/tsbs_generate_queries/databases/questdb/common.go +++ b/cmd/tsbs_generate_queries/databases/questdb/common.go @@ -1,8 +1,10 @@ package questdb import ( + "encoding/json" "fmt" "net/url" + "strings" "time" "github.com/questdb/tsbs/cmd/tsbs_generate_queries/uses/devops" @@ -19,7 +21,7 @@ func (g *BaseGenerator) GenerateEmptyQuery() query.Query { return query.NewHTTP() } -// fillInQuery fills the query struct with data. +// fillInQuery fills the query struct with data (legacy non-parameterized). func (g *BaseGenerator) fillInQuery(qi query.Query, humanLabel, humanDesc, sql string) { v := url.Values{} v.Set("count", "false") @@ -33,6 +35,55 @@ func (g *BaseGenerator) fillInQuery(qi query.Query, humanLabel, humanDesc, sql s q.Body = nil } +// fillInQueryWithParams fills the query struct with parameterized SQL and bind values. +// sqlTemplate uses $1, $2, etc. placeholders for PostgreSQL prepared statements. +// params contains the values to bind at runtime. +func (g *BaseGenerator) fillInQueryWithParams(qi query.Query, humanLabel, humanDesc, sqlTemplate string, params []interface{}) { + q := qi.(*query.HTTP) + q.HumanLabel = []byte(humanLabel) + q.HumanDescription = []byte(humanDesc) + q.Method = []byte("GET") + + // Store parameterized SQL in RawQuery + q.RawQuery = []byte(sqlTemplate) + + // Store parameters as JSON in Body for pgx mode + paramsJSON, _ := json.Marshal(params) + q.Body = paramsJSON + + // For HTTP mode, we still need to generate the full SQL path + // Substitute parameters to create the HTTP URL + fullSQL := substituteParams(sqlTemplate, params) + v := url.Values{} + v.Set("count", "false") + v.Set("query", fullSQL) + q.Path = []byte(fmt.Sprintf("/exec?%s", v.Encode())) +} + +// substituteParams replaces $1, $2, etc. with actual values for HTTP mode +func substituteParams(sql string, params []interface{}) string { + result := sql + for i, param := range params { + placeholder := fmt.Sprintf("$%d", i+1) + var replacement string + switch v := param.(type) { + case string: + replacement = fmt.Sprintf("'%s'", v) + case []string: + // Handle string arrays for hostname IN clauses + quoted := make([]string, len(v)) + for j, s := range v { + quoted[j] = fmt.Sprintf("'%s'", s) + } + replacement = fmt.Sprintf("(%s)", strings.Join(quoted, ", ")) + default: + replacement = fmt.Sprintf("%v", v) + } + result = strings.Replace(result, placeholder, replacement, 1) + } + return result +} + // NewDevops creates a new devops use case query generator. func (g *BaseGenerator) NewDevops(start, end time.Time, scale int) (utils.QueryGenerator, error) { core, err := devops.NewCore(start, end, scale) diff --git a/cmd/tsbs_generate_queries/databases/questdb/devops.go b/cmd/tsbs_generate_queries/databases/questdb/devops.go index c33b27200..730132618 100644 --- a/cmd/tsbs_generate_queries/databases/questdb/devops.go +++ b/cmd/tsbs_generate_queries/databases/questdb/devops.go @@ -49,24 +49,26 @@ func (d *Devops) MaxAllCPU(qi query.Query, nHosts int, duration time.Duration) { hosts, err := d.GetRandomHosts(nHosts) panicIfErr(err) - sql := fmt.Sprintf(` + // Parameterized SQL for prepared statements + sqlTemplate := fmt.Sprintf(` SELECT - date_trunc('hour', timestamp) AS hour, %s FROM cpu - WHERE hostname IN ('%s') - AND timestamp >= '%s' - AND timestamp < '%s' - GROUP BY hour - ORDER BY hour`, - strings.Join(selectClauses, ", "), - strings.Join(hosts, "', '"), + WHERE hostname IN $1 + AND timestamp >= $2 + AND timestamp < $3 + SAMPLE BY 1h`, + strings.Join(selectClauses, ", ")) + + params := []interface{}{ + hosts, interval.StartString(), - interval.EndString()) + interval.EndString(), + } humanLabel := devops.GetMaxAllLabel("QuestDB", nHosts) humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString()) - d.fillInQuery(qi, humanLabel, humanDesc, sql) + d.fillInQueryWithParams(qi, humanLabel, humanDesc, sqlTemplate, params) } // GroupByTimeAndPrimaryTag selects the AVG of metrics in the group `cpu` per device @@ -82,21 +84,24 @@ func (d *Devops) GroupByTimeAndPrimaryTag(qi query.Query, numMetrics int) { interval := d.Interval.MustRandWindow(devops.DoubleGroupByDuration) selectClauses := d.getSelectAggClauses("avg", metrics) - sql := fmt.Sprintf(` - SELECT date_trunc('hour', timestamp) as timestamp, hostname, + // Parameterized SQL for prepared statements + sqlTemplate := fmt.Sprintf(` + SELECT hostname, %s FROM cpu - WHERE timestamp >= '%s' - AND timestamp < '%s' - GROUP BY timestamp, hostname - ORDER BY timestamp, hostname`, - strings.Join(selectClauses, ", "), + WHERE timestamp >= $1 + AND timestamp < $2 + SAMPLE BY 1h`, + strings.Join(selectClauses, ", ")) + + params := []interface{}{ interval.StartString(), - interval.EndString()) + interval.EndString(), + } humanLabel := devops.GetDoubleGroupByLabel("QuestDB", numMetrics) humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString()) - d.fillInQuery(qi, humanLabel, humanDesc, sql) + d.fillInQueryWithParams(qi, humanLabel, humanDesc, sqlTemplate, params) } // GroupByOrderByLimit populates a query.Query that has a time WHERE clause, @@ -106,19 +111,23 @@ func (d *Devops) GroupByTimeAndPrimaryTag(qi query.Query, numMetrics int) { // groupby-orderby-limit func (d *Devops) GroupByOrderByLimit(qi query.Query) { interval := d.Interval.MustRandWindow(time.Hour) - sql := fmt.Sprintf(` - SELECT date_trunc('minute', timestamp) AS minute, - max(usage_user) + + // Parameterized SQL for prepared statements + sqlTemplate := ` + SELECT max(usage_user) FROM cpu - WHERE timestamp < '%s' - GROUP BY minute - ORDER BY minute DESC - LIMIT 5`, - interval.EndString()) + WHERE timestamp < $1 + SAMPLE BY 1m + ORDER BY timestamp DESC + LIMIT 5` + + params := []interface{}{ + interval.EndString(), + } humanLabel := "QuestDB max cpu over last 5 min-intervals (random end)" humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.EndString()) - d.fillInQuery(qi, humanLabel, humanDesc, sql) + d.fillInQueryWithParams(qi, humanLabel, humanDesc, sqlTemplate, params) } // LastPointPerHost finds the last row for every host in the dataset @@ -142,36 +151,45 @@ func (d *Devops) LastPointPerHost(qi query.Query) { // high-cpu-all func (d *Devops) HighCPUForHosts(qi query.Query, nHosts int) { interval := d.Interval.MustRandWindow(devops.HighCPUDuration) - sql := "" + + var sqlTemplate string + var params []interface{} + if nHosts > 0 { hosts, err := d.GetRandomHosts(nHosts) panicIfErr(err) - sql = fmt.Sprintf(` + // Parameterized SQL for prepared statements + sqlTemplate = ` SELECT * FROM cpu WHERE usage_user > 90.0 - AND hostname IN ('%s') - AND timestamp >= '%s' - AND timestamp < '%s'`, - strings.Join(hosts, "', '"), + AND hostname IN $1 + AND timestamp >= $2 + AND timestamp < $3` + params = []interface{}{ + hosts, interval.StartString(), - interval.EndString()) + interval.EndString(), + } } else { - sql = fmt.Sprintf(` + // Parameterized SQL for prepared statements (no hostname filter) + sqlTemplate = ` SELECT * FROM cpu WHERE usage_user > 90.0 - AND timestamp >= '%s' - AND timestamp < '%s'`, + AND timestamp >= $1 + AND timestamp < $2` + params = []interface{}{ interval.StartString(), - interval.EndString()) + interval.EndString(), + } } humanLabel, err := devops.GetHighCPULabel("QuestDB", nHosts) panicIfErr(err) humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString()) - d.fillInQuery(qi, humanLabel, humanDesc, sql) + d.fillInQueryWithParams(qi, humanLabel, humanDesc, sqlTemplate, params) } // GroupByTime selects the MAX for metrics under 'cpu', per minute for N random @@ -192,23 +210,26 @@ func (d *Devops) GroupByTime(qi query.Query, nHosts, numMetrics int, timeRange t hosts, err := d.GetRandomHosts(nHosts) panicIfErr(err) - sql := fmt.Sprintf(` - SELECT date_trunc('minute', timestamp) as minute, + // Parameterized SQL for prepared statements + sqlTemplate := fmt.Sprintf(` + SELECT %s FROM cpu - WHERE hostname IN ('%s') - AND timestamp >= '%s' - AND timestamp < '%s' - GROUP BY minute - ORDER BY minute`, - strings.Join(selectClauses, ", "), - strings.Join(hosts, "', '"), + WHERE hostname IN $1 + AND timestamp >= $2 + AND timestamp < $3 + SAMPLE BY 1m`, + strings.Join(selectClauses, ", ")) + + params := []interface{}{ + hosts, interval.StartString(), - interval.EndString()) + interval.EndString(), + } humanLabel := fmt.Sprintf( "QuestDB %d cpu metric(s), random %4d hosts, random %s by 1m", numMetrics, nHosts, timeRange) humanDesc := fmt.Sprintf("%s: %s", humanLabel, interval.StartString()) - d.fillInQuery(qi, humanLabel, humanDesc, sql) + d.fillInQueryWithParams(qi, humanLabel, humanDesc, sqlTemplate, params) } diff --git a/cmd/tsbs_generate_queries/databases/questdb/devops_test.go b/cmd/tsbs_generate_queries/databases/questdb/devops_test.go index 338378367..78a9d169a 100644 --- a/cmd/tsbs_generate_queries/databases/questdb/devops_test.go +++ b/cmd/tsbs_generate_queries/databases/questdb/devops_test.go @@ -14,8 +14,8 @@ import ( func TestDevopsGroupByTime(t *testing.T) { expectedHumanLabel := "QuestDB 1 cpu metric(s), random 1 hosts, random 1s by 1m" expectedHumanDesc := "QuestDB 1 cpu metric(s), random 1 hosts, random 1s by 1m: 1970-01-01T00:05:58Z" - expectedQuery := "SELECT date_trunc('minute', timestamp) as minute, max(usage_user) AS max_usage_user FROM cpu " + - "WHERE hostname IN ('host_9') AND timestamp >= '1970-01-01T00:05:58Z' AND timestamp < '1970-01-01T00:05:59Z' GROUP BY minute ORDER BY minute" + expectedQuery := "SELECT max(usage_user) AS max_usage_user FROM cpu " + + "WHERE hostname IN ('host_9') AND timestamp >= '1970-01-01T00:05:58Z' AND timestamp < '1970-01-01T00:05:59Z' SAMPLE BY 1m" rand.Seed(123) // Setting seed for testing purposes. s := time.Unix(0, 0) @@ -40,8 +40,8 @@ func TestDevopsGroupByTime(t *testing.T) { func TestDevopsGroupByOrderByLimit(t *testing.T) { expectedHumanLabel := "QuestDB max cpu over last 5 min-intervals (random end)" expectedHumanDesc := "QuestDB max cpu over last 5 min-intervals (random end): 1970-01-01T01:16:22Z" - expectedQuery := "SELECT date_trunc('minute', timestamp) AS minute, max(usage_user) FROM cpu " + - "WHERE timestamp < '1970-01-01T01:16:22Z' GROUP BY minute ORDER BY minute DESC LIMIT 5" + expectedQuery := "SELECT max(usage_user) FROM cpu " + + "WHERE timestamp < '1970-01-01T01:16:22Z' SAMPLE BY 1m ORDER BY timestamp DESC LIMIT 5" rand.Seed(123) // Setting seed for testing purposes. s := time.Unix(0, 0) @@ -72,18 +72,18 @@ func TestDevopsGroupByTimeAndPrimaryTag(t *testing.T) { input: 1, expectedHumanLabel: "QuestDB mean of 1 metrics, all hosts, random 12h0m0s by 1h", expectedHumanDesc: "QuestDB mean of 1 metrics, all hosts, random 12h0m0s by 1h: 1970-01-01T00:16:22Z", - expectedQuery: "SELECT date_trunc('hour', timestamp) as timestamp, hostname, avg(usage_user) AS avg_usage_user FROM cpu " + + expectedQuery: "SELECT hostname, avg(usage_user) AS avg_usage_user FROM cpu " + "WHERE timestamp >= '1970-01-01T00:16:22Z' AND timestamp < '1970-01-01T12:16:22Z' " + - "GROUP BY timestamp, hostname ORDER BY timestamp, hostname", + "SAMPLE BY 1h", }, { desc: "5 metrics", input: 5, expectedHumanLabel: "QuestDB mean of 5 metrics, all hosts, random 12h0m0s by 1h", expectedHumanDesc: "QuestDB mean of 5 metrics, all hosts, random 12h0m0s by 1h: 1970-01-01T00:54:10Z", - expectedQuery: "SELECT date_trunc('hour', timestamp) as timestamp, hostname, avg(usage_user) AS avg_usage_user, avg(usage_system) AS avg_usage_system, avg(usage_idle) AS avg_usage_idle, avg(usage_nice) AS avg_usage_nice, avg(usage_iowait) AS avg_usage_iowait FROM cpu " + + expectedQuery: "SELECT hostname, avg(usage_user) AS avg_usage_user, avg(usage_system) AS avg_usage_system, avg(usage_idle) AS avg_usage_idle, avg(usage_nice) AS avg_usage_nice, avg(usage_iowait) AS avg_usage_iowait FROM cpu " + "WHERE timestamp >= '1970-01-01T00:54:10Z' AND timestamp < '1970-01-01T12:54:10Z' " + - "GROUP BY timestamp, hostname ORDER BY timestamp, hostname", + "SAMPLE BY 1h", }, } @@ -112,16 +112,16 @@ func TestMaxAllCPU(t *testing.T) { input: 1, expectedHumanLabel: "QuestDB max of all CPU metrics, random 1 hosts, random 8h0m0s by 1h", expectedHumanDesc: "QuestDB max of all CPU metrics, random 1 hosts, random 8h0m0s by 1h: 1970-01-01T00:54:10Z", - expectedQuery: "SELECT date_trunc('hour', timestamp) AS hour, max(usage_user) AS max_usage_user, max(usage_system) AS max_usage_system, max(usage_idle) AS max_usage_idle, max(usage_nice) AS max_usage_nice, max(usage_iowait) AS max_usage_iowait, max(usage_irq) AS max_usage_irq, max(usage_softirq) AS max_usage_softirq, max(usage_steal) AS max_usage_steal, max(usage_guest) AS max_usage_guest, max(usage_guest_nice) AS max_usage_guest_nice FROM cpu " + + expectedQuery: "SELECT max(usage_user) AS max_usage_user, max(usage_system) AS max_usage_system, max(usage_idle) AS max_usage_idle, max(usage_nice) AS max_usage_nice, max(usage_iowait) AS max_usage_iowait, max(usage_irq) AS max_usage_irq, max(usage_softirq) AS max_usage_softirq, max(usage_steal) AS max_usage_steal, max(usage_guest) AS max_usage_guest, max(usage_guest_nice) AS max_usage_guest_nice FROM cpu " + "WHERE hostname IN ('host_3') AND timestamp >= '1970-01-01T00:54:10Z' AND timestamp < '1970-01-01T08:54:10Z' " + - "GROUP BY hour ORDER BY hour", + "SAMPLE BY 1h", }, { desc: "5 hosts", input: 5, expectedHumanLabel: "QuestDB max of all CPU metrics, random 5 hosts, random 8h0m0s by 1h", expectedHumanDesc: "QuestDB max of all CPU metrics, random 5 hosts, random 8h0m0s by 1h: 1970-01-01T00:37:12Z", - expectedQuery: "SELECT date_trunc('hour', timestamp) AS hour, max(usage_user) AS max_usage_user, max(usage_system) AS max_usage_system, max(usage_idle) AS max_usage_idle, max(usage_nice) AS max_usage_nice, max(usage_iowait) AS max_usage_iowait, max(usage_irq) AS max_usage_irq, max(usage_softirq) AS max_usage_softirq, max(usage_steal) AS max_usage_steal, max(usage_guest) AS max_usage_guest, max(usage_guest_nice) AS max_usage_guest_nice FROM cpu WHERE hostname IN ('host_9', 'host_5', 'host_1', 'host_7', 'host_2') AND timestamp >= '1970-01-01T00:37:12Z' AND timestamp < '1970-01-01T08:37:12Z' GROUP BY hour ORDER BY hour", + expectedQuery: "SELECT max(usage_user) AS max_usage_user, max(usage_system) AS max_usage_system, max(usage_idle) AS max_usage_idle, max(usage_nice) AS max_usage_nice, max(usage_iowait) AS max_usage_iowait, max(usage_irq) AS max_usage_irq, max(usage_softirq) AS max_usage_softirq, max(usage_steal) AS max_usage_steal, max(usage_guest) AS max_usage_guest, max(usage_guest_nice) AS max_usage_guest_nice FROM cpu WHERE hostname IN ('host_9', 'host_5', 'host_1', 'host_7', 'host_2') AND timestamp >= '1970-01-01T00:37:12Z' AND timestamp < '1970-01-01T08:37:12Z' SAMPLE BY 1h", }, } diff --git a/cmd/tsbs_run_queries_questdb/main.go b/cmd/tsbs_run_queries_questdb/main.go index 904b9d312..584995f5c 100644 --- a/cmd/tsbs_run_queries_questdb/main.go +++ b/cmd/tsbs_run_queries_questdb/main.go @@ -1,14 +1,19 @@ -// tsbs_run_queries_influx speed tests InfluxDB using requests from stdin. +// tsbs_run_queries_questdb speed tests QuestDB using requests from stdin or file. // -// It reads encoded Query objects from stdin, and makes concurrent requests -// to the provided HTTP endpoint. This program has no knowledge of the -// internals of the endpoint. +// It reads encoded Query objects from stdin or file, and makes concurrent requests +// to the provided endpoint. Supports both HTTP/JSON and PostgreSQL wire protocol (pgx v5). package main import ( + "context" + "encoding/json" "fmt" + "net/url" + "strings" + "time" "github.com/blagojts/viper" + "github.com/jackc/pgx/v5" "github.com/questdb/tsbs/internal/utils" "github.com/questdb/tsbs/pkg/query" "github.com/spf13/pflag" @@ -19,6 +24,13 @@ var ( restURL string username string password string + // PostgreSQL mode options (default) + useHTTP bool + pgHost string + pgPort string + pgUser string + pgPass string + pgDBName string ) // Global vars: @@ -31,9 +43,18 @@ func init() { var config query.BenchmarkRunnerConfig config.AddToFlagSet(pflag.CommandLine) - pflag.String("url", "http://localhost:9000/", "Server URL. In case of HTTPS, the client will not validate the certificate, i.e. it trusts any server") - pflag.String("username", "", "Basic auth username") - pflag.String("password", "", "Basic auth password") + // PostgreSQL/pgx options (default mode) + pflag.String("pg-host", "localhost", "PostgreSQL host") + pflag.String("pg-port", "8812", "PostgreSQL port") + pflag.String("pg-user", "admin", "PostgreSQL user") + pflag.String("pg-pass", "quest", "PostgreSQL password") + pflag.String("pg-db", "qdb", "PostgreSQL database name") + + // HTTP options (legacy mode) + pflag.Bool("use-http", false, "Use HTTP REST API instead of PostgreSQL wire protocol") + pflag.String("url", "http://localhost:9000/", "Server URL for HTTP mode") + pflag.String("username", "", "Basic auth username (HTTP mode)") + pflag.String("password", "", "Basic auth password (HTTP mode)") pflag.Parse() @@ -47,6 +68,13 @@ func init() { panic(fmt.Errorf("unable to decode config: %s", err)) } + pgHost = viper.GetString("pg-host") + pgPort = viper.GetString("pg-port") + pgUser = viper.GetString("pg-user") + pgPass = viper.GetString("pg-pass") + pgDBName = viper.GetString("pg-db") + + useHTTP = viper.GetBool("use-http") restURL = viper.GetString("url") username = viper.GetString("username") password = viper.GetString("password") @@ -59,25 +87,49 @@ func main() { } type processor struct { - w *HTTPClient - opts *HTTPClientDoOptions + // HTTP mode + httpClient *HTTPClient + httpOpts *HTTPClientDoOptions + // pgx mode + conn *pgx.Conn + ctx context.Context } func newProcessor() query.Processor { return &processor{} } func (p *processor) Init(workerNumber int) { - p.opts = &HTTPClientDoOptions{ - Username: username, - Password: password, - Debug: runner.DebugLevel(), - PrettyPrintResponses: runner.DoPrintResponses(), + if useHTTP { + p.httpOpts = &HTTPClientDoOptions{ + Username: username, + Password: password, + Debug: runner.DebugLevel(), + PrettyPrintResponses: runner.DoPrintResponses(), + } + p.httpClient = NewHTTPClient(restURL) + } else { + connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + pgHost, pgPort, pgUser, pgPass, pgDBName) + p.ctx = context.Background() + conn, err := pgx.Connect(p.ctx, connStr) + if err != nil { + panic(fmt.Sprintf("Unable to connect to QuestDB via pgx: %v", err)) + } + p.conn = conn } - p.w = NewHTTPClient(restURL) } func (p *processor) ProcessQuery(q query.Query, _ bool) ([]*query.Stat, error) { hq := q.(*query.HTTP) - lag, err := p.w.Do(hq, p.opts) + + var lag float64 + var err error + + if useHTTP { + lag, err = p.httpClient.Do(hq, p.httpOpts) + } else { + lag, err = p.processQueryPgx(hq) + } + if err != nil { return nil, err } @@ -85,3 +137,122 @@ func (p *processor) ProcessQuery(q query.Query, _ bool) ([]*query.Stat, error) { stat.Init(q.HumanLabelName(), lag) return []*query.Stat{stat}, nil } + +func (p *processor) Close() { + if p.conn != nil { + p.conn.Close(p.ctx) + } +} + +// processQueryPgx extracts SQL from HTTP query and runs it via native pgx v5 +// Supports parameterized queries with bind variables for better performance +func (p *processor) processQueryPgx(hq *query.HTTP) (float64, error) { + // Check if query has parameters in Body (new parameterized format) + if len(hq.Body) > 0 && len(hq.RawQuery) > 0 { + return p.processQueryPgxWithParams(hq) + } + + // Fall back to legacy non-parameterized path extraction + pathStr := string(hq.Path) + + // Parse URL to extract query parameter + // The path looks like: /exec?count=false&query=SELECT... + if idx := strings.Index(pathStr, "?"); idx != -1 { + queryStr := pathStr[idx+1:] + values, err := url.ParseQuery(queryStr) + if err != nil { + return 0, fmt.Errorf("failed to parse query params: %v", err) + } + + sqlQuery := values.Get("query") + if sqlQuery == "" { + return 0, fmt.Errorf("no SQL query found in path: %s", pathStr) + } + + start := time.Now() + + // Use native pgx Query (not database/sql) for better performance + rows, err := p.conn.Query(p.ctx, sqlQuery) + if err != nil { + return 0, fmt.Errorf("query failed: %v", err) + } + + // Fetch all rows - same approach as TimescaleDB benchmark + for rows.Next() { + } + rows.Close() + + if err := rows.Err(); err != nil { + return 0, fmt.Errorf("row iteration error: %v", err) + } + + lag := float64(time.Since(start).Nanoseconds()) / 1e6 // milliseconds + return lag, nil + } + + return 0, fmt.Errorf("invalid path format: %s", pathStr) +} + +// processQueryPgxWithParams executes parameterized query with bind variables +// Arrays are inlined since QuestDB doesn't support array bind params for IN clause +func (p *processor) processQueryPgxWithParams(hq *query.HTTP) (float64, error) { + sqlTemplate := string(hq.RawQuery) + + // Parse parameters from JSON in Body + var rawParams []interface{} + if err := json.Unmarshal(hq.Body, &rawParams); err != nil { + return 0, fmt.Errorf("failed to parse query params JSON: %v", err) + } + + // First pass: inline arrays and track which original indices are kept + var params []interface{} + inlinedIndices := make(map[int]bool) + for i, raw := range rawParams { + switch v := raw.(type) { + case []interface{}: + // Inline array values in SQL (QuestDB doesn't support array bind params) + placeholder := fmt.Sprintf("$%d", i+1) + var quoted []string + for _, item := range v { + quoted = append(quoted, fmt.Sprintf("'%v'", item)) + } + inlineList := "(" + strings.Join(quoted, ",") + ")" + sqlTemplate = strings.Replace(sqlTemplate, placeholder, inlineList, 1) + inlinedIndices[i+1] = true + default: + params = append(params, v) + } + } + + // Second pass: renumber remaining placeholders + // Build mapping from old index to new index + newIdx := 1 + for origIdx := 1; origIdx <= len(rawParams); origIdx++ { + if !inlinedIndices[origIdx] { + if origIdx != newIdx { + sqlTemplate = strings.ReplaceAll(sqlTemplate, fmt.Sprintf("$%d", origIdx), fmt.Sprintf("$%d", newIdx)) + } + newIdx++ + } + } + + start := time.Now() + + // Use Query with prepared statement-style execution + rows, err := p.conn.Query(p.ctx, sqlTemplate, params...) + if err != nil { + return 0, fmt.Errorf("parameterized query failed: %v (sql: %s)", err, sqlTemplate) + } + + // Fetch all rows + for rows.Next() { + } + rows.Close() + + if err := rows.Err(); err != nil { + return 0, fmt.Errorf("row iteration error: %v", err) + } + + lag := float64(time.Since(start).Nanoseconds()) / 1e6 // milliseconds + return lag, nil +} diff --git a/go.mod b/go.mod index 73486a3bd..278f24280 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,82 @@ module github.com/questdb/tsbs -go 1.14 +go 1.23.0 require ( github.com/HdrHistogram/hdrhistogram-go v1.0.0 github.com/SiriDB/go-siridb-connector v0.0.0-20190110105621-86b34c44c921 - github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 github.com/aws/aws-sdk-go v1.35.13 github.com/blagojts/viper v1.6.3-0.20200313094124-068f44cf5e69 github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 - github.com/go-ole/go-ole v1.2.4 // indirect github.com/gocql/gocql v0.0.0-20190810123941-df4b9cc33030 github.com/golang/protobuf v1.4.2 github.com/golang/snappy v0.0.1 github.com/google/flatbuffers v1.11.0 - github.com/google/go-cmp v0.5.2 + github.com/google/go-cmp v0.6.0 github.com/jackc/pgx/v4 v4.8.0 + github.com/jackc/pgx/v5 v5.7.6 github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5 github.com/kshvakov/clickhouse v1.3.11 github.com/lib/pq v1.3.0 github.com/pkg/errors v0.9.1 - github.com/pkg/profile v1.2.1 github.com/prometheus/common v0.13.0 github.com/shirou/gopsutil v3.21.3+incompatible github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/timescale/promscale v0.0.0-20201006153045-6a66a36f5c84 - github.com/tklauser/go-sysconf v0.3.5 // indirect github.com/transceptor-technology/go-qpack v0.0.0-20190116123619-49a14b216a45 github.com/valyala/fasthttp v1.15.1 go.uber.org/atomic v1.6.0 - golang.org/x/net v0.0.0-20200904194848-62affa334b73 + golang.org/x/net v0.25.0 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e gopkg.in/yaml.v2 v2.3.0 ) + +require ( + github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect + github.com/andybalholm/brotli v1.0.0 // indirect + github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/go-ole/go-ole v1.2.4 // indirect + github.com/gogo/protobuf v1.3.1 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.14.8 // indirect + github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.6.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.0.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgtype v1.4.2 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/klauspost/compress v1.10.10 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mitchellh/mapstructure v1.2.2 // indirect + github.com/pelletier/go-toml v1.4.0 // indirect + github.com/rogpeppe/go-internal v1.6.1 // indirect + github.com/sergi/go-diff v1.0.0 // indirect + github.com/sirupsen/logrus v1.6.0 // indirect + github.com/spf13/afero v1.2.2 // indirect + github.com/spf13/cast v1.3.0 // indirect + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + github.com/tklauser/go-sysconf v0.3.5 // indirect + github.com/tklauser/numcpus v0.2.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect + google.golang.org/grpc v1.32.0 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.51.0 // indirect +) diff --git a/go.sum b/go.sum index e942b7e6c..59abca334 100644 --- a/go.sum +++ b/go.sum @@ -401,8 +401,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -499,7 +500,6 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -519,7 +519,6 @@ github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye47 github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= @@ -529,8 +528,9 @@ github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX github.com/jackc/pgproto3/v2 v2.0.2 h1:q1Hsy66zh4vuNsajBUF2PNqfAMMfxU5mk594lPE9vjY= github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= @@ -547,10 +547,15 @@ github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6 github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.8.0 h1:xO3bPvwr0MJSoDfb4yeeWZIxSZ2VFBm5axPnaNEnGUQ= github.com/jackc/pgx/v4 v4.8.0/go.mod h1:AjqYcDmEyst6GF8jJi/RF73Gla9d7/HLZzJEZj2uwpM= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.1 h1:PJAw7H/9hoWC4Kf3J8iNmL1SwA6E8vfsLqBiL+F6CtI= github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= @@ -600,8 +605,9 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= @@ -697,7 +703,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -755,7 +760,6 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -807,6 +811,8 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= @@ -829,11 +835,8 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= -github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= -github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/gopsutil v3.21.3+incompatible h1:uenXGGa8ESCQq+dbgtl916dmg6PSAz2cXov0uORQ9v8= github.com/shirou/gopsutil v3.21.3+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= @@ -889,8 +892,10 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= @@ -988,8 +993,9 @@ golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1024,7 +1030,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1065,8 +1070,9 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1081,6 +1087,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1138,17 +1146,18 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96 h1:gJciq3lOg0eS9fSZJcoHfv7q1BfC6cJfnmSSKL1yu3Q= golang.org/x/sys v0.0.0-20200908134130-d2e65c121b96/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa h1:ZYxPR6aca/uhfRJyaOAtflSHjJYiktO7QnJC5ut7iY4= golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1230,8 +1239,9 @@ golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200822203824-307de81be3f4/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112 h1:DmrRJy1qn9VDMf4+GSpRlwfZ51muIF7r96MFBFP4bPM= golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1324,8 +1334,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -1350,8 +1361,9 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v0.0.0-20181223230014-1083505acf35/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=