Fixed Issue #3328 - #3352
Fixed Issue #3328#3352Hydrocharged wants to merge 1 commit into
Conversation
|
SummaryThe run covered generated-column metadata across ordinary and generated fields, default and nullability handling, expression grouping and normalization, and consistency across client access paths. It included normal usage and a focused edge case involving quoted punctuation in an expression; the overall behavior was healthy aside from a minor metadata-formatting issue. Safe to merge — the only PR-attributable issue is a minor edge-case defect in how generated-expression metadata is normalized, while generated-column behavior and the broader metadata contract remain intact. The issue affects display of metadata rather than execution, so it is a caveat rather than a merge blocker. Tests run by ItoTip Reply with @itoqa to send us feedback on this test run. |
| @@ -149,6 +149,10 @@ func getRowFromColumn(ctx *sql.Context, curOrdPos int, col *sql.Column, catName, | |||
| datetimePrecision := getDatetimePrecision(col.Type) | |||
|
|
|||
| columnDefault := information_schema.GetColumnDefault(ctx, col.Default) | |||
There was a problem hiding this comment.
Quoted parentheses keep extra expression wrappers
What failed: The generated column is correctly marked as ALWAYS and the quoted closing parenthesis is preserved, but the metadata reports ((("label" || ')'))) instead of the normalized expression "label" || ')'.
Impact · Steps · Stub / mock · Analysis · Why this is likely a bug
- Severity: Minor
- Impact: Users and tools that inspect generated-column metadata may see extra parentheses around expressions containing a quoted parenthesis. The generated column still works, and the issue is limited to this metadata display case.
- Steps to Reproduce:
- Create a table with a text column generated from
label || ')'. - Query
information_schema.columnsfor that generated column. - Compare
generation_expressionwith the expression without the redundant outer wrapper.
- Create a table with a text column generated from
- Stub / mock content: No stubs, mocks, or bypasses were applied for this test in the recorded run.
- Code Analysis: The PR changes
getRowFromColumninserver/tables/information_schema/columns_table.goat lines 152-155 to pass every generated expression throughtrimEnclosingParens. The helper at lines 205-210 removes an outer pair only whenparenDepthStaysPositivereturns true. The new balance check at lines 213-228 scans every rune without tracking SQL quote state. For the expression produced by the failing case, the)inside the single-quoted literal is treated as a structural closing parenthesis, causing the depth to become negative and making the helper leave the redundant outer parentheses in place. The PR diff explicitly adds this implementation in the production path, while the test result confirms the resulting metadata value. The smallest practical fix is to make the balance scan ignore parentheses inside quoted SQL strings, or to use the parser's expression structure when deciding which wrappers enclose the full expression. - Why this is likely a bug: The expected metadata format removes only parentheses that wrap the entire generated expression while preserving parentheses that are part of the expression or a quoted value. The reported result violates that contract for a valid SQL expression containing
')', and the production helper explains the mismatch directly: it performs character-level balancing without recognizing quotes. This is a real, reproducible metadata normalization defect rather than a setup issue; a targeted quote-aware balance check fixes the failing case without changing generated-column execution.
Relevant code
server/tables/information_schema/columns_table.go:151-155
columnDefault := information_schema.GetColumnDefault(ctx, col.Default)
var generationExpression any
if col.Generated != nil {
generationExpression = trimEnclosingParens(col.Generated.String())
}server/tables/information_schema/columns_table.go:205-228
func trimEnclosingParens(expr string) string {
for len(expr) > 1 && expr[0] == '(' && expr[len(expr)-1] == ')' && parenDepthStaysPositive(expr[1:len(expr)-1]) {
expr = expr[1 : len(expr)-1]
}
return expr
}
func parenDepthStaysPositive(expr string) bool {
depth := 0
for _, r := range expr {
switch r {
case '(':
depth++
case ')':
depth--
if depth < 0 {
return false
}
}
}
return true
}Evidence Package
Copy prompt for an agent
Ito QA identified the following failure during automated PR testing. Please investigate and propose a fix.
**Minor severity — Quoted parentheses keep extra expression wrappers**
**What failed:** The generated column is correctly marked as ALWAYS and the quoted closing parenthesis is preserved, but the metadata reports `((("label" || ')')))` instead of the normalized expression `"label" || ')'`.
- **Impact:** Users and tools that inspect generated-column metadata may see extra parentheses around expressions containing a quoted parenthesis. The generated column still works, and the issue is limited to this metadata display case.
- **Steps to reproduce:**
1. Create a table with a text column generated from `label || ')'`.
2. Query `information_schema.columns` for that generated column.
3. Compare `generation_expression` with the expression without the redundant outer wrapper.
- **Stub / mock content:** No stubs, mocks, or bypasses were applied for this test in the recorded run.
- **Code analysis:** The PR changes `getRowFromColumn` in `server/tables/information_schema/columns_table.go` at lines 152-155 to pass every generated expression through `trimEnclosingParens`. The helper at lines 205-210 removes an outer pair only when `parenDepthStaysPositive` returns true. The new balance check at lines 213-228 scans every rune without tracking SQL quote state. For the expression produced by the failing case, the `)` inside the single-quoted literal is treated as a structural closing parenthesis, causing the depth to become negative and making the helper leave the redundant outer parentheses in place. The PR diff explicitly adds this implementation in the production path, while the test result confirms the resulting metadata value. The smallest practical fix is to make the balance scan ignore parentheses inside quoted SQL strings, or to use the parser's expression structure when deciding which wrappers enclose the full expression.
- **Why this is likely a bug:** The expected metadata format removes only parentheses that wrap the entire generated expression while preserving parentheses that are part of the expression or a quoted value. The reported result violates that contract for a valid SQL expression containing `')'`, and the production helper explains the mismatch directly: it performs character-level balancing without recognizing quotes. This is a real, reproducible metadata normalization defect rather than a setup issue; a targeted quote-aware balance check fixes the failing case without changing generated-column execution.
**Relevant code:**
`server/tables/information_schema/columns_table.go:151-155`
~~~go
columnDefault := information_schema.GetColumnDefault(ctx, col.Default)
var generationExpression any
if col.Generated != nil {
generationExpression = trimEnclosingParens(col.Generated.String())
}
~~~
`server/tables/information_schema/columns_table.go:205-228`
~~~go
func trimEnclosingParens(expr string) string {
for len(expr) > 1 && expr[0] == '(' && expr[len(expr)-1] == ')' && parenDepthStaysPositive(expr[1:len(expr)-1]) {
expr = expr[1 : len(expr)-1]
}
return expr
}
func parenDepthStaysPositive(expr string) bool {
depth := 0
for _, r := range expr {
switch r {
case '(':
depth++
case ')':
depth--
if depth < 0 {
return false
}
}
}
return true
}
~~~
|
|
@Hydrocharged DOLT
|
zachmu
left a comment
There was a problem hiding this comment.
Change looks fine, see comments.
| }, | ||
| }, | ||
| { | ||
| Name: "Issue #3328: information_schema.columns.generation_expression", |
There was a problem hiding this comment.
Put this in information_schema test or similar
| } | ||
|
|
||
| // trimEnclosingParens removes every pair of parentheses that wraps the entire expression. | ||
| func trimEnclosingParens(expr string) string { |
There was a problem hiding this comment.
Might need to change after changes further down the stack. We don't really enforce the right level of parens in our serialized default / generated column expressions, it's been very tricky to get right. All we really care about is having at least one enclosing pair.
| }, | ||
| { | ||
| Name: "Issue #3328: information_schema.columns.generation_expression", | ||
| SetUpScript: []string{ |
There was a problem hiding this comment.
Does Dolt have this same bug? Please confirm and file if so.

Fixes #3328.
Stacked on #3351.