From ce4b3f3da222877430c2b1bc99bd15558e2ba66e Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 16 Sep 2026 03:40:26 +0900 Subject: [PATCH 1/3] refactor: map structured plan appendices from spannerplan Adopt github.com/apstndb/spannerplan@195dc13 (PR 65) via a temporary pseudo-version and replace the local appendix formatter with a small mapping to ResultAppendix. Propagate BuildAppendices errors through processPlan, buildQueryPlanAppendix, and buildExplainAnalyzeResult. Keep CLI section resolution, predicate markers, and query-mode output unchanged. Do not enable scalar-variable resolution. --- go.mod | 2 +- go.sum | 4 +- internal/mycli/explain_print_sections.go | 163 +++--------------- internal/mycli/explain_print_sections_test.go | 101 +++++------ internal/mycli/statements_explain_describe.go | 5 +- .../mycli/statements_explain_describe_test.go | 20 ++- 6 files changed, 83 insertions(+), 212 deletions(-) diff --git a/go.mod b/go.mod index b7eb6e03..531ac3c4 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/apstndb/spanemuboost v0.4.7 github.com/apstndb/spaniter v0.3.1 github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6 - github.com/apstndb/spannerplan v0.3.0 + github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e github.com/apstndb/spanstats v0.1.0 github.com/apstndb/spantype v0.3.13 github.com/apstndb/spanvalue v0.8.4 diff --git a/go.sum b/go.sum index 860a5e55..9bf07d6b 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/apstndb/spaniter v0.3.1 h1:hhi4+JCF80x696bg7zIco7Vo8kmPbkbTdvSOEhItp5 github.com/apstndb/spaniter v0.3.1/go.mod h1:aBSHcHIqgAZXCxFdi734R/wAQUIuCQ6WZ+CjOCxARIM= github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6 h1:JP8l0QJZRVSRwBXTxaMMAjrmF0kQDjXlCM0bwTCo5Gs= github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6/go.mod h1:Enpmw/D11tME865ROYDQzoHSIo9V8uCgWdu+fxA7n2M= -github.com/apstndb/spannerplan v0.3.0 h1:EKB9Iw5iCloWoJXIFUxh93zXIO6NqoAld9fnzzkL/UA= -github.com/apstndb/spannerplan v0.3.0/go.mod h1:zVUS187Z4DGaJekIBt2E1JYb9UMKQ7s5ZZo3HOr522E= +github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e h1:w7sigXasdgOUkp0XJGsS4gn7bIJy1wfExKSRgUjjc10= +github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e/go.mod h1:zVUS187Z4DGaJekIBt2E1JYb9UMKQ7s5ZZo3HOr522E= github.com/apstndb/spanstats v0.1.0 h1:wAPqtFn1AfQaIudB4HwTLiIPinwWJw/gQhgE7NQWVKo= github.com/apstndb/spanstats v0.1.0/go.mod h1:DFGYC9e7WE0BFQL0st6EQwrl1Pn2FuvDWSefwI9FTWc= github.com/apstndb/spantype v0.3.13 h1:FTP3zUpVXMfPlZ3P+1RK6SYHND+96YVht0I+ZHGIzMc= diff --git a/internal/mycli/explain_print_sections.go b/internal/mycli/explain_print_sections.go index 0aad83c8..750125bc 100644 --- a/internal/mycli/explain_print_sections.go +++ b/internal/mycli/explain_print_sections.go @@ -17,7 +17,6 @@ package mycli import ( "fmt" "slices" - "strings" sppb "cloud.google.com/go/spanner/apiv1/spannerpb" "github.com/apstndb/spannerplan/plantree" @@ -68,48 +67,25 @@ func resolveExplainPrintSections(sysVars *systemVariables, override *planref.Pri return append(planref.PrintSections{}, sysVars.Display.ParsedExplainPrintSections...) } -func buildPlanAppendices(rows []plantree.RowWithPredicates, sections planref.PrintSections) ([]string, []ResultAppendix) { +func buildPlanAppendices(rows []plantree.RowWithPredicates, sections planref.PrintSections) ([]string, []ResultAppendix, error) { + // Always pass WithPrintSections so an empty CLI/override list stays empty. + // Omitting the option would select the library default (predicates only). + // Do not enable scalar-variable resolution; keep the historical raw descriptions. + built, err := planref.BuildAppendices(rows, planref.WithPrintSections(sections...)) + if err != nil { + return nil, nil, err + } + var predicates []string - var appendices []ResultAppendix - for _, section := range sections { - var appendix ResultAppendix - switch section { - case planref.PrintPredicates: - predicates = appendixLines(rows, func(row plantree.RowWithPredicates) []string { - return row.Predicates - }) - appendix = ResultAppendix{Title: "Predicates(identified by ID):", Lines: predicates} - case planref.PrintOrdering: - appendix = ResultAppendix{ - Title: "Ordering(identified by ID):", - Lines: appendixLines(rows, func(row plantree.RowWithPredicates) []string { - return scalarLinkLines(row, isOrderingScalarLink, func(link plantree.ScalarChildLink) string { - return normalizeKeyOrderSuffix(link.Description) - }) - }), - } - case planref.PrintAggregate: - appendix = ResultAppendix{ - Title: "Aggregates(identified by ID):", - Lines: appendixLines(rows, func(row plantree.RowWithPredicates) []string { - return scalarLinkLines(row, isAggregateScalarLink, scalarLinkDescription) - }), - } - case planref.PrintTyped, planref.PrintFull: - appendix = ResultAppendix{ - Title: "Node Parameters(identified by ID):", - Lines: appendixLines(rows, func(row plantree.RowWithPredicates) []string { - return scalarLinkLines(row, func(_ plantree.RowWithPredicates, link plantree.ScalarChildLink) bool { - return section == planref.PrintFull || link.Type != "" - }, formatRawScalarLink) - }), - } - } - if len(appendix.Lines) > 0 { - appendices = append(appendices, appendix) + appendices := make([]ResultAppendix, 0, len(built)) + for _, appendix := range built { + mapped := ResultAppendix{Title: appendix.Title, Lines: appendix.Lines} + appendices = append(appendices, mapped) + if appendix.Section == planref.PrintPredicates { + predicates = appendix.Lines } } - return predicates, appendices + return predicates, appendices, nil } // buildQueryPlanAppendix renders a query plan as one or more titled result @@ -143,7 +119,10 @@ func buildQueryPlanAppendix(sysVars *systemVariables, plan *sppb.QueryPlan) ([]R } planAppendix := ResultAppendix{Title: "Query Plan(identified by ID):", Lines: lines} - _, sectionAppendices := buildPlanAppendices(rows, sections) + _, sectionAppendices, err := buildPlanAppendices(rows, sections) + if err != nil { + return nil, err + } return append([]ResultAppendix{planAppendix}, sectionAppendices...), nil } @@ -154,105 +133,3 @@ func formatPlanRowID(row plantree.RowWithPredicates, sections planref.PrintSecti } return fmt.Sprint(row.ID) } - -func appendixLines(rows []plantree.RowWithPredicates, items func(plantree.RowWithPredicates) []string) []string { - var maxIDLength int - for _, row := range rows { - if length := len(fmt.Sprint(row.ID)); length > maxIDLength { - maxIDLength = length - } - } - - var lines []string - for _, row := range rows { - for i, item := range items(row) { - var prefix string - if i == 0 { - prefix = fmt.Sprintf("%*d:", maxIDLength, row.ID) - } else { - prefix = strings.Repeat(" ", maxIDLength+1) - } - lines = append(lines, fmt.Sprintf("%s %s", prefix, item)) - } - } - return lines -} - -type scalarLinkGroup struct { - typ string - values []string -} - -func scalarLinkLines( - row plantree.RowWithPredicates, - include func(plantree.RowWithPredicates, plantree.ScalarChildLink) bool, - format func(plantree.ScalarChildLink) string, -) []string { - groupByType := map[string]int{} - var groups []scalarLinkGroup - - for _, link := range row.ScalarChildLinks { - if !include(row, link) { - continue - } - - groupIndex, ok := groupByType[link.Type] - if !ok { - groupIndex = len(groups) - groupByType[link.Type] = groupIndex - groups = append(groups, scalarLinkGroup{typ: link.Type}) - } - groups[groupIndex].values = append(groups[groupIndex].values, format(link)) - } - - lines := make([]string, 0, len(groups)) - for _, group := range groups { - joined := strings.Join(group.values, ", ") - if joined == "" { - continue - } - - typePart := "" - if group.typ != "" { - typePart = group.typ + ": " - } - lines = append(lines, typePart+joined) - } - return lines -} - -func formatRawScalarLink(link plantree.ScalarChildLink) string { - if link.Variable != "" { - return fmt.Sprintf("$%s=%s", link.Variable, link.Description) - } - return link.Description -} - -func scalarLinkDescription(link plantree.ScalarChildLink) string { - return link.Description -} - -func normalizeKeyOrderSuffix(s string) string { - s = strings.TrimSpace(s) - for _, suffix := range []string{"(ASC)", "(DESC)"} { - if strings.HasSuffix(s, " "+suffix) { - return strings.TrimSuffix(s, " "+suffix) + " " + strings.Trim(suffix, "()") - } - } - return s -} - -func isOrderingScalarLink(row plantree.RowWithPredicates, link plantree.ScalarChildLink) bool { - switch row.DisplayName { - case "Sort", "Sort Limit": - return link.Type == "Key" - case "Minor Sort", "Minor Sort Limit": - return link.Type == "MajorKey" || link.Type == "MinorKey" - default: - return false - } -} - -func isAggregateScalarLink(row plantree.RowWithPredicates, link plantree.ScalarChildLink) bool { - return row.DisplayName == "Aggregate" && (link.Type == "Key" || link.Type == "Agg") -} diff --git a/internal/mycli/explain_print_sections_test.go b/internal/mycli/explain_print_sections_test.go index 5f82c314..f102f04e 100644 --- a/internal/mycli/explain_print_sections_test.go +++ b/internal/mycli/explain_print_sections_test.go @@ -15,8 +15,10 @@ package mycli import ( + "strings" "testing" + "github.com/apstndb/spanner-mycli/enums" "github.com/apstndb/spannerplan/plantree" planref "github.com/apstndb/spannerplan/plantree/reference" "github.com/google/go-cmp/cmp" @@ -34,80 +36,67 @@ func TestBuildPlanAppendices(t *testing.T) { DisplayName: "Sort", ScalarChildLinks: []plantree.ScalarChildLink{ {Type: "Key", Description: "$LastName (ASC)"}, - {Type: "Key", Description: "$FirstName (DESC)"}, - }, - }, - { - ID: 2, - DisplayName: "Aggregate", - ScalarChildLinks: []plantree.ScalarChildLink{ - {Type: "Key", Description: "$SingerId"}, - {Type: "Agg", Variable: "count", Description: "COUNT(*)"}, }, }, } - predicates, appendices := buildPlanAppendices(rows, planref.PrintSections{ + predicates, appendices, err := buildPlanAppendices(rows, planref.PrintSections{ planref.PrintPredicates, planref.PrintOrdering, - planref.PrintAggregate, }) - - wantPredicates := []string{"0: Condition: ($SingerId = 1)"} - if diff := cmp.Diff(wantPredicates, predicates); diff != "" { - t.Errorf("predicates mismatch (-want +got):\n%s", diff) + if err != nil { + t.Fatalf("buildPlanAppendices() error = %v", err) + } + if len(appendices) != 2 { + t.Fatalf("len(appendices) = %d, want 2: %+v", len(appendices), appendices) + } + if appendices[0].Title != "Predicates(identified by ID):" { + t.Errorf("appendices[0].Title = %q", appendices[0].Title) + } + if appendices[1].Title != "Ordering(identified by ID):" { + t.Errorf("appendices[1].Title = %q", appendices[1].Title) + } + if diff := cmp.Diff(appendices[0].Lines, predicates); diff != "" { + t.Errorf("legacy predicates slice should match PrintPredicates lines (-appendix +predicates):\n%s", diff) } - wantAppendices := []ResultAppendix{ - { - Title: "Predicates(identified by ID):", - Lines: []string{"0: Condition: ($SingerId = 1)"}, - }, - { - Title: "Ordering(identified by ID):", - Lines: []string{"1: Key: $LastName ASC, $FirstName DESC"}, - }, - { - Title: "Aggregates(identified by ID):", - Lines: []string{ - "2: Key: $SingerId", - " Agg: COUNT(*)", - }, - }, + predicates, appendices, err = buildPlanAppendices(rows, planref.PrintSections{}) + if err != nil { + t.Fatalf("empty sections error = %v", err) } - if diff := cmp.Diff(wantAppendices, appendices); diff != "" { - t.Errorf("appendices mismatch (-want +got):\n%s", diff) + if len(predicates) != 0 || len(appendices) != 0 { + t.Fatalf("empty sections returned predicates=%q appendices=%+v", predicates, appendices) } } -func TestBuildPlanAppendicesTypedAndFull(t *testing.T) { +func TestBuildPlanAppendicesPropagatesInvalidSections(t *testing.T) { t.Parallel() - rows := []plantree.RowWithPredicates{ - { - ID: 0, - ScalarChildLinks: []plantree.ScalarChildLink{ - {Type: "Condition", Description: "($SingerId = 1)"}, - {Variable: "SingerId", Description: "SingerId"}, - }, - }, + _, _, err := buildPlanAppendices(nil, planref.PrintSections{planref.PrintFull, planref.PrintPredicates}) + if err == nil { + t.Fatal("buildPlanAppendices() error = nil, want invalid section combination") } +} - _, typed := buildPlanAppendices(rows, planref.PrintSections{planref.PrintTyped}) - wantTyped := []ResultAppendix{{ - Title: "Node Parameters(identified by ID):", - Lines: []string{"0: Condition: ($SingerId = 1)"}, - }} - if diff := cmp.Diff(wantTyped, typed); diff != "" { - t.Errorf("typed appendices mismatch (-want +got):\n%s", diff) +func TestBuildQueryPlanAppendixPropagatesAppendixError(t *testing.T) { + t.Parallel() + sysVars := newSystemVariablesWithDefaultsForTest() + sysVars.Display.ParsedExplainPrintSections = planref.PrintSections{planref.PrintFull, planref.PrintPredicates} + _, err := buildQueryPlanAppendix(sysVars, testQueryPlan(t)) + if err == nil { + t.Fatal("buildQueryPlanAppendix() error = nil, want invalid section combination") } +} - _, full := buildPlanAppendices(rows, planref.PrintSections{planref.PrintFull}) - wantFull := []ResultAppendix{{ - Title: "Node Parameters(identified by ID):", - Lines: []string{"0: Condition: ($SingerId = 1)", " $SingerId=SingerId"}, - }} - if diff := cmp.Diff(wantFull, full); diff != "" { - t.Errorf("full appendices mismatch (-want +got):\n%s", diff) +func TestBuildExplainAnalyzeResultPropagatesAppendixError(t *testing.T) { + t.Parallel() + sysVars := newSystemVariablesWithDefaultsForTest() + invalid := planref.PrintSections{planref.PrintFull, planref.PrintPredicates} + _, err := buildExplainAnalyzeResult(sysVars, testQueryPlan(t), QueryStats{}, enums.ExplainFormatUnspecified, 0, &invalid) + if err == nil { + t.Fatal("buildExplainAnalyzeResult() error = nil, want invalid section combination") + } + if !strings.Contains(err.Error(), "failed to process query plan") { + t.Fatalf("buildExplainAnalyzeResult() error = %v, want wrapped process-plan error", err) } } diff --git a/internal/mycli/statements_explain_describe.go b/internal/mycli/statements_explain_describe.go index 63b28582..9ee1b9d4 100644 --- a/internal/mycli/statements_explain_describe.go +++ b/internal/mycli/statements_explain_describe.go @@ -683,7 +683,10 @@ func processPlan(plan *sppb.QueryPlan, columnRenderDefs []columnRenderDef, inlin } rows = append(rows, toRow(rowStrs...)) } - predicates, appendices = buildPlanAppendices(rowsWithPredicates, printSections) + predicates, appendices, err = buildPlanAppendices(rowsWithPredicates, printSections) + if err != nil { + return nil, nil, nil, err + } return rows, predicates, appendices, nil } diff --git a/internal/mycli/statements_explain_describe_test.go b/internal/mycli/statements_explain_describe_test.go index 53775ad2..4cf3d3d5 100644 --- a/internal/mycli/statements_explain_describe_test.go +++ b/internal/mycli/statements_explain_describe_test.go @@ -562,18 +562,20 @@ func TestProcessPlanAppendicesUsingRealPlan(t *testing.T) { t.Parallel() plan := loadTestPlan(t, "testdata/plans/scalar_subqueries.input.json") - _, _, appendices, err := processPlanWithoutStats(plan, enums.ExplainFormatTraditional, 0, false, planref.PrintSections{ - planref.PrintAggregate, - planref.PrintTyped, - }) + _, _, aggregates, err := processPlanWithoutStats(plan, enums.ExplainFormatTraditional, 0, false, planref.PrintSections{planref.PrintAggregate}) if err != nil { - t.Fatalf("processPlanWithoutStats() error = %v", err) + t.Fatalf("processPlanWithoutStats(aggregate) error = %v", err) + } + if !appendixContains(aggregates, "Aggregates(identified by ID):", "Agg: COUNT()") { + t.Fatalf("aggregate appendix does not contain COUNT() line: %#v", aggregates) } - if !appendixContains(appendices, "Aggregates(identified by ID):", "Agg: COUNT()") { - t.Fatalf("aggregate appendix does not contain COUNT() line: %#v", appendices) + + _, _, typed, err := processPlanWithoutStats(plan, enums.ExplainFormatTraditional, 0, false, planref.PrintSections{planref.PrintTyped}) + if err != nil { + t.Fatalf("processPlanWithoutStats(typed) error = %v", err) } - if !appendixContains(appendices, "Node Parameters(identified by ID):", "Condition:") { - t.Fatalf("typed appendix does not contain condition line: %#v", appendices) + if !appendixContains(typed, "Node Parameters(identified by ID):", "Condition:") { + t.Fatalf("typed appendix does not contain condition line: %#v", typed) } } From 35cd70b3e33261d570294faddfebb743ec640706 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 16 Sep 2026 03:42:12 +0900 Subject: [PATCH 2/3] fix: keep nil appendix slices when no sections render Preserve the historical Result.Appendices nil contract for EXPLAIN output that has no selected appendix lines. --- internal/mycli/explain_print_sections.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mycli/explain_print_sections.go b/internal/mycli/explain_print_sections.go index 750125bc..5b2623d2 100644 --- a/internal/mycli/explain_print_sections.go +++ b/internal/mycli/explain_print_sections.go @@ -77,7 +77,7 @@ func buildPlanAppendices(rows []plantree.RowWithPredicates, sections planref.Pri } var predicates []string - appendices := make([]ResultAppendix, 0, len(built)) + var appendices []ResultAppendix for _, appendix := range built { mapped := ResultAppendix{Title: appendix.Title, Lines: appendix.Lines} appendices = append(appendices, mapped) From f1749b68f8cfb38e2e371d7f400a8aa0642596c9 Mon Sep 17 00:00:00 2001 From: apstndb <803393+apstndb@users.noreply.github.com> Date: Wed, 16 Sep 2026 03:57:49 +0900 Subject: [PATCH 3/3] chore: pin spannerplan v0.3.1 and tighten appendix tests Replace the temporary pseudo-version with the released github.com/apstndb/spannerplan@v0.3.1 (69dd04e). Assert literal predicate and ordering appendix lines, and require the specific full-section validation error instead of any non-nil error. --- go.mod | 2 +- go.sum | 4 +-- internal/mycli/explain_print_sections_test.go | 29 ++++++++++++------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 531ac3c4..1df3ac7d 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/apstndb/spanemuboost v0.4.7 github.com/apstndb/spaniter v0.3.1 github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6 - github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e + github.com/apstndb/spannerplan v0.3.1 github.com/apstndb/spanstats v0.1.0 github.com/apstndb/spantype v0.3.13 github.com/apstndb/spanvalue v0.8.4 diff --git a/go.sum b/go.sum index 9bf07d6b..ce1b6aa7 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/apstndb/spaniter v0.3.1 h1:hhi4+JCF80x696bg7zIco7Vo8kmPbkbTdvSOEhItp5 github.com/apstndb/spaniter v0.3.1/go.mod h1:aBSHcHIqgAZXCxFdi734R/wAQUIuCQ6WZ+CjOCxARIM= github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6 h1:JP8l0QJZRVSRwBXTxaMMAjrmF0kQDjXlCM0bwTCo5Gs= github.com/apstndb/spanner-docs-embed v0.0.0-20260312161525-0136df2da2a6/go.mod h1:Enpmw/D11tME865ROYDQzoHSIo9V8uCgWdu+fxA7n2M= -github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e h1:w7sigXasdgOUkp0XJGsS4gn7bIJy1wfExKSRgUjjc10= -github.com/apstndb/spannerplan v0.3.1-0.20260915183136-195dc131696e/go.mod h1:zVUS187Z4DGaJekIBt2E1JYb9UMKQ7s5ZZo3HOr522E= +github.com/apstndb/spannerplan v0.3.1 h1:CloO1irr01HDurWxLy1gYAliIGbNdeVl/bpD0Q+zK8U= +github.com/apstndb/spannerplan v0.3.1/go.mod h1:zVUS187Z4DGaJekIBt2E1JYb9UMKQ7s5ZZo3HOr522E= github.com/apstndb/spanstats v0.1.0 h1:wAPqtFn1AfQaIudB4HwTLiIPinwWJw/gQhgE7NQWVKo= github.com/apstndb/spanstats v0.1.0/go.mod h1:DFGYC9e7WE0BFQL0st6EQwrl1Pn2FuvDWSefwI9FTWc= github.com/apstndb/spantype v0.3.13 h1:FTP3zUpVXMfPlZ3P+1RK6SYHND+96YVht0I+ZHGIzMc= diff --git a/internal/mycli/explain_print_sections_test.go b/internal/mycli/explain_print_sections_test.go index f102f04e..0f865fa0 100644 --- a/internal/mycli/explain_print_sections_test.go +++ b/internal/mycli/explain_print_sections_test.go @@ -56,8 +56,16 @@ func TestBuildPlanAppendices(t *testing.T) { if appendices[1].Title != "Ordering(identified by ID):" { t.Errorf("appendices[1].Title = %q", appendices[1].Title) } - if diff := cmp.Diff(appendices[0].Lines, predicates); diff != "" { - t.Errorf("legacy predicates slice should match PrintPredicates lines (-appendix +predicates):\n%s", diff) + wantPredicateLine := "0: Condition: ($SingerId = 1)" + wantOrderingLine := "1: Key: $LastName ASC" + if diff := cmp.Diff([]string{wantPredicateLine}, appendices[0].Lines); diff != "" { + t.Errorf("predicate appendix lines mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff([]string{wantOrderingLine}, appendices[1].Lines); diff != "" { + t.Errorf("ordering appendix lines mismatch (-want +got):\n%s", diff) + } + if diff := cmp.Diff([]string{wantPredicateLine}, predicates); diff != "" { + t.Errorf("legacy predicates slice mismatch (-want +got):\n%s", diff) } predicates, appendices, err = buildPlanAppendices(rows, planref.PrintSections{}) @@ -69,11 +77,13 @@ func TestBuildPlanAppendices(t *testing.T) { } } +const invalidPrintSectionsCause = `print section "full" cannot be combined with other sections` + func TestBuildPlanAppendicesPropagatesInvalidSections(t *testing.T) { t.Parallel() _, _, err := buildPlanAppendices(nil, planref.PrintSections{planref.PrintFull, planref.PrintPredicates}) - if err == nil { - t.Fatal("buildPlanAppendices() error = nil, want invalid section combination") + if err == nil || !strings.Contains(err.Error(), invalidPrintSectionsCause) { + t.Fatalf("buildPlanAppendices() error = %v, want %q", err, invalidPrintSectionsCause) } } @@ -82,8 +92,8 @@ func TestBuildQueryPlanAppendixPropagatesAppendixError(t *testing.T) { sysVars := newSystemVariablesWithDefaultsForTest() sysVars.Display.ParsedExplainPrintSections = planref.PrintSections{planref.PrintFull, planref.PrintPredicates} _, err := buildQueryPlanAppendix(sysVars, testQueryPlan(t)) - if err == nil { - t.Fatal("buildQueryPlanAppendix() error = nil, want invalid section combination") + if err == nil || !strings.Contains(err.Error(), invalidPrintSectionsCause) { + t.Fatalf("buildQueryPlanAppendix() error = %v, want %q", err, invalidPrintSectionsCause) } } @@ -92,11 +102,8 @@ func TestBuildExplainAnalyzeResultPropagatesAppendixError(t *testing.T) { sysVars := newSystemVariablesWithDefaultsForTest() invalid := planref.PrintSections{planref.PrintFull, planref.PrintPredicates} _, err := buildExplainAnalyzeResult(sysVars, testQueryPlan(t), QueryStats{}, enums.ExplainFormatUnspecified, 0, &invalid) - if err == nil { - t.Fatal("buildExplainAnalyzeResult() error = nil, want invalid section combination") - } - if !strings.Contains(err.Error(), "failed to process query plan") { - t.Fatalf("buildExplainAnalyzeResult() error = %v, want wrapped process-plan error", err) + if err == nil || !strings.Contains(err.Error(), "failed to process query plan") || !strings.Contains(err.Error(), invalidPrintSectionsCause) { + t.Fatalf("buildExplainAnalyzeResult() error = %v, want wrapped %q", err, invalidPrintSectionsCause) } }