Skip to content

Commit b970e9f

Browse files
committed
chore: do not strip type cast
1 parent a1c93ca commit b970e9f

File tree

18 files changed

+182
-184
lines changed

18 files changed

+182
-184
lines changed

internal/diff/table.go

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package diff
22

33
import (
44
"fmt"
5-
"regexp"
65
"sort"
76
"strings"
87

@@ -1104,9 +1103,7 @@ func buildColumnClauses(column *ir.Column, isPartOfAnyPK bool, tableSchema strin
11041103
defaultValue = strings.ReplaceAll(defaultValue, schemaPrefix, "")
11051104
}
11061105

1107-
// Strip type qualifiers from default values
1108-
defaultValue = stripTypeQualifiers(defaultValue)
1109-
1106+
// Type casts are now preserved (from pg_query.Deparse) for canonical representation
11101107
parts = append(parts, fmt.Sprintf("DEFAULT %s", defaultValue))
11111108
}
11121109

@@ -1213,18 +1210,6 @@ func formatColumnDataTypeForCreate(column *ir.Column) string {
12131210
return dataType
12141211
}
12151212

1216-
// stripTypeQualifiers removes PostgreSQL type qualifiers from default values
1217-
func stripTypeQualifiers(defaultValue string) string {
1218-
// Use regex to match any type qualifier pattern (::typename)
1219-
// This handles both built-in types and user-defined types like enums
1220-
re := regexp.MustCompile(`(.*)::[a-zA-Z_][a-zA-Z0-9_\s]*(\[\])?$`)
1221-
matches := re.FindStringSubmatch(defaultValue)
1222-
if len(matches) > 1 {
1223-
return matches[1]
1224-
}
1225-
return defaultValue
1226-
}
1227-
12281213
// indexesStructurallyEqual compares two indexes for structural equality
12291214
// excluding comments and other metadata that don't require index recreation
12301215
func indexesStructurallyEqual(oldIndex, newIndex *ir.Index) bool {

ir/formatter.go

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,23 @@ func (f *postgreSQLFormatter) formatRangeSubselect(subselect *pg_query.RangeSubs
219219
}
220220
}
221221

222+
// formatExpressionWithTextCast formats an expression and adds ::text cast to string literals
223+
// This is used in CASE THEN/ELSE clauses to match PostgreSQL's canonical representation
224+
func (f *postgreSQLFormatter) formatExpressionWithTextCast(expr *pg_query.Node) {
225+
// Check if this is a string constant that needs a ::text cast
226+
if aConst := expr.GetAConst(); aConst != nil {
227+
if aConst.GetSval() != nil {
228+
// Format the string literal with ::text cast
229+
f.formatAConst(aConst)
230+
f.buffer.WriteString("::text")
231+
return
232+
}
233+
}
234+
235+
// For other expressions, format normally
236+
f.formatExpression(expr)
237+
}
238+
222239
// formatExpression formats a general expression
223240
//
224241
// NOTE: Two important expression types for array operations:
@@ -558,14 +575,15 @@ func (f *postgreSQLFormatter) formatCaseExpr(caseExpr *pg_query.CaseExpr) {
558575
f.buffer.WriteString(" WHEN ")
559576
f.formatExpression(when.Expr)
560577
f.buffer.WriteString(" THEN ")
561-
f.formatExpressionStripCast(when.Result)
578+
// Add ::text cast to string literals in THEN clause
579+
f.formatExpressionWithTextCast(when.Result)
562580
}
563581
}
564582

565-
// Format ELSE clause, stripping unnecessary type casts from constants/NULL
583+
// Format ELSE clause - add ::text cast to string literals
566584
if caseExpr.Defresult != nil {
567585
f.buffer.WriteString(" ELSE ")
568-
f.formatExpressionStripCast(caseExpr.Defresult)
586+
f.formatExpressionWithTextCast(caseExpr.Defresult)
569587
}
570588

571589
f.buffer.WriteString(" END")
@@ -647,26 +665,6 @@ func (f *postgreSQLFormatter) formatNullTest(nullTest *pg_query.NullTest) {
647665
}
648666
}
649667

