Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions cmd/query/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package query

import (
"fmt"
"strconv"

"github.com/duneanalytics/cli/cmdutil"
"github.com/duneanalytics/cli/output"
Expand All @@ -23,9 +22,9 @@ func newArchiveCmd() *cobra.Command {
}

func runArchive(cmd *cobra.Command, args []string) error {
queryID, err := strconv.Atoi(args[0])
queryID, err := parseQueryID(args[0])
if err != nil {
return fmt.Errorf("invalid query ID %q: must be an integer", args[0])
return err
}

client := cmdutil.ClientFromCmd(cmd)
Expand Down
5 changes: 2 additions & 3 deletions cmd/query/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package query

import (
"fmt"
"strconv"
"strings"

"github.com/duneanalytics/cli/cmdutil"
Expand All @@ -24,9 +23,9 @@ func newGetCmd() *cobra.Command {
}

func runGet(cmd *cobra.Command, args []string) error {
queryID, err := strconv.Atoi(args[0])
queryID, err := parseQueryID(args[0])
if err != nil {
return fmt.Errorf("invalid query ID %q: must be an integer", args[0])
return err
}

client := cmdutil.ClientFromCmd(cmd)
Expand Down
14 changes: 14 additions & 0 deletions cmd/query/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package query

import (
"fmt"
"strconv"
)

func parseQueryID(arg string) (int, error) {
id, err := strconv.Atoi(arg)
if err != nil {
return 0, fmt.Errorf("invalid query ID %q: must be an integer", arg)
}
return id, nil
}
1 change: 1 addition & 0 deletions cmd/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ func NewQueryCmd() *cobra.Command {
cmd.AddCommand(newGetCmd())
cmd.AddCommand(newUpdateCmd())
cmd.AddCommand(newArchiveCmd())
cmd.AddCommand(newRunCmd())
return cmd
}
160 changes: 160 additions & 0 deletions cmd/query/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package query

import (
"fmt"
"strings"
"time"

"github.com/duneanalytics/cli/cmdutil"
"github.com/duneanalytics/cli/output"
"github.com/duneanalytics/duneapi-client-go/models"
"github.com/spf13/cobra"
)

func newRunCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "run <query-id>",
Short: "Execute a saved query and display results",
Args: cobra.ExactArgs(1),
RunE: runRun,
}

cmd.Flags().StringArray("param", nil, "query parameter in key=value format (repeatable)")
cmd.Flags().String("performance", "medium", `performance tier: "medium" or "large"`)
cmd.Flags().Int("limit", 0, "maximum number of rows to display (0 = all)")
cmd.Flags().Bool("no-wait", false, "submit execution and exit without waiting for results")
output.AddFormatFlag(cmd, "text")

return cmd
}

func runRun(cmd *cobra.Command, args []string) error {
queryID, err := parseQueryID(args[0])
if err != nil {
return err
}

paramFlags, _ := cmd.Flags().GetStringArray("param")
params, err := parseParams(paramFlags)
if err != nil {
return err
}

performance, _ := cmd.Flags().GetString("performance")
if performance != "medium" && performance != "large" {
return fmt.Errorf("invalid performance tier %q: must be \"medium\" or \"large\"", performance)
}

req := models.ExecuteRequest{
QueryID: queryID,
Performance: performance,
}
if len(params) > 0 {
req.QueryParameters = params
}

noWait, _ := cmd.Flags().GetBool("no-wait")
if noWait {
return runNoWait(cmd, req)
}
return runWait(cmd, req)
}

func runNoWait(cmd *cobra.Command, req models.ExecuteRequest) error {
client := cmdutil.ClientFromCmd(cmd)

resp, err := client.QueryExecute(req)
if err != nil {
return err
}

w := cmd.OutOrStdout()
switch output.FormatFromCmd(cmd) {
case output.FormatJSON:
return output.PrintJSON(w, resp)
default:
fmt.Fprintf(w, "Execution ID: %s\n", resp.ExecutionID)
fmt.Fprintf(w, "State: %s\n", resp.State)
return nil
}
}

func runWait(cmd *cobra.Command, req models.ExecuteRequest) error {
client := cmdutil.ClientFromCmd(cmd)

exec, err := client.RunQuery(req)
if err != nil {
return err
}

resp, err := exec.WaitGetResults(5*time.Second, 60)
if err != nil {
return err
}

if resp.State != "QUERY_STATE_COMPLETED" {
msg := fmt.Sprintf("query execution failed with state %s", resp.State)
if resp.Error != nil {
msg += fmt.Sprintf(": %s", resp.Error.Message)
}
return fmt.Errorf("%s", msg)
}

return displayResults(cmd, resp)
}

func parseParams(raw []string) (map[string]any, error) {
if len(raw) == 0 {
return nil, nil
}
params := make(map[string]any, len(raw))
for _, s := range raw {
key, value, ok := strings.Cut(s, "=")
if !ok {
return nil, fmt.Errorf("invalid parameter %q: expected key=value format", s)
}
if key == "" {
return nil, fmt.Errorf("invalid parameter %q: key cannot be empty", s)
}
params[key] = value
}
return params, nil
}

func displayResults(cmd *cobra.Command, resp *models.ResultsResponse) error {
w := cmd.OutOrStdout()

if output.FormatFromCmd(cmd) == output.FormatJSON {
return output.PrintJSON(w, resp)
}

limit, _ := cmd.Flags().GetInt("limit")
columns := resp.Result.Metadata.ColumnNames
sourceRows := resp.Result.Rows
totalRows := len(sourceRows)

if limit > 0 && limit < totalRows {
sourceRows = sourceRows[:limit]
}
rows := resultRowsToStrings(sourceRows, columns)

output.PrintTable(w, columns, rows)
if limit > 0 && limit < totalRows {
fmt.Fprintf(w, "\nShowing %d of %d rows\n", limit, totalRows)
} else {
fmt.Fprintf(w, "\n%d rows\n", totalRows)
}
return nil
}

func resultRowsToStrings(rows []map[string]any, columns []string) [][]string {
out := make([][]string, len(rows))
for i, row := range rows {
cells := make([]string, len(columns))
for j, col := range columns {
cells[j] = fmt.Sprintf("%v", row[col])
}
out[i] = cells
}
return out
}
Loading