Skip to content

Commit 20a7593

Browse files
committed
Handle large number overflow and preserve original source text
- For hex numbers that overflow uint64 (like 0x123456789ABCDEF01), convert to Float64 - For decimal numbers that overflow, try float64 parsing - Preserve original source text in Literal.Source for formatting in CAST expressions - Update explain for negated uint64 values that overflow int64 to output Float64 - Use Source field in formatElementAsString to preserve exact text in array/tuple casts
1 parent 3783bb2 commit 20a7593

11 files changed

Lines changed: 70 additions & 44 deletions

File tree

internal/explain/expressions.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,15 @@ func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, dept
435435
// ClickHouse normalizes -0 to UInt64_0
436436
if val == 0 {
437437
fmt.Fprintf(sb, "%sLiteral UInt64_0\n", indent)
438-
} else {
438+
} else if val <= 9223372036854775808 {
439+
// Value fits in int64 when negated
440+
// Note: -9223372036854775808 is int64 min, so 9223372036854775808 is included
439441
fmt.Fprintf(sb, "%sLiteral Int64_-%d\n", indent, val)
442+
} else {
443+
// Value too large for int64 - output as Float64
444+
f := -float64(val)
445+
s := FormatFloat(f)
446+
fmt.Fprintf(sb, "%sLiteral Float64_%s\n", indent, s)
440447
}
441448
return
442449
}
@@ -657,7 +664,16 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
657664
fmt.Fprintf(sb, "%sLiteral Int64_%d (alias %s)\n", indent, -val, escapeAlias(n.Alias))
658665
return
659666
case uint64:
660-
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, escapeAlias(n.Alias))
667+
if val <= 9223372036854775808 {
668+
// Value fits in int64 when negated
669+
// Note: -9223372036854775808 is int64 min, so 9223372036854775808 is included
670+
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, escapeAlias(n.Alias))
671+
} else {
672+
// Value too large for int64 - output as Float64
673+
f := -float64(val)
674+
s := FormatFloat(f)
675+
fmt.Fprintf(sb, "%sLiteral Float64_%s (alias %s)\n", indent, s, escapeAlias(n.Alias))
676+
}
661677
return
662678
}
663679
case ast.LiteralFloat:

internal/explain/format.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,10 @@ func formatElementAsString(expr ast.Expression) string {
608608
case ast.LiteralInteger:
609609
return fmt.Sprintf("%d", e.Value)
610610
case ast.LiteralFloat:
611+
// Use Source if available (preserves original text for large numbers)
612+
if e.Source != "" {
613+
return e.Source
614+
}
611615
return fmt.Sprintf("%v", e.Value)
612616
case ast.LiteralString:
613617
s := e.Value.(string)

parser/expression.go

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,31 @@ package parser
22

33
import (
44
"math"
5+
"math/big"
56
"strconv"
67
"strings"
78

89
"github.com/sqlc-dev/doubleclick/ast"
910
"github.com/sqlc-dev/doubleclick/token"
1011
)
1112

13+
// parseHexToFloat converts a hex string (with 0x prefix) to float64
14+
// Used for hex numbers that overflow uint64
15+
func parseHexToFloat(s string) (float64, bool) {
16+
if !strings.HasPrefix(strings.ToLower(s), "0x") {
17+
return 0, false
18+
}
19+
hexPart := s[2:]
20+
bi := new(big.Int)
21+
_, ok := bi.SetString(hexPart, 16)
22+
if !ok {
23+
return 0, false
24+
}
25+
f := new(big.Float).SetInt(bi)
26+
result, _ := f.Float64()
27+
return result, true
28+
}
29+
1230
// Operator precedence levels
1331
const (
1432
LOWEST = iota
@@ -984,10 +1002,28 @@ func (p *Parser) parseNumber() ast.Expression {
9841002
// Try unsigned uint64 for large positive numbers
9851003
u, uerr := strconv.ParseUint(value, base, 64)
9861004
if uerr != nil {
987-
// Too large for int64/uint64, store as string with IsBigInt flag
988-
lit.Type = ast.LiteralString
989-
lit.Value = value
990-
lit.IsBigInt = true
1005+
// Too large for int64/uint64, try as float64
1006+
var f float64
1007+
var ok bool
1008+
if isHex {
1009+
// For hex numbers, use parseHexToFloat since strconv.ParseFloat
1010+
// doesn't handle hex integers without 'p' exponent
1011+
f, ok = parseHexToFloat(value)
1012+
} else {
1013+
var ferr error
1014+
f, ferr = strconv.ParseFloat(value, 64)
1015+
ok = ferr == nil
1016+
}
1017+
if !ok {
1018+
// Still can't parse, store as string with IsBigInt flag
1019+
lit.Type = ast.LiteralString
1020+
lit.Value = value
1021+
lit.IsBigInt = true
1022+
} else {
1023+
lit.Type = ast.LiteralFloat
1024+
lit.Value = f
1025+
lit.Source = value // Preserve original source text
1026+
}
9911027
} else {
9921028
lit.Type = ast.LiteralInteger
9931029
lit.Value = u // Store as uint64
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt1": true,
4-
"stmt2": true
5-
}
6-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt9": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"explain_todo":{"stmt8":true}}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt12": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt9": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt54": true,
4-
"stmt55": true
5-
}
6-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt5": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)