650-
// formatExpressionStripCast formats an expression, stripping unnecessary type casts from constants and NULL
651-
func (f *postgreSQLFormatter) formatExpressionStripCast(expr *pg_query.Node) {
652-
// If this is a TypeCast of a constant or NULL, format just the value without the cast
653-
if typeCast := expr.GetTypeCast(); typeCast != nil {
654-
if typeCast.Arg != nil {
655-
if aConst := typeCast.Arg.GetAConst(); aConst != nil {
656-
// This is a typed constant, format just the constant value
657-
f.formatAConst(aConst)
658-
return
659-
}
660-
// For non-constant args, recursively strip casts
661-
f.formatExpressionStripCast(typeCast.Arg)
662-
return
663-
}
664-
}
665-
666-
// Otherwise, format normally
667-
f.formatExpression(expr)
668-
}
669-
670668
// formatAArrayExpr formats array expressions (ARRAY[...])
671669
func (f *postgreSQLFormatter) formatAArrayExpr(arrayExpr *pg_query.A_ArrayExpr) {
672670
f.buffer.WriteString("ARRAY[")
@@ -682,17 +680,17 @@ func (f *postgreSQLFormatter) formatAArrayExpr(arrayExpr *pg_query.A_ArrayExpr)
682680
// formatArrayAsIN is a helper to format "column IN (values)" syntax
683681
// Used by both formatAExpr and formatScalarArrayOpExpr to convert "= ANY(ARRAY[...])" to "IN (...)"
684682
func (f *postgreSQLFormatter) formatArrayAsIN(leftExpr *pg_query.Node, arrayElements []*pg_query.Node) {
685-
// Format left side (the column/expression)
686-
f.formatExpressionStripCast(leftExpr)
683+
// Format left side (the column/expression) - preserves type casts for canonical representation
684+
f.formatExpression(leftExpr)
687685

688686
f.buffer.WriteString(" IN (")
689687

690-
// Format array elements as comma-separated list, stripping unnecessary type casts
688+
// Format array elements as comma-separated list - preserves type casts for canonical representation
691689
for i, elem := range arrayElements {
692690
if i > 0 {
693691
f.buffer.WriteString(", ")
694692
}
695-
f.formatExpressionStripCast(elem)
693+
f.formatExpression(elem)
696694
}
697695

698696
f.buffer.WriteString(")")

ir/normalize.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -112,28 +112,16 @@ func normalizeDefaultValue(value string) string {
112112
}
113113

114114
// Handle type casting - remove explicit type casts that are semantically equivalent
115-
// Pattern: ''::text -> ''
116-
// Pattern: '{}'::jsonb -> '{}'
115+
// Use regex to properly handle type casts within complex expressions
116+
// Pattern: 'literal'::type -> 'literal' (removes redundant casts from string literals)
117117
if strings.Contains(value, "::") {
118-
// Find the cast and remove it for simple literal values
119-
if strings.HasPrefix(value, "'") {
120-
if idx := strings.Index(value, "'::"); idx != -1 {
121-
// Find the closing quote
122-
if closeIdx := strings.Index(value[1:], "'"); closeIdx != -1 {
123-
literal := value[:closeIdx+2] // Include the closing quote
124-
if literal == "''" || literal == "'{}'" {
125-
value = literal
126-
}
127-
}
128-
}
129-
}
130-
// Pattern: 'G'::schema.type_name -> 'G'
131-
// Pattern: 'G'::type_name -> 'G'
132-
if strings.Contains(value, "'::") {
133-
if idx := strings.Index(value, "'::"); idx != -1 {
134-
value = value[:idx+1]
135-
}
136-
}
118+
// Use regex to match and remove type casts from string literals
119+
// This handles: 'text'::text, 'utc'::text, '{}'::jsonb, '{}'::text[], etc.
120+
// Pattern explanation:
121+
// '([^']*)' - matches a quoted string literal (capturing the content)
122+
// ::[\w.]+(\[\])? - matches ::typename or ::typename[] (including schema.typename and array types)
123+
re := regexp.MustCompile(`'([^']*)'::[\w.]+(\[\])?`)
124+
value = re.ReplaceAllString(value, "'$1'")
137125
}
138126

139127
return value
@@ -891,7 +879,7 @@ func normalizeCheckClause(checkClause string) string {
891879
func normalizeExpressionWithPgQuery(expr string) string {
892880
// Create a dummy SELECT statement with the expression to parse it
893881
dummySQL := fmt.Sprintf("SELECT %s", expr)
894-
882+
895883
parseResult, err := pg_query.Parse(dummySQL)
896884
if err != nil {
897885
// If parsing fails, return empty string to trigger fallback
@@ -935,7 +923,7 @@ func removeRedundantNumericCasts(expr string) string {
935923
re := regexp.MustCompile(pattern)
936924
result = re.ReplaceAllString(result, "$1")
937925
}
938-
926+
939927
return result
940928
}
941929

0 commit comments

Comments
 (0)