Skip to content

Fix position tracking across ratio, IS NULL, GROUP BY, ALTER and dictionary nodes#283

Merged
git-hulk merged 1 commit into
masterfrom
fix/position-tracking
Jul 5, 2026
Merged

Fix position tracking across ratio, IS NULL, GROUP BY, ALTER and dictionary nodes#283
git-hulk merged 1 commit into
masterfrom
fix/position-tracking

Conversation

@git-hulk

@git-hulk git-hulk commented Jul 5, 2026

Copy link
Copy Markdown
Member

Summary

A cluster of wrong-span bugs, all instances of the same root pattern: positions stamped from caller-supplied pos parameters, or read from the stream after the relevant token was already consumed.

Node Before After
SAMPLE 1/2 denominator NumPos = numerator's pos (overlapping spans) starts at its own token
SELECT ... WITH TOTALS statementEnd = position of WITH end of TOTALS
GroupByClause.GroupByEnd start of the next token (span swallows trailing whitespace) end of the clause's last token (expr, ALL, or WITH CUBE/ROLLUP/TOTALS)
ALTER TABLE t DETACH ... clause Pos pointed at PARTITION (keyword consumed before p.Pos(), unlike all 12 sibling branches) points at DETACH
a IS [NOT] NULL IsPos = next token after NULL; End() = operand end (span excluded IS NULL entirely) Pos() = operand start, IsPos = the IS keyword, End() = new NullEnd
DictionaryAttribute.End() NamePos + len(\"IS_OBJECT_ID\") — name start plus a keyword length, landing mid-token new AttrEnd tracks the real end
AlterTableDropPartition.End() computed a.Settings.End() and discarded the result (missing return) includes the SETTINGS clause

Verification

While auditing I also re-checked two suspected sites that turned out to be correct (no change): parseProjectionSelect's RightParenPos and SYSTEM ... REPLICATED SENDS' StatementEnd both capture the token before consuming it.

AST impact

New fields: IsNullExpr.NullEnd, IsNotNullExpr.NullEnd, DictionaryAttribute.AttrEnd; IsNullExpr.Pos() now starts at the operand (consistent with IsNotNullExpr). Golden JSON diffs are exclusively position-field values plus these fields; no format output changed. Exact span assertions live in the new position_test.go.

🤖 Generated with Claude Code

…ionary nodes

Positions are stamped from caller-supplied `pos` parameters or read
after the relevant token was already consumed, producing wrong or
overlapping spans in several nodes:

- parseRatioExpr passed the numerator's pos to the denominator too, so
  `SAMPLE 1/2` produced two literals with the same NumPos and
  overlapping spans. The denominator now starts at its own token.
- SELECT ... WITH TOTALS set statementEnd to the position of WITH,
  captured before consuming it. It now ends at the TOTALS token.
- GroupByClause.GroupByEnd was set to p.Pos() after the clause - the
  START of the next token - so the span swallowed trailing whitespace.
  It now ends at the last token of the clause (expression, ALL, or the
  WITH CUBE/ROLLUP/TOTALS keyword).
- The ALTER TABLE ... DETACH branch consumed the keyword before taking
  p.Pos(), unlike all 12 sibling branches, so the clause's Pos pointed
  at PARTITION. It now points at DETACH.
- IsNullExpr/IsNotNullExpr stamped IsPos with the position AFTER the
  consumed NULL (the next token's start) and their End() stopped at the
  operand, excluding "IS [NOT] NULL" entirely. IsPos is now the IS
  keyword, a new NullEnd field marks the end of NULL, Pos() starts at
  the operand and End() returns NullEnd.
- DictionaryAttribute.End() returned NamePos + len("IS_OBJECT_ID")
  and friends - the attribute NAME's start plus a keyword length,
  landing mid-token for any realistic name/type. A new AttrEnd field
  tracks the real end of the last property.
- AlterTableDropPartition.End() computed a.Settings.End() and discarded
  the result (missing return), excluding the SETTINGS clause from the
  span.

Golden JSON diffs are exclusively position-field values plus the two
new fields (NullEnd, AttrEnd); no format output changed. Span
assertions live in the new position_test.go.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@git-hulk git-hulk merged commit 4feeec5 into master Jul 5, 2026
4 checks passed
@git-hulk git-hulk deleted the fix/position-tracking branch July 5, 2026 03:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 154087443d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread parser/parser_query.go
return nil, err
}
if expr != nil {
groupByEnd = expr.End()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the parser position after GROUP BY aggregate params

When parsing GROUP BY CUBE(...), ROLLUP(...), or GROUPING SETS(...) with no trailing WITH modifier, expr is a ParamExprList, whose End() is the position of the closing ) rather than the byte after it. Assigning GroupByEnd from expr.End() makes both GroupByClause.End() and the enclosing SelectQuery.End() one byte short (for example, SELECT a FROM t GROUP BY CUBE(a) reports 31 instead of len(sql)==32), so these supported GROUP BY forms still exclude their final token from the span.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant