From e16a7bc502f16b290fd1eeed515b0ccfec398081 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 23 Sep 2026 07:06:07 +0900 Subject: [PATCH] Preserve commas within complete SQL parameter assignments --- README.md | 9 ++++++--- main.go | 2 +- param_cli_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 param_cli_test.go diff --git a/README.md b/README.md index 1a3d95e..dcdcee9 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,11 @@ Flags: --jq-input-mode="eager" How query rows are passed to jq (json/yaml only): eager (full ResultSet), lazy (JQValue root). - --param=PARAM,... [name]=[type or literal]; legacy [name]:[...] - also accepted + --param=PARAM One [name]=[type or literal] assignment. + Repeat the flag for more parameters. Legacy + [name]:[...] is accepted. Commas stay inside + the value; one flag is not split into several + assignments. --param-file=STRING YAML or JSON file of query parameters (name to type/literal string) --log-grpc gRPC logging: --log-grpc means payload; @@ -256,7 +259,7 @@ $ execspansql ${DATABASE_ID} --query-mode=PROFILE --discard-results \ Many Cloud Spanner clients don't support parameter. Without modifications, query which have parameters are impossible to execute and query whose parameters' types are `STRUCT` or `ARRAY` are impossible to show query plans. -execspansql supports query parameters via repeated `--param name=value` flags (legacy `name:value` is also accepted) or a `--param-file` (YAML or JSON). When both are given, `--param` overrides entries from the file. +execspansql supports query parameters via repeated `--param name=value` flags (legacy `name:value` is also accepted) or a `--param-file` (YAML or JSON). Each `--param` is one assignment: a comma inside an ARRAY, STRUCT, or string literal is kept. The former shorthand `--param=n=1,m=2` is not split into two parameters; pass `--param=n=1 --param=m=2` instead. When both a file and flags are given, `--param` overrides entries from the file. #### PLAN with complex typed parameters diff --git a/main.go b/main.go index 4e848ca..04bc7ea 100644 --- a/main.go +++ b/main.go @@ -104,7 +104,7 @@ type opts struct { JqRawOutput bool `name:"raw-output" short:"r" help:"(--raw-output of jq)"` JqFromFile string `name:"filter-file" xor:"filter" help:"(--from-file of jq)"` JqInputMode string `name:"jq-input-mode" enum:"eager,lazy" default:"eager" help:"How query rows are passed to jq (json/yaml only): eager (full ResultSet), lazy (JQValue root)."` - ParamFlags []string `name:"param" help:"[name]=[type or literal]; legacy [name]:[...] also accepted"` + ParamFlags []string `name:"param" sep:"none" help:"One [name]=[type or literal] assignment. Repeat the flag for more parameters. Legacy [name]:[...] is accepted. Commas stay inside the value; one flag is not split into several assignments."` ParamFile string `name:"param-file" help:"YAML or JSON file of query parameters (name to type/literal string)"` LogGrpc logGrpcFlag `name:"log-grpc" enum:"off,metadata,payload" default:"off" help:"gRPC logging: --log-grpc means payload; use --log-grpc=off|metadata|payload to select a mode (payload may include request and response payloads)"` TraceProject string `name:"experimental-trace-project" xor:"trace" help:"Export traces to Cloud Trace in the given project."` diff --git a/param_cli_test.go b/param_cli_test.go new file mode 100644 index 0000000..f468a5e --- /dev/null +++ b/param_cli_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "strings" + "testing" +) + +func TestParamFlagKeepsCommasThroughCLI(t *testing.T) { + values := []string{ + `arr=[1, 2]`, + `s='a,b'`, + `x=STRUCT(1,'two')`, + } + args := []string{"db", "--project=p", "--instance=i", "--sql=SELECT @arr"} + for _, value := range values { + args = append(args, "--param="+value) + } + o, err := processFlags(args) + if err != nil { + t.Fatal(err) + } + if strings.Join(o.ParamFlags, "\n") != strings.Join(values, "\n") { + t.Fatalf("ParamFlags = %#v, want one entry per flag", o.ParamFlags) + } + if _, err := prepareCommand(o); err != nil { + t.Fatalf("prepareCommand: %v", err) + } +} + +func TestParamFlagDoesNotSplitCommaSeparatedAssignments(t *testing.T) { + o, err := processFlags([]string{ + "db", "--project=p", "--instance=i", "--sql=SELECT @n, @m", + "--param=n=1,m=2", + }) + if err != nil { + t.Fatal(err) + } + if len(o.ParamFlags) != 1 || o.ParamFlags[0] != "n=1,m=2" { + t.Fatalf("ParamFlags = %#v, want a single unsplit assignment", o.ParamFlags) + } +}