Repository: apstndb/spannerplanviz
Pull request #38: feat: Implement --known-flag
Author: login: apstndb
Created: 2025-05-29T17:58:02Z | Merged: 2025-05-29T19:10:53Z

This PR implements `--known-flag`. It is enhancement of `--full-scan`.
If it is set to `LABEL` (default), the below known boolean metadata are printed as key-only label only if its value is `true`.

- `Full scan`
- `split_ranges_aligned`

This feature is also available as `queryplan.WithKnownFlagFormat`.

## `--known-flag=RAW`

```
$ execspansql ${SPANNER_DATABASE} --sql="SELECT * FROM Singers JOIN Albums USING (SingerId)" --query-mode=PROFILE --format=yaml | go run ./cmd/rendertree --compact --known-flag=RAW
+-----+-----------------------------------------------------------------------------+
| ID  | Operator                                                                    |
+-----+-----------------------------------------------------------------------------+
|   0 | Distributed Union on Singers<Row>(split_ranges_aligned:true)                |
|   1 | +Local Distributed Union<Row>                                               |
|   2 |  +Serialize Result<Row>                                                     |
|   3 |   +Cross Apply<Row>                                                         |
|   4 |    +[Input]Table Scan on Singers<Row>(Full scan:true,scan_method:Automatic) |
|  10 |    +[Map]Local Distributed Union<Row>                                       |
|  11 |     +Filter Scan<Row>(seekable_key_size:0)                                  |
| *12 |      +Table Scan on Albums<Row>(scan_method:Row)                            |
+-----+-----------------------------------------------------------------------------+
Predicates(identified by ID):
 12: Seek Condition: ($SingerId_1 = $SingerId)
```

###  `--known-flag=LABEL` (new default)
```
$ execspansql ${SPANNER_DATABASE} --sql="SELECT * FROM Singers JOIN Albums USING (SingerId)" --query-mode=PROFILE --format=yaml | go run ./cmd/rendertree --compact                 
+-----+------------------------------------------------------------------------+
| ID  | Operator                                                               |
+-----+------------------------------------------------------------------------+
|   0 | Distributed Union on Singers<Row>(split_ranges_aligned)                |
|   1 | +Local Distributed Union<Row>                                          |
|   2 |  +Serialize Result<Row>                                                |
|   3 |   +Cross Apply<Row>                                                    |
|   4 |    +[Input]Table Scan on Singers<Row>(Full scan,scan_method:Automatic) |
|  10 |    +[Map]Local Distributed Union<Row>                                  |
|  11 |     +Filter Scan<Row>(seekable_key_size:0)                             |
| *12 |      +Table Scan on Albums<Row>(scan_method:Row)                       |
+-----+------------------------------------------------------------------------+
Predicates(identified by ID):
 12: Seek Condition: ($SingerId_1 = $SingerId)
```


Diff:
diff --git a/cmd/rendertree/main.go b/cmd/rendertree/main.go
index b466340..2786e9e 100644
--- a/cmd/rendertree/main.go
+++ b/cmd/rendertree/main.go
@@ -195,13 +195,26 @@ func run() error {
 	disallowUnknownStats := flag.Bool("disallow-unknown-stats", false, "error on unknown stats field")
 	executionMethod := flag.String("execution-method", "angle", "Format execution method metadata: 'angle' or 'raw' (default: angle)")
 	targetMetadata := flag.String("target-metadata", "on", "Format target metadata: 'on' or 'raw' (default: on)")
-	fullscan := flag.String("full-scan", "label", "Format full scan: 'label' or 'raw' (default: label)")
+	fullscan := flag.String("full-scan", "", "Deprecated alias for --known-flag.")
+	knownFlag := flag.String("known-flag", "", "Format known flags: 'label' or 'raw' (default: label)")
 	compact := flag.Bool("compact", false, "Enable compact format")
 
 	var custom stringList
 	flag.Var(&custom, "custom", "")
 	flag.Parse()
 
+	if *fullscan != "" {
+		if *knownFlag != "" {
+			fmt.Fprintln(os.Stderr, "--full-scan and --known-flag are mutually exclusive.")
+			flag.Usage()
+			os.Exit(1)
+		}
+
+		fmt.Fprintln(os.Stderr, "--full-scan is deprecated. you must migrate to --known-flag.")
+
+		*knownFlag = *fullscan
+	}
+
 	printMode := parsePrintMode(*printModeStr)
 
 	var withStats bool
@@ -246,13 +259,13 @@ func run() error {
 		os.Exit(1)
 	}
 
-	switch strings.ToUpper(*fullscan) {
+	switch strings.ToUpper(*knownFlag) {
 	case "", "LABEL":
-		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithFullScanFormat(queryplan.FullScanFormatLabel)))
+		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithKnownFlagFormat(queryplan.KnownFlagFormatLabel)))
 	case "RAW":
