commit 6b65bccfd07d30b22a6c8ca105cfb5445021f4e5
parent 50acd5dd300ab27ade59be50b463ce63fe3c525c
Author: Erik Loualiche <[email protected]>
Date: Fri, 20 Feb 2026 09:20:26 -0600
Add SELECT columns field to download form and --columns CLI flag
Allows users to specify which columns to download instead of always
using SELECT *. The TUI form shows column names from loaded metadata
as placeholder hints.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
Diffstat:
3 files changed, 62 insertions(+), 27 deletions(-)
diff --git a/cmd/download.go b/cmd/download.go
@@ -11,13 +11,14 @@ import (
)
var (
- dlSchema string
- dlTable string
- dlWhere string
- dlQuery string
- dlOut string
- dlFormat string
- dlLimit int
+ dlSchema string
+ dlTable string
+ dlColumns string
+ dlWhere string
+ dlQuery string
+ dlOut string
+ dlFormat string
+ dlLimit int
)
var downloadCmd = &cobra.Command{
@@ -27,6 +28,7 @@ var downloadCmd = &cobra.Command{
Examples:
wrds download --schema crsp --table dsf --where "date='2020-01-02'" --out crsp_dsf.parquet
+ wrds download --schema comp --table funda --columns "gvkey,datadate,sale" --out funda.parquet
wrds download --query "SELECT permno, date, prc FROM crsp.dsf LIMIT 1000" --out out.parquet
wrds download --schema comp --table funda --out funda.csv --format csv`,
RunE: runDownload,
@@ -38,6 +40,7 @@ func init() {
f := downloadCmd.Flags()
f.StringVar(&dlSchema, "schema", "", "Schema name (e.g. crsp)")
f.StringVar(&dlTable, "table", "", "Table name (e.g. dsf)")
+ f.StringVarP(&dlColumns, "columns", "c", "*", "Columns to select (comma-separated, default *)")
f.StringVar(&dlWhere, "where", "", "SQL WHERE clause (without the WHERE keyword)")
f.StringVar(&dlQuery, "query", "", "Full SQL query (overrides --schema/--table/--where)")
f.StringVar(&dlOut, "out", "", "Output file path (required)")
@@ -76,7 +79,11 @@ func buildQuery() (string, error) {
return "", fmt.Errorf("either --query or both --schema and --table must be specified")
}
- q := fmt.Sprintf("SELECT * FROM wrds.%s.%s", dlSchema, dlTable)
+ sel := "*"
+ if dlColumns != "" && dlColumns != "*" {
+ sel = dlColumns
+ }
+ q := fmt.Sprintf("SELECT %s FROM wrds.%s.%s", sel, dlSchema, dlTable)
if dlWhere != "" {
q += " WHERE " + dlWhere
diff --git a/internal/tui/app.go b/internal/tui/app.go
@@ -229,11 +229,13 @@ func (a *App) loadMeta(schema, tbl string) tea.Cmd {
func (a *App) startDownload(msg DlSubmitMsg) tea.Cmd {
return func() tea.Msg {
- var query string
+ sel := "*"
+ if msg.Columns != "" && msg.Columns != "*" {
+ sel = msg.Columns
+ }
+ query := fmt.Sprintf("SELECT %s FROM wrds.%s.%s", sel, msg.Schema, msg.Table)
if msg.Where != "" {
- query = fmt.Sprintf("SELECT * FROM wrds.%s.%s WHERE %s", msg.Schema, msg.Table, msg.Where)
- } else {
- query = fmt.Sprintf("SELECT * FROM wrds.%s.%s", msg.Schema, msg.Table)
+ query += " WHERE " + msg.Where
}
err := export.Export(query, msg.Out, export.Options{Format: msg.Format})
if err != nil {
@@ -529,7 +531,13 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
break
}
if a.selectedSchema != "" && a.selectedTable != "" {
- a.dlForm = newDlForm(a.selectedSchema, a.selectedTable)
+ var colNames []string
+ if a.previewMeta != nil {
+ for _, c := range a.previewMeta.Columns {
+ colNames = append(colNames, c.Name)
+ }
+ }
+ a.dlForm = newDlForm(a.selectedSchema, a.selectedTable, colNames)
a.state = stateDownloadForm
return a, nil
}
diff --git a/internal/tui/dlform.go b/internal/tui/dlform.go
@@ -12,7 +12,8 @@ import (
type dlFormField int
const (
- fieldWhere dlFormField = iota
+ fieldSelect dlFormField = iota
+ fieldWhere
fieldOut
fieldFormat
fieldCount
@@ -29,19 +30,33 @@ type DlForm struct {
// DlSubmitMsg is sent when the user confirms the download form.
type DlSubmitMsg struct {
- Schema string
- Table string
- Where string
- Out string
- Format string
+ Schema string
+ Table string
+ Columns string
+ Where string
+ Out string
+ Format string
}
// DlCancelMsg is sent when the user cancels.
type DlCancelMsg struct{}
-func newDlForm(schema, table string) DlForm {
+func newDlForm(schema, table string, colNames []string) DlForm {
f := DlForm{schema: schema, table: table}
+ f.inputs[fieldSelect] = textinput.New()
+ placeholder := "e.g. gvkey, datadate, sale"
+ if len(colNames) > 0 {
+ hint := strings.Join(colNames, ", ")
+ if len(hint) > 60 {
+ hint = hint[:57] + "..."
+ }
+ placeholder = "e.g. " + hint
+ }
+ f.inputs[fieldSelect].Placeholder = placeholder
+ f.inputs[fieldSelect].CharLimit = 1024
+ f.inputs[fieldSelect].SetValue("*")
+
f.inputs[fieldWhere] = textinput.New()
f.inputs[fieldWhere].Placeholder = "e.g. date >= '2020-01-01'"
f.inputs[fieldWhere].CharLimit = 512
@@ -56,7 +71,7 @@ func newDlForm(schema, table string) DlForm {
f.inputs[fieldFormat].CharLimit = 10
f.inputs[fieldFormat].SetValue("parquet")
- f.inputs[fieldWhere].Focus()
+ f.inputs[fieldSelect].Focus()
return f
}
@@ -82,13 +97,18 @@ func (f DlForm) Update(msg tea.Msg) (DlForm, tea.Cmd) {
if format == "" {
format = "parquet"
}
+ columns := strings.TrimSpace(f.inputs[fieldSelect].Value())
+ if columns == "" {
+ columns = "*"
+ }
return f, func() tea.Msg {
return DlSubmitMsg{
- Schema: f.schema,
- Table: f.table,
- Where: f.inputs[fieldWhere].Value(),
- Out: out,
- Format: format,
+ Schema: f.schema,
+ Table: f.table,
+ Columns: columns,
+ Where: f.inputs[fieldWhere].Value(),
+ Out: out,
+ Format: format,
}
}
case "tab", "down":
@@ -115,7 +135,7 @@ func (f DlForm) View(width int) string {
title := stylePanelHeader.Render(fmt.Sprintf("Download %s.%s", f.schema, f.table))
sb.WriteString(title + "\n\n")
- labels := []string{"WHERE clause", "Output path", "Format (parquet/csv)"}
+ labels := []string{"SELECT columns", "WHERE clause", "Output path", "Format (parquet/csv)"}
for i, label := range labels {
style := lipgloss.NewStyle().Foreground(colorMuted)
if dlFormField(i) == f.focused {