From 8582882da9bc8f5ad21035a8f7d480c42a1e9f1e Mon Sep 17 00:00:00 2001 From: Steven McCanne Date: Mon, 28 Sep 2026 06:55:40 -0700 Subject: [PATCH 1/2] add -- args passing from shell as const args in query Closes #2223 --- book/src/command/super.md | 13 +++++- cmd/super/root/command.go | 2 +- compiler/ast/value.go | 9 ++--- compiler/parser/api.go | 50 ++++++++++++++++++++--- compiler/parser/ztests/command-args.yaml | 51 ++++++++++++++++++++++++ pkg/charm/instance.go | 29 +++++++++++++- 6 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 compiler/parser/ztests/command-args.yaml diff --git a/book/src/command/super.md b/book/src/command/super.md index 69af8eaf93..65ca41f76f 100644 --- a/book/src/command/super.md +++ b/book/src/command/super.md @@ -17,7 +17,7 @@ Instead, your shell is your REPL and the `super` command lets you: The `super` command is invoked either by itself to run a query: ``` -super [ -c | -I ] [ options ] [ ... ] +super [ -c | -I ] [ options ] [ ... ] [ -- [ ... ]] ``` or with a [sub-command](sub-commands.md): ``` @@ -32,6 +32,17 @@ SuperDB query engine detached from the database storage layer. The [input data](input.md) may be specified as command-line paths or referenced within the query. +One or more `` arguments act as if a +[from](../super-sql/operators/from.md) operator has been prepended to +the query specified. When no query is specified, +the inputs are scanned with no query applied. + +Arguments may be passed from the command-line into the query using +`--` to terminate the input path specifications and indicate subsequent query arguments. +All of the shell arguments after `--` are assembled into a string array +called `args` as a +[constant](../super-sql/declarations/constants.md) +appearing in the main scope of the query. For built-in command help and a listing of all available options, simply run `super` without any arguments. diff --git a/cmd/super/root/command.go b/cmd/super/root/command.go index 6ee9dc5e53..33c87f1746 100644 --- a/cmd/super/root/command.go +++ b/cmd/super/root/command.go @@ -84,7 +84,7 @@ func (c *Command) Run(args []string) error { return nil } if len(args) > 0 { - ast.PrependFileScan(args) + args = ast.PrependFileScan(args) } env := exec.NewEnvironment(storage.NewLocalEngine(), nil) env.Dynamic = c.inputFlags.Dynamic diff --git a/compiler/ast/value.go b/compiler/ast/value.go index 9fdb1e58b4..6ca8b2208a 100644 --- a/compiler/ast/value.go +++ b/compiler/ast/value.go @@ -24,11 +24,10 @@ func (*Decorated) valueNode() {} type ( Primitive struct { - Kind string `json:"kind" unpack:""` - Type string `json:"type"` - Text string `json:"text"` - TextPos int `json:"text_pos"` - Loc `json:"loc"` + Kind string `json:"kind" unpack:""` + Type string `json:"type"` + Text string `json:"text"` + Loc `json:"loc"` } Record struct { Kind string `json:"kind" unpack:""` diff --git a/compiler/parser/api.go b/compiler/parser/api.go index a1b13bcdc0..2a2f117f1b 100644 --- a/compiler/parser/api.go +++ b/compiler/parser/api.go @@ -2,6 +2,7 @@ package parser import ( "errors" + "slices" "github.com/brimdata/super/compiler/ast" "github.com/brimdata/super/compiler/srcfiles" @@ -36,11 +37,50 @@ func (a *AST) ConvertToDeleteWhere(pool, branch string) error { return nil } -func (a *AST) PrependFileScan(paths []string) { - a.seq.Prepend(&ast.FileScan{ - Kind: "FileScan", - Paths: paths, - }) +func (a *AST) PrependFileScan(paths []string) []string { + var args []string + if k := slices.Index(paths, "--"); k >= 0 { + args = paths[k+1:] + paths = paths[:k] + } + if len(paths) > 0 { + a.seq.Prepend(&ast.FileScan{ + Kind: "FileScan", + Paths: paths, + }) + } + if args != nil { + a.seq = []ast.Op{ + &ast.ScopeOp{ + Kind: "ScopeOp", + Decls: []ast.Decl{ + &ast.ConstDecl{ + Kind: "ConstDecl", + Name: &ast.ID{Name: "args"}, + Expr: stringArray(args), + }, + }, + Body: a.seq, + }, + } + } + return paths +} + +func stringArray(in []string) ast.Expr { + var elems []ast.ArrayElem + for _, s := range in { + e := &ast.Primitive{ + Kind: "Primitive", + Type: "string", + Text: s, + } + elems = append(elems, &ast.ExprElem{Kind: "ExprElem", Expr: e}) + } + return &ast.ArrayExpr{ + Kind: "ArrayExpr", + Elems: elems, + } } // ParseText parses a query text in string form. diff --git a/compiler/parser/ztests/command-args.yaml b/compiler/parser/ztests/command-args.yaml new file mode 100644 index 0000000000..fb38ac2b7c --- /dev/null +++ b/compiler/parser/ztests/command-args.yaml @@ -0,0 +1,51 @@ +script: | + super -s -c "values args" -- a b c + +outputs: + - name: stdout + data: | + ["a","b","c"] + +--- + +script: | + super -s -c "values args" -- + +outputs: + - name: stdout + data: | + [] + +--- + +script: | + super -s -c "values args" in.sup -- + +inputs: + - name: in.sup + data: | + null + null + +outputs: + - name: stdout + data: | + [] + [] + +--- + +script: | + super -s -c "values args" - -- + +inputs: + - name: stdin + data: | + null + null + +outputs: + - name: stdout + data: | + [] + [] diff --git a/pkg/charm/instance.go b/pkg/charm/instance.go index d153ea6abf..7b39dcf063 100644 --- a/pkg/charm/instance.go +++ b/pkg/charm/instance.go @@ -77,7 +77,7 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ } path = append(path, component) parent = cmd - if err := flags.Parse(args); err != nil { + if err := flags.Parse(escape(args)); err != nil { if usage { s := strings.Join(args, " ") err = fmt.Errorf("at flag: %q: %w", s, err) @@ -87,7 +87,7 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ if help { return path, nil, hidden, NeedHelp } - rest := flags.Args() + rest := unescape(flags.Args()) if len(rest) != 0 { spec = component.spec.lookupSub(rest[0]) if spec != nil { @@ -103,6 +103,31 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ } } +// escape/unescape prevents Go flags from stopping at "--" and instead treats +// "--" as termination of flag parsing and retains it as remaining args. +// This lets us implement args options passed to the query with "... -- arg arg" +func escape(args []string) []string { + var out []string + for _, arg := range args { + if arg == "--" { + arg = "\x00--" + } + out = append(out, arg) + } + return out +} + +func unescape(args []string) []string { + var out []string + for _, arg := range args { + if arg == "\x00--" { + arg = "--" + } + out = append(out, arg) + } + return out +} + func diff(flags *flag.FlagSet, all map[string]*flag.Flag) map[string]*flag.Flag { difference := make(map[string]*flag.Flag) flags.VisitAll(func(f *flag.Flag) { From 8caebc3df151092e2fbc811d5d11a38caf8b98ca Mon Sep 17 00:00:00 2001 From: Steven McCanne Date: Mon, 28 Sep 2026 14:19:21 -0700 Subject: [PATCH 2/2] address PR feedback --- .../super}/ztests/command-args.yaml | 0 pkg/charm/instance.go | 41 ++++++------------- 2 files changed, 13 insertions(+), 28 deletions(-) rename {compiler/parser => cmd/super}/ztests/command-args.yaml (100%) diff --git a/compiler/parser/ztests/command-args.yaml b/cmd/super/ztests/command-args.yaml similarity index 100% rename from compiler/parser/ztests/command-args.yaml rename to cmd/super/ztests/command-args.yaml diff --git a/pkg/charm/instance.go b/pkg/charm/instance.go index 7b39dcf063..76abf39eeb 100644 --- a/pkg/charm/instance.go +++ b/pkg/charm/instance.go @@ -3,6 +3,7 @@ package charm import ( "flag" "fmt" + "slices" "sort" "strings" ) @@ -77,9 +78,18 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ } path = append(path, component) parent = cmd - if err := flags.Parse(escape(args)); err != nil { + // These gymnastics prevent Go flags from stopping at and consuming the "--" + // and retains it as remaining args, which lets us implement args options + // passed to the query with "... -- arg arg" + savedArgs := args + var dashDashArgs []string + if k := slices.Index(args, "--"); k >= 0 { + dashDashArgs = args[k:] + args = args[:k] + } + if err := flags.Parse(args); err != nil { if usage { - s := strings.Join(args, " ") + s := strings.Join(savedArgs, " ") err = fmt.Errorf("at flag: %q: %w", s, err) } return path, nil, false, err @@ -87,7 +97,7 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ if help { return path, nil, hidden, NeedHelp } - rest := unescape(flags.Args()) + rest := append(flags.Args(), dashDashArgs...) if len(rest) != 0 { spec = component.spec.lookupSub(rest[0]) if spec != nil { @@ -103,31 +113,6 @@ func parse(spec *Spec, args []string, parent Command, interiorLeaf int) (path, [ } } -// escape/unescape prevents Go flags from stopping at "--" and instead treats -// "--" as termination of flag parsing and retains it as remaining args. -// This lets us implement args options passed to the query with "... -- arg arg" -func escape(args []string) []string { - var out []string - for _, arg := range args { - if arg == "--" { - arg = "\x00--" - } - out = append(out, arg) - } - return out -} - -func unescape(args []string) []string { - var out []string - for _, arg := range args { - if arg == "\x00--" { - arg = "--" - } - out = append(out, arg) - } - return out -} - func diff(flags *flag.FlagSet, all map[string]*flag.Flag) map[string]*flag.Flag { difference := make(map[string]*flag.Flag) flags.VisitAll(func(f *flag.Flag) {