-		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithFullScanFormat(queryplan.FullScanFormatRaw)))
+		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithKnownFlagFormat(queryplan.KnownFlagFormatRaw)))
 	default:
-		fmt.Fprintf(os.Stderr, "Invalid value for -full-scan flag: %s.  Must be 'label' or 'raw'.\n", *fullscan)
+		fmt.Fprintf(os.Stderr, "Invalid value for -known-flag flag: %s.  Must be 'label' or 'raw'.\n", *knownFlag)
 		flag.Usage()
 		os.Exit(1)
 	}
diff --git a/cmd/rendertree/main_test.go b/cmd/rendertree/main_test.go
index 4f6f991..7cd35bf 100644
--- a/cmd/rendertree/main_test.go
+++ b/cmd/rendertree/main_test.go
@@ -54,7 +54,7 @@ func TestRenderTree(t *testing.T) {
 			`+-----+-------------------------------------------------------------------------------------------+
 | ID  | Operator                                                                                  |
 +-----+-------------------------------------------------------------------------------------------+
-|   0 | Distributed Union on AlbumsByAlbumTitle <Row> (split_ranges_aligned: false)               |
+|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                             |
 |  *1 | +- Distributed Cross Apply <Row>                                                          |
 |   2 |    +- [Input] Create Batch <Row>                                                          |
 |   3 |    |  +- Local Distributed Union <Row>                                                    |
@@ -80,7 +80,7 @@ Predicates(identified by ID):
 			`+-----+-----------------------------------------------------------------------------+
 | ID  | Operator                                                                    |
 +-----+-----------------------------------------------------------------------------+
-|   0 | Distributed Union on AlbumsByAlbumTitle<Row>(split_ranges_aligned:false)    |
+|   0 | Distributed Union on AlbumsByAlbumTitle<Row>                                |
 |  *1 | +Distributed Cross Apply<Row>                                               |
 |   2 |  +[Input]Create Batch<Row>                                                  |
 |   3 |  |+Local Distributed Union<Row>                                             |
@@ -106,7 +106,7 @@ Predicates(identified by ID):
 			`+-----+-------------------------------------------------------------------------------------------+------+-------+------------+
 | ID  | Operator                                                                                  | Rows | Exec. | Latency    |
 +-----+-------------------------------------------------------------------------------------------+------+-------+------------+
-|   0 | Distributed Union on AlbumsByAlbumTitle <Row> (split_ranges_aligned: false)               |   33 |     1 | 1.92 msecs |
+|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                             |   33 |     1 | 1.92 msecs |
 |  *1 | +- Distributed Cross Apply <Row>                                                          |   33 |     1 |  1.9 msecs |
 |   2 |    +- [Input] Create Batch <Row>                                                          |      |       |            |
 |   3 |    |  +- Local Distributed Union <Row>                                                    |    7 |     1 | 0.95 msecs |
@@ -148,7 +148,7 @@ Predicates(identified by ID):
 			`+-----+-------------------------------------------------------------------------------------------+------+---------+----------+
 | ID  | Operator                                                                                  | Rows | Scanned | Filtered |
 +-----+-------------------------------------------------------------------------------------------+------+---------+----------+
-|   0 | Distributed Union on AlbumsByAlbumTitle <Row> (split_ranges_aligned: false)               |   33 |         |          |
+|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                             |   33 |         |          |
 |  *1 | +- Distributed Cross Apply <Row>                                                          |   33 |         |          |
 |   2 |    +- [Input] Create Batch <Row>                                                          |      |         |          |
 |   3 |    |  +- Local Distributed Union <Row>                                                    |    7 |         |          |
@@ -180,7 +180,7 @@ Predicates(identified by ID):
 			`+-----+-------------------------------------------------------------------------------------------+------+---------+----------+
 | ID  | Operator                                                                                  | Rows | Scanned | Filtered |
 +-----+-------------------------------------------------------------------------------------------+------+---------+----------+
-|   0 | Distributed Union on AlbumsByAlbumTitle <Row> (split_ranges_aligned: false)               |   33 |         |          |
+|   0 | Distributed Union on AlbumsByAlbumTitle <Row>                                             |   33 |         |          |
 |  *1 | +- Distributed Cross Apply <Row>                                                          |   33 |         |          |
 |   2 |    +- [Input] Create Batch <Row>                                                          |      |         |          |
 |   3 |    |  +- Local Distributed Union <Row>                                                    |    7 |         |          |
@@ -209,7 +209,7 @@ Predicates(identified by ID):
 		opts := []plantree.Option{plantree.WithQueryPlanOptions(
 			queryplan.WithTargetMetadataFormat(queryplan.TargetMetadataFormatOn),
 			queryplan.WithExecutionMethodFormat(queryplan.ExecutionMethodFormatAngle),
-			queryplan.WithFullScanFormat(queryplan.FullScanFormatLabel),
+			queryplan.WithKnownFlagFormat(queryplan.KnownFlagFormatLabel),
 		)}
 
 		if tcase.compact {
diff --git a/plantree/plantree.go b/plantree/plantree.go
index dd3aa8d..84c6c74 100644
--- a/plantree/plantree.go
+++ b/plantree/plantree.go
@@ -9,8 +9,8 @@ import (
 
 	sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
 	"github.com/apstndb/lox"
-	"github.com/samber/lo"
 	"github.com/apstndb/treeprint"
+	"github.com/samber/lo"
 
 	"github.com/apstndb/spannerplanviz/queryplan"
 	"github.com/apstndb/spannerplanviz/stats"
diff --git a/queryplan/queryplan.go b/queryplan/queryplan.go
index b5c40a5..67374ef 100644
--- a/queryplan/queryplan.go
+++ b/queryplan/queryplan.go
@@ -3,6 +3,7 @@ package queryplan
 import (
 	"cmp"
 	"fmt"
+	"slices"
 	"sort"
 	"strings"
 
@@ -91,7 +92,7 @@ func (qp *QueryPlan) GetParentNodeByChildLink(link *sppb.PlanNode_ChildLink) *sp
 type option struct {
 	executionMethodFormat ExecutionMethodFormat
 	targetMetadataFormat  TargetMetadataFormat
-	fullScanFormat        FullScanFormat
+	knownFlagFormat       KnownFlagFormat
 	compact               bool
 }
 
@@ -117,14 +118,21 @@ const (
 	TargetMetadataFormatOn
 )
 
-type FullScanFormat int64
+type KnownFlagFormat int64
+type FullScanFormat = KnownFlagFormat
 
 const (
-	// FullScanFormatRaw prints Full scan metadata as is.
-	FullScanFormatRaw FullScanFormat = iota
+	// KnownFlagFormatRaw prints known boolean flag metadata as is.
+	KnownFlagFormatRaw KnownFlagFormat = iota
 
-	// FullScanFormatLabel prints Full scan metadata as "Full scan" without value.
-	FullScanFormatLabel
+	// KnownFlagFormatLabel prints known boolean flag metadata without value if true or omits if false.
+	KnownFlagFormatLabel
+
+	// Deprecated: FullScanFormatRaw is an alias of KnownFlagFormatRaw. (Deprecated)
+	FullScanFormatRaw = KnownFlagFormatRaw
+
+	// Deprecated: FullScanFormatLabel is an alias of KnownFlagFormatLabel. (Deprecated)
+	FullScanFormatLabel = KnownFlagFormatLabel
 )
 
 func WithExecutionMethodFormat(fmt ExecutionMethodFormat) Option {
@@ -139,18 +147,25 @@ func WithTargetMetadataFormat(fmt TargetMetadataFormat) Option {
 	}
 }
 
-func WithFullScanFormat(fmt FullScanFormat) Option {
+func WithKnownFlagFormat(fmt KnownFlagFormat) Option {
 	return func(o *option) {
-		o.fullScanFormat = fmt
+		o.knownFlagFormat = fmt
 	}
 }
 
+// Deprecated: WithFullScanFormat is an alias of WithKnownFlagFormat.
+func WithFullScanFormat(fmt FullScanFormat) Option {
+	return WithKnownFlagFormat(fmt)
+}
+
 func EnableCompact() Option {
 	return func(o *option) {
 		o.compact = true
 	}
 }
 
+var knownBooleanFlagKeys = []string{"Full scan", "split_ranges_aligned"}
+
 func NodeTitle(node *sppb.PlanNode, opts ...Option) string {
 	var o option
 	for _, opt := range opts {
@@ -176,8 +191,8 @@ func NodeTitle(node *sppb.PlanNode, opts ...Option) string {
 	executionMethodPart := lox.IfOrEmpty(o.executionMethodFormat == ExecutionMethodFormatAngle && len(executionMethod) > 0,
 		"<"+executionMethod+">")
 
-	var needFullscanToFront bool
-	fields := make([]string, 0)
+	var labels []string
+	var fields []string
 	for k, v := range metadataFields {
 		switch k {
 		case "call_type", "iterator_type": // Skip because it is displayed in node title
@@ -203,21 +218,21 @@ func NodeTitle(node *sppb.PlanNode, opts ...Option) string {
 			if o.targetMetadataFormat != TargetMetadataFormatRaw {
 				continue
 			}
-		case "Full scan":
-			if o.fullScanFormat != FullScanFormatRaw && v.GetStringValue() == "true" {
-				needFullscanToFront = true
-				continue
+		}
+
+		if o.knownFlagFormat != KnownFlagFormatRaw && slices.Contains(knownBooleanFlagKeys, k) {
+			if v.GetStringValue() == "true" {
+				labels = append(labels, k)
 			}
+			continue
 		}
 		fields = append(fields, fmt.Sprintf("%s:%s%s", k, sep, v.GetStringValue()))
 	}
 
+	sort.Strings(labels)
 	sort.Strings(fields)
-	if needFullscanToFront {
-		fields = append([]string{"Full scan"}, fields...)
-	}
 
-	return joinIfNotEmpty(sep, operator, executionMethodPart, encloseIfNotEmpty("(", strings.Join(fields, ","+sep), ")"))
+	return joinIfNotEmpty(sep, operator, executionMethodPart, encloseIfNotEmpty("(", strings.Join(slices.Concat(labels, fields), ","+sep), ")"))
 }
 
 func encloseIfNotEmpty(open, input, close string) string {


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @apstndb, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
Hello team,

Gemini here, providing a summary of this pull request to help everyone quickly understand the changes.

This PR introduces a new command-line flag, `--known-flag`, to the `rendertree` tool. This flag provides more granular control over how certain boolean metadata fields, specifically "Full scan" and "split_ranges_aligned", are displayed in the query plan output. The primary goal is to make the default output cleaner by only showing these flags as simple labels (e.g., `Full scan`) when their value is `true` and omitting them entirely when `false`. The existing `--full-scan` flag is now an alias for `--known-flag` to maintain backward compatibility. The underlying `queryplan` package has been refactored to support this new, more general "known flag" formatting concept, allowing for potential future expansion to other boolean flags.

Please review the changes at your convenience.

### Highlights
* **New `--known-flag` option**: Adds a new command-line flag `--known-flag` to `cmd/rendertree` to control the formatting of specific boolean metadata in the output. It accepts `label` (default) or `raw`.
* **`--full-scan` alias**: The existing `--full-scan` flag is now an alias for the new `--known-flag` for backward compatibility.
* **Improved boolean metadata formatting**: Implements a new default display format (`label`) for known boolean flags ("Full scan", "split_ranges_aligned") where only the key is shown if the value is `true`, and the entry is omitted if `false`. The `raw` format shows the full `key:value`.
* **Refactor `queryplan` formatting**: Refactors the internal `queryplan` package to generalize the formatting logic from just "full scan" to any "known flag", introducing `KnownFlagFormat` and updating related functions (`WithKnownFlagFormat`). Deprecated aliases are provided for the old types and functions.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **cmd/rendertree/main.go**
  * Added `--known-flag` string flag with a default value of "label" (L199).
  * Changed the default value of `--full-scan` to an empty string and updated its description to indicate it's an alias (L198).
  * Added logic to set `knownFlag` based on `fullscan` if `fullscan` is provided (L202-L204).
  * Updated the switch statement to use the `knownFlag` variable instead of `fullscan` (L254).
  * Updated the options passed to `plantree.WithQueryPlanOptions` to use `queryplan.WithKnownFlagFormat` instead of `queryplan.WithFullScanFormat` (L256, L258).
* **cmd/rendertree/main_test.go**
  * Updated expected output strings in multiple test cases to reflect that `split_ranges_aligned: false` is no longer displayed in the default format (L57, L83, L109, L151, L183).
  * Updated the test options to use `queryplan.WithKnownFlagFormat` instead of `queryplan.WithFullScanFormat` (L212).
* **plantree/plantree.go**
  * Reordered imports (L12-L13).
  * Modified the `NodeTitle` function to handle a list of `labels` and `fields` separately (L192-L193).
  * Added logic to check if a metadata key is one of the known flags ("Full scan", "split_ranges_aligned") and, if the format is not `Raw` and the value is "true", add it to the `labels` list instead of `fields` (L221-L225).
  * Sorted `labels` separately (L230).
  * Concatenated `labels` and `fields` before joining them for the final output string (L233).
* **queryplan/queryplan.go**
  * Added `slices` import (L6).
  * Renamed the `fullScanFormat` field in the `option` struct to `knownFlagFormat` (L95).
  * Defined a new type `KnownFlagFormat` and constants `KnownFlagFormatRaw` and `KnownFlagFormatLabel` (L121-L129).
  * Made `FullScanFormat` an alias for `KnownFlagFormat` and added deprecated aliases for the old constants (L122, L131-L135).
  * Renamed the `WithFullScanFormat` function to `WithKnownFlagFormat` (L150).
  * Added a deprecated `WithFullScanFormat` function that calls `WithKnownFlagFormat` for backward compatibility (L156-L159).

</details>

<details>
<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.
</details>


<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

> Command line flags,
> Change how the output looks,
> Labels or raw text.


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T17:58:42Z
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request successfully implements the `--known-flag` feature, providing a more flexible way to display known boolean metadata fields like `Full scan` and `split_ranges_aligned`. The old `--full-scan` flag is now an alias, ensuring backward compatibility. The changes are well-structured across the CLI and query plan logic, and tests have been updated accordingly. The deprecation strategy is clear. I have a few minor suggestions to improve CLI messaging and code maintainability.

### Summary of Findings
* **CLI Flag Help Text Clarity**: The help text for the deprecated `--full-scan` flag could be more precise about its behavior, specifically that it overrides `--known-flag` if set, to avoid potential user confusion.
* **CLI Error Message Accuracy**: The error message for an invalid value for known flag formatting can be misleading as it refers to the old flag name (`--full-scan`) and uses its value, even when the new `--known-flag` was used with an invalid value.
* **Code Maintainability: Hardcoded Flag Names**: In `queryplan/queryplan.go`, the list of metadata keys treated as 'known flags' (e.g., 'Full scan', 'split_ranges_aligned') is hardcoded within the `NodeTitle` function. Extracting this list to a package-level constant or variable would improve maintainability if this list needs to be updated or referenced elsewhere in the future.

### Merge Readiness
The pull request is well on its way to being ready for merge. I recommend addressing the `medium` severity suggestions to enhance CLI usability and code maintainability. Once these are considered, the PR should be in excellent shape. As a reviewer, I am not authorized to approve pull requests, so please ensure further review and approval as per your team's process.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T17:59:53Z
user: login: gemini-code-assist[bot]
body: ## Code Review
This pull request effectively implements the `--known-flag` feature, providing a cleaner way to display known boolean metadata in query plans. The deprecation of the older `--full-scan` flag is handled gracefully with backward compatibility in mind.

The changes are well-structured, and the logic for the new flag's behavior (`LABEL` vs. `RAW`) is clearly implemented in `queryplan/queryplan.go` and correctly utilized in `cmd/rendertree/main.go`. Test cases in `cmd/rendertree/main_test.go` have been appropriately updated to reflect the new default behavior, ensuring that boolean flags which are `false` are omitted when the `LABEL` format (the new default) is active.

Overall, the code quality is good, and the changes align well with the described objectives. I found no issues of `medium` severity or higher.

### Merge Readiness
The pull request appears to be in good shape. The new feature is well-implemented, the deprecated flag is handled correctly, and tests are updated. Based on this review, the changes look ready for merging. As always, ensure any other required reviews or CI checks are completed. I am not authorized to approve the pull request.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-29T18:14:25Z

Review comments:
diff_hunk: @@ -195,9 +195,14 @@ func run() error {
 	disallowUnknownStats := flag.Bool("disallow-unknown-stats", false, "error on unknown stats field")
 	executionMethod := flag.String("execution-method", "angle", "Format execution method metadata: 'angle' or 'raw' (default: angle)")
 	targetMetadata := flag.String("target-metadata", "on", "Format target metadata: 'on' or 'raw' (default: on)")
-	fullscan := flag.String("full-scan", "label", "Format full scan: 'label' or 'raw' (default: label)")
+	fullscan := flag.String("full-scan", "", "Alias of --known-flag")
path: cmd/rendertree/main.go
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The help text for `--full-scan` currently states it's an "Alias of --known-flag". While true, it might be clearer to also mention its precedence. The current logic (lines 202-204) means that if `--full-scan` is provided, its value will override any value set for `--known-flag` (including its default).

Could the help text be updated to reflect this behavior more explicitly, perhaps something like "Deprecated alias for --known-flag. If set, its value takes precedence and is used for known flag formatting."? Note that I am unable to approve this pull request.

```suggestion
fullscan := flag.String("full-scan", "", "Deprecated alias for --known-flag. If set, its value takes precedence.")
```
created_at: 2025-05-29T17:59:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -246,11 +251,11 @@
 		os.Exit(1)
 	}
 
-	switch strings.ToUpper(*fullscan) {
+	switch strings.ToUpper(*knownFlag) {
 	case "", "LABEL":
-		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithFullScanFormat(queryplan.FullScanFormatLabel)))
+		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithKnownFlagFormat(queryplan.KnownFlagFormatLabel)))
 	case "RAW":
-		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithFullScanFormat(queryplan.FullScanFormatRaw)))
+		opts = append(opts, plantree.WithQueryPlanOptions(queryplan.WithKnownFlagFormat(queryplan.KnownFlagFormatRaw)))
 	default:
 		fmt.Fprintf(os.Stderr, "Invalid value for -full-scan flag: %s.  Must be 'label' or 'raw'.\n", *fullscan)
path: cmd/rendertree/main.go
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The error message for an invalid flag value currently is `fmt.Fprintf(os.Stderr, "Invalid value for -full-scan flag: %s.  Must be 'label' or 'raw'.\n", *fullscan)`. 

This can be confusing if the user specified `--known-flag` with an invalid value, as `*fullscan` might be empty in that scenario, leading to a message like "Invalid value for -full-scan flag: . Must be 'label' or 'raw'.".

To improve clarity, would it be better to refer to `--known-flag` (as it's the primary flag now) and use `*knownFlag` for the invalid value in the message? This would make the error message consistent with the flag being processed by the switch statement. Note that I am unable to approve this pull request.

```suggestion
fmt.Fprintf(os.Stderr, "Invalid value for --known-flag (or its alias --full-scan): %s.  Must be 'label' or 'raw'.\n", *knownFlag)
```
created_at: 2025-05-29T17:59:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,21 +216,21 @@ func NodeTitle(node *sppb.PlanNode, opts ...Option) string {
 			if o.targetMetadataFormat != TargetMetadataFormatRaw {
 				continue
 			}
-		case "Full scan":
-			if o.fullScanFormat != FullScanFormatRaw && v.GetStringValue() == "true" {
-				needFullscanToFront = true
-				continue
+		}
+
+		if o.knownFlagFormat != KnownFlagFormatRaw && slices.Contains([]string{"Full scan", "split_ranges_aligned"}, k) {
path: queryplan/queryplan.go
user: login: gemini-code-assist[bot]
body: ![medium](https://img.shields.io/badge/priority-medium-yellow)

The list of known flag keys (`[]string{"Full scan", "split_ranges_aligned"}`) is currently defined inline within the `NodeTitle` function. 

For better maintainability and readability, especially if this list might grow or be referenced elsewhere, have you considered defining it as a package-level unexported constant or variable? For example:

```go
var knownBooleanFlagKeys = []string{"Full scan", "split_ranges_aligned"}
// ...
if o.knownFlagFormat != KnownFlagFormatRaw && slices.Contains(knownBooleanFlagKeys, k) {
```
This would make it easier to manage these keys. Note that I am unable to approve this pull request.

```suggestion
if o.knownFlagFormat != KnownFlagFormatRaw && slices.Contains(knownBooleanFlagKeys, k) {
```
created_at: 2025-05-29T17:59:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: apstndb
created_at: 2025-05-29T18:13:22Z
body: /gemini review
author_association: OWNER
user: login: apstndb
created_at: 2025-05-29T18:14:44Z
body: /gemini summary
author_association: OWNER
user: login: gemini-code-assist[bot]
created_at: 2025-05-29T18:15:08Z
body: ## Summary of Changes
Hello! Gemini here, providing a summary of this pull request to help everyone get up to speed quickly.

This PR introduces a new command-line flag `--known-flag` to the `rendertree` tool. The purpose is to generalize the formatting of specific known boolean metadata fields in the query plan output, extending beyond just the "Full scan" field. The existing `--full-scan` flag is deprecated and now acts as an alias for `--known-flag`.

The new `--known-flag` supports two formats: `LABEL` (the new default) and `RAW`. In `LABEL` format, known boolean flags like "Full scan" and "split_ranges_aligned" are printed as key-only labels if their value is `true`, and omitted if `false`. In `RAW` format, they are printed as `key:value` pairs regardless of the value, similar to other metadata fields.

This change also involves refactoring the internal `queryplan` package to handle this new generalized formatting option.

### Highlights
* **New `--known-flag`**: Introduces the `--known-flag` command-line option in `cmd/rendertree` to control the formatting of specific known boolean metadata fields in the query plan output.
* **Deprecated `--full-scan`**: The existing `--full-scan` flag is now deprecated and serves as an alias for `--known-flag`. Logic is added to handle mutual exclusivity if both flags are provided.
* **Generalized Boolean Flag Formatting**: Implements a new formatting logic for known boolean flags (`Full scan`, `split_ranges_aligned`). The `LABEL` format (default) prints the key only if the value is `true`, while the `RAW` format prints `key:value`.
* **Internal Refactoring**: Refactors the `queryplan` package to replace `FullScanFormat` with `KnownFlagFormat` and update the `NodeTitle` function to handle multiple known boolean flags based on the selected format.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **cmd/rendertree/main.go**
  * Added the `--known-flag` command-line flag (line 199).
  * Modified `--full-scan` flag to be a deprecated alias for `--known-flag` (line 198).
  * Added logic to check for mutual exclusivity between `--full-scan` and `--known-flag` and handle the alias (lines 206-213).
  * Updated the switch statement to process the value of `*knownFlag` instead of `*fullscan` (line 259).
  * Updated the call to `plantree.WithQueryPlanOptions` to use `queryplan.WithKnownFlagFormat` (lines 261, 263).
* **cmd/rendertree/main_test.go**
  * Updated expected output strings in several test cases to reflect the new default `LABEL` format, which omits `false` boolean flags like `split_ranges_aligned: false` (lines 57, 83, 109, 151, 183).
  * Updated test setup to use `queryplan.WithKnownFlagFormat` instead of `queryplan.WithFullScanFormat` (line 212).
* **plantree/plantree.go**
  * Reordered import statements (lines 12-13).
* **queryplan/queryplan.go**
  * Added `slices` import (line 6).
  * Renamed the `fullScanFormat` field in the `option` struct to `knownFlagFormat` (line 95).
  * Renamed the `FullScanFormat` type to `KnownFlagFormat` and added `FullScanFormat` as a type alias for `KnownFlagFormat` (lines 121-122).
  * Renamed constants `FullScanFormatRaw` and `FullScanFormatLabel` to `KnownFlagFormatRaw` and `KnownFlagFormatLabel` respectively, and added deprecated aliases for the old names (lines 124-135).
  * Renamed the `WithFullScanFormat` function to `WithKnownFlagFormat` and added a deprecated alias `WithFullScanFormat` (lines 150-159).
  * Added a package-level variable `knownBooleanFlagKeys` listing the keys for known boolean flags (line 167).
  * Modified the `NodeTitle` function to iterate through metadata fields, check if a key is in `knownBooleanFlagKeys`, and format boolean flags based on `o.knownFlagFormat`, separating them into `labels` and `fields` slices (lines 194-230).
  * Removed the special handling logic for the "Full scan" key (removed lines 206-210).
  * Sorted both `labels` and `fields` slices before joining (lines 232-233).
  * Updated the final string join to concatenate `labels` and `fields` (line 235).

</details>

### Activity
* The author (@apstndb) requested a review from Gemini.
* The author (@apstndb) requested a summary from Gemini.
* Gemini provided review comments suggesting improvements to the help text for `--full-scan` (comment 2114477587).
* Gemini provided review comments suggesting improvements to the error message for invalid flag values (comment 2114477590).
* Gemini provided review comments suggesting defining the list of known flag keys as a package-level variable (comment 2114477592).

author_association: CONTRIBUTOR