normalizeCheckExpr normalizes a CHECK constraint expression AST for comparison mode parameter controls PostgreSQL-specific normalization (IN to ANY conversion)
(expr parser.Expr, mode GeneratorMode)
| 267 | // normalizeCheckExpr normalizes a CHECK constraint expression AST for comparison |
| 268 | // mode parameter controls PostgreSQL-specific normalization (IN to ANY conversion) |
| 269 | func normalizeCheckExpr(expr parser.Expr, mode GeneratorMode) parser.Expr { |
| 270 | if expr == nil { |
| 271 | return nil |
| 272 | } |
| 273 | |
| 274 | switch e := expr.(type) { |
| 275 | case *parser.IntervalExpr: |
| 276 | if mode == GeneratorModePostgres { |
| 277 | return normalizeExpr(e, mode) |
| 278 | } |
| 279 | return expr |
| 280 | case *parser.CastExpr: |
| 281 | // Numeric/money/interval casts fold to a value-independent form, so reuse the |
| 282 | // value-context path; otherwise CHECK keeps its own cast simplification below. |
| 283 | if mode == GeneratorModePostgres && e.Type != nil && isFoldableValueCastTypeName(strings.ToLower(e.Type.Type)) { |
| 284 | return normalizeExpr(e, mode) |
| 285 | } |
| 286 | // Remove certain casts that PostgreSQL simplifies |
| 287 | // - text, character varying: Always removed |
| 288 | // - date, timestamp without time zone: Removed when cast from string literals |
| 289 | // - time, time without time zone: Kept but normalized (PostgreSQL preserves these for precision) |
| 290 | if e.Type != nil { |
| 291 | typeStr := strings.ToLower(e.Type.Type) |
| 292 | |
| 293 | // Always remove text casts |
| 294 | if typeStr == "text" || typeStr == "character varying" { |
| 295 | return normalizeCheckExpr(e.Expr, mode) |
| 296 | } |
| 297 | |
| 298 | normalizedTypeStr := normalizeTypeName(typeStr, mode) |
| 299 | |
| 300 | // Remove date/timestamp casts from string literals |
| 301 | // PostgreSQL simplifies '2020-01-01'::date to '2020-01-01' in CHECK constraints |
| 302 | if normalizedTypeStr == "date" || normalizedTypeStr == "timestamp" { |
| 303 | return normalizeCheckExpr(e.Expr, mode) |
| 304 | } |
| 305 | |
| 306 | // Remove redundant array typecasts on ARRAY constructors |
| 307 | // PostgreSQL normalizes ARRAY['a'::varchar]::text[] to ARRAY['a'::varchar::text] |
| 308 | // by pushing down the array typecast to each element. Since we strip ::text casts |
| 309 | // on elements, we also need to strip ::text[] on the ARRAY constructor itself. |
| 310 | // e.g., ARRAY['a'::varchar]::text[] -> ARRAY['a'::varchar] (after stripping ::text[]) |
| 311 | // ARRAY['a'::varchar::text] -> ARRAY['a'::varchar] (after stripping ::text on element) |
| 312 | // Empty arrays (ARRAY[]) need the typecast or PostgreSQL can't determine the type. |
| 313 | normalizedExpr := normalizeCheckExpr(e.Expr, mode) |
| 314 | if arrayConstructor, isArrayConstructor := normalizedExpr.(*parser.ArrayConstructor); isArrayConstructor { |
| 315 | if strings.HasSuffix(typeStr, "[]") && len(arrayConstructor.Elements) > 0 { |
| 316 | // Non-empty array with array typecast: strip the redundant typecast |
| 317 | return normalizedExpr |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | // For time types, keep the cast but use normalized type name |
| 322 | return &parser.CastExpr{ |
| 323 | Expr: normalizedExpr, |
| 324 | Type: &parser.ConvertType{Type: normalizedTypeStr}, |
| 325 | } |
| 326 | } |