github.com/Ali-iotechsys/sqlboiler/v4@v4.0.0-20221208124957-6aec9a5f1f71/queries/reflect.go (about) 1 package queries 2 3 import ( 4 "bytes" 5 "context" 6 "database/sql" 7 "database/sql/driver" 8 "fmt" 9 "reflect" 10 "strconv" 11 "strings" 12 "sync" 13 "time" 14 "unicode" 15 16 "github.com/friendsofgo/errors" 17 "github.com/volatiletech/sqlboiler/v4/boil" 18 "github.com/volatiletech/strmangle" 19 ) 20 21 // Identifies what kind of object we're binding to 22 type bindKind int 23 24 const ( 25 kindStruct bindKind = iota 26 kindSliceStruct 27 kindPtrSliceStruct 28 ) 29 30 const ( 31 loadMethodPrefix = "Load" 32 relationshipStructName = "R" 33 loaderStructName = "L" 34 sentinel = uint64(255) 35 ) 36 37 // BindP executes the query and inserts the 38 // result into the passed in object pointer. 39 // It panics on error. 40 // Also see documentation for Bind() and Query.Bind() 41 func (q *Query) BindP(ctx context.Context, exec boil.Executor, obj interface{}) { 42 if err := q.Bind(ctx, exec, obj); err != nil { 43 panic(boil.WrapErr(err)) 44 } 45 } 46 47 // BindG executes the query and inserts 48 // the result into the passed in object pointer. 49 // It uses the global executor. 50 // Also see documentation for Bind() and Query.Bind() 51 func (q *Query) BindG(ctx context.Context, obj interface{}) error { 52 return q.Bind(ctx, boil.GetDB(), obj) 53 } 54 55 // Bind inserts the rows into the passed in object pointer, because the caller 56 // owns the rows it is imperative to note that the caller MUST both close the 57 // rows and check for errors on the rows. 58 // 59 // If you neglect closing the rows your application may have a memory leak 60 // if the rows are not implicitly closed by iteration alone. 61 // If you neglect checking the rows.Err silent failures may occur in your 62 // application. 63 // 64 // Valid types to bind to are: *Struct, []*Struct, and []Struct. Keep in mind 65 // if you use []Struct that Bind will be doing copy-by-value as a method 66 // of keeping heap memory usage low which means if your Struct contains 67 // reference types/pointers you will see incorrect results, do not use 68 // []Struct with a Struct with reference types. 69 // 70 // Bind rules: 71 // - Struct tags control bind, in the form of: `boil:"name,bind"` 72 // - If "name" is omitted the sql column names that come back are TitleCased 73 // and matched against the field name. 74 // - If the "name" part of the struct tag is specified, the given name will 75 // be used instead of the struct field name for binding. 76 // - If the "name" of the struct tag is "-", this field will not be bound to. 77 // - If the ",bind" option is specified on a struct field and that field 78 // is a struct itself, it will be recursed into to look for fields for 79 // binding. 80 // - If one or more boil struct tags are duplicated and there are multiple 81 // matching columns for those tags the behaviour of Bind will be undefined 82 // for those fields with duplicated struct tags. 83 // 84 // Example usage: 85 // 86 // type JoinStruct struct { 87 // // User1 can have it's struct fields bound to since it specifies 88 // // ,bind in the struct tag, it will look specifically for 89 // // fields that are prefixed with "user." returning from the query. 90 // // For example "user.id" column name will bind to User1.ID 91 // User1 *models.User `boil:"user,bind"` 92 // // User2 will follow the same rules as noted above except it will use 93 // // "friend." as the prefix it's looking for. 94 // User2 *models.User `boil:"friend,bind"` 95 // // RandomData will not be recursed into to look for fields to 96 // // bind and will not be bound to because of the - for the name. 97 // RandomData myStruct `boil:"-"` 98 // // Date will not be recursed into to look for fields to bind because 99 // // it does not specify ,bind in the struct tag. But it can be bound to 100 // // as it does not specify a - for the name. 101 // Date time.Time 102 // } 103 // 104 // models.Users( 105 // qm.InnerJoin("users as friend on users.friend_id = friend.id") 106 // ).Bind(&joinStruct) 107 // 108 // For custom objects that want to use eager loading, please see the 109 // loadRelationships function. 110 func Bind(rows *sql.Rows, obj interface{}) error { 111 structType, sliceType, singular, err := bindChecks(obj) 112 if err != nil { 113 return err 114 } 115 116 return bind(rows, obj, structType, sliceType, singular) 117 } 118 119 // Bind executes the query and inserts the 120 // result into the passed in object pointer. 121 // 122 // If Context is non-nil it will upgrade the 123 // Executor to a ContextExecutor and query with the passed context. 124 // If Context is non-nil, any eager loading that's done must also 125 // be using load* methods that support context as the first parameter. 126 // 127 // Also see documentation for Bind() 128 func (q *Query) Bind(ctx context.Context, exec boil.Executor, obj interface{}) error { 129 structType, sliceType, bkind, err := bindChecks(obj) 130 if err != nil { 131 return err 132 } 133 134 var rows *sql.Rows 135 if ctx != nil { 136 rows, err = q.QueryContext(ctx, exec.(boil.ContextExecutor)) 137 } else { 138 rows, err = q.Query(exec) 139 } 140 if err != nil { 141 return errors.Wrap(err, "bind failed to execute query") 142 } 143 if err = bind(rows, obj, structType, sliceType, bkind); err != nil { 144 if innerErr := rows.Close(); innerErr != nil { 145 return errors.Wrapf(err, "error on rows.Close after bind error: %+v", innerErr) 146 } 147 148 return err 149 } 150 if err = rows.Close(); err != nil { 151 return errors.Wrap(err, "failed to clean up rows in bind") 152 } 153 if err = rows.Err(); err != nil { 154 return errors.Wrap(err, "error from rows in bind") 155 } 156 157 if len(q.load) != 0 { 158 return eagerLoad(ctx, exec, q.load, q.loadMods, obj, bkind) 159 } 160 161 return nil 162 } 163 164 // bindChecks resolves information about the bind target, and errors if it's not an object 165 // we can bind to. 166 func bindChecks(obj interface{}) (structType reflect.Type, sliceType reflect.Type, bkind bindKind, err error) { 167 typ := reflect.TypeOf(obj) 168 kind := typ.Kind() 169 170 setErr := func() { 171 err = errors.Errorf("obj type should be *Type, *[]Type, or *[]*Type but was %q", reflect.TypeOf(obj).String()) 172 } 173 174 for i := 0; ; i++ { 175 switch i { 176 case 0: 177 if kind != reflect.Ptr { 178 setErr() 179 return 180 } 181 case 1: 182 switch kind { 183 case reflect.Struct: 184 structType = typ 185 bkind = kindStruct 186 return 187 case reflect.Slice: 188 sliceType = typ 189 default: 190 setErr() 191 return 192 } 193 case 2: 194 switch kind { 195 case reflect.Struct: 196 structType = typ 197 bkind = kindSliceStruct 198 return 199 case reflect.Ptr: 200 default: 201 setErr() 202 return 203 } 204 case 3: 205 if kind != reflect.Struct { 206 setErr() 207 return 208 } 209 structType = typ 210 bkind = kindPtrSliceStruct 211 return 212 } 213 214 typ = typ.Elem() 215 kind = typ.Kind() 216 } 217 } 218 219 func bind(rows *sql.Rows, obj interface{}, structType, sliceType reflect.Type, bkind bindKind) error { 220 cols, err := rows.Columns() 221 if err != nil { 222 return errors.Wrap(err, "bind failed to get column names") 223 } 224 225 var ptrSlice reflect.Value 226 switch bkind { 227 case kindSliceStruct, kindPtrSliceStruct: 228 ptrSlice = reflect.Indirect(reflect.ValueOf(obj)) 229 } 230 231 mapping, err := getMappingCache(structType).mapping(cols) 232 if err != nil { 233 return err 234 } 235 236 var oneStruct reflect.Value 237 if bkind == kindSliceStruct { 238 oneStruct = reflect.Indirect(reflect.New(structType)) 239 } 240 241 foundOne := false 242 Rows: 243 for rows.Next() { 244 foundOne = true 245 var newStruct reflect.Value 246 var pointers []interface{} 247 248 switch bkind { 249 case kindStruct: 250 pointers = PtrsFromMapping(reflect.Indirect(reflect.ValueOf(obj)), mapping) 251 case kindSliceStruct: 252 pointers = PtrsFromMapping(oneStruct, mapping) 253 case kindPtrSliceStruct: 254 newStruct = reflect.New(structType) 255 pointers = PtrsFromMapping(reflect.Indirect(newStruct), mapping) 256 } 257 if err != nil { 258 return err 259 } 260 261 if err := rows.Scan(pointers...); err != nil { 262 return errors.Wrap(err, "failed to bind pointers to obj") 263 } 264 265 switch bkind { 266 case kindStruct: 267 break Rows 268 case kindSliceStruct: 269 ptrSlice.Set(reflect.Append(ptrSlice, oneStruct)) 270 case kindPtrSliceStruct: 271 ptrSlice.Set(reflect.Append(ptrSlice, newStruct)) 272 } 273 } 274 275 if bkind == kindStruct && !foundOne { 276 return sql.ErrNoRows 277 } 278 279 return nil 280 } 281 282 // BindMapping creates a mapping that helps look up the pointer for the 283 // column given. 284 func BindMapping(typ reflect.Type, mapping map[string]uint64, cols []string) ([]uint64, error) { 285 ptrs := make([]uint64, len(cols)) 286 287 ColLoop: 288 for i, c := range cols { 289 ptrMap, ok := mapping[c] 290 if ok { 291 ptrs[i] = ptrMap 292 continue 293 } 294 295 suffix := "." + c 296 for maybeMatch, mapping := range mapping { 297 if strings.HasSuffix(maybeMatch, suffix) { 298 ptrs[i] = mapping 299 continue ColLoop 300 } 301 } 302 // if c doesn't exist in the model, the pointer will be the zero value in the ptrs array and it's value will be thrown away 303 continue 304 } 305 306 return ptrs, nil 307 } 308 309 // PtrsFromMapping expects to be passed an addressable struct and a mapping 310 // of where to find things. It pulls the pointers out referred to by the mapping. 311 func PtrsFromMapping(val reflect.Value, mapping []uint64) []interface{} { 312 ptrs := make([]interface{}, len(mapping)) 313 for i, m := range mapping { 314 ptrs[i] = ptrFromMapping(val, m, true).Interface() 315 } 316 return ptrs 317 } 318 319 // ValuesFromMapping expects to be passed an addressable struct and a mapping 320 // of where to find things. It pulls the pointers out referred to by the mapping. 321 func ValuesFromMapping(val reflect.Value, mapping []uint64) []interface{} { 322 ptrs := make([]interface{}, len(mapping)) 323 for i, m := range mapping { 324 ptrs[i] = ptrFromMapping(val, m, false).Interface() 325 } 326 return ptrs 327 } 328 329 // ptrFromMapping expects to be passed an addressable struct that it's looking 330 // for things on. 331 func ptrFromMapping(val reflect.Value, mapping uint64, addressOf bool) reflect.Value { 332 if mapping == 0 { 333 var ignored interface{} 334 return reflect.ValueOf(&ignored) 335 } 336 for i := 0; i < 8; i++ { 337 v := (mapping >> uint(i*8)) & sentinel 338 339 if v == sentinel { 340 if addressOf && val.Kind() != reflect.Ptr { 341 return val.Addr() 342 } else if !addressOf && val.Kind() == reflect.Ptr { 343 return reflect.Indirect(val) 344 } 345 return val 346 } 347 348 val = val.Field(int(v)) 349 if val.Kind() == reflect.Ptr { 350 val = reflect.Indirect(val) 351 } 352 } 353 354 panic("could not find pointer from mapping") 355 } 356 357 // MakeStructMapping creates a map of the struct to be able to quickly look 358 // up its pointers and values by name. 359 func MakeStructMapping(typ reflect.Type) map[string]uint64 { 360 fieldMaps := make(map[string]uint64) 361 makeStructMappingHelper(typ, "", 0, 0, fieldMaps) 362 return fieldMaps 363 } 364 365 func makeStructMappingHelper(typ reflect.Type, prefix string, current uint64, depth uint, fieldMaps map[string]uint64) { 366 if typ.Kind() == reflect.Ptr { 367 typ = typ.Elem() 368 } 369 370 n := typ.NumField() 371 for i := 0; i < n; i++ { 372 f := typ.Field(i) 373 374 tag, recurse := getBoilTag(f) 375 if len(tag) == 0 { 376 tag = unTitleCase(f.Name) 377 } else if tag[0] == '-' { 378 continue 379 } 380 381 if len(prefix) != 0 { 382 tag = fmt.Sprintf("%s.%s", prefix, tag) 383 } 384 385 if recurse { 386 makeStructMappingHelper(f.Type, tag, current|uint64(i)<<depth, depth+8, fieldMaps) 387 continue 388 } 389 390 fieldMaps[tag] = current | (sentinel << (depth + 8)) | (uint64(i) << depth) 391 } 392 } 393 394 func getBoilTag(field reflect.StructField) (name string, recurse bool) { 395 tag := field.Tag.Get("boil") 396 397 if len(tag) == 0 { 398 return "", false 399 } 400 401 ind := strings.IndexByte(tag, ',') 402 if ind == -1 { 403 return tag, false 404 } else if ind == 0 { 405 return "", true 406 } 407 408 nameFragment := tag[:ind] 409 return nameFragment, true 410 } 411 412 var ( 413 mappingCachesMu sync.Mutex 414 mappingCaches = make(map[reflect.Type]*mappingCache) 415 ) 416 417 func getMappingCache(typ reflect.Type) *mappingCache { 418 mappingCachesMu.Lock() 419 defer mappingCachesMu.Unlock() 420 421 cache := mappingCaches[typ] 422 if cache != nil { 423 return cache 424 } 425 426 cache = newMappingCache(typ) 427 mappingCaches[typ] = cache 428 429 return cache 430 } 431 432 type mappingCache struct { 433 typ reflect.Type 434 435 mu sync.Mutex 436 structMap map[string]uint64 437 colMappings map[string][]uint64 438 } 439 440 func newMappingCache(typ reflect.Type) *mappingCache { 441 return &mappingCache{ 442 typ: typ, 443 structMap: MakeStructMapping(typ), 444 colMappings: make(map[string][]uint64), 445 } 446 } 447 448 func (b *mappingCache) mapping(cols []string) ([]uint64, error) { 449 buf := strmangle.GetBuffer() 450 defer strmangle.PutBuffer(buf) 451 452 for _, s := range cols { 453 buf.WriteString(s) 454 buf.WriteByte(0) 455 } 456 457 key := buf.Bytes() 458 459 b.mu.Lock() 460 defer b.mu.Unlock() 461 462 mapping := b.colMappings[string(key)] 463 if mapping != nil { 464 return mapping, nil 465 } 466 467 mapping, err := BindMapping(b.typ, b.structMap, cols) 468 if err != nil { 469 return nil, err 470 } 471 472 b.colMappings[string(key)] = mapping 473 474 return mapping, nil 475 } 476 477 // Equal is different to reflect.DeepEqual in that it's both less efficient 478 // less magical, and dosen't concern itself with a wide variety of types that could 479 // be present but it does use the driver.Valuer interface since many types that will 480 // go through database things will use these. 481 // 482 // We're focused on basic types + []byte. Since we're really only interested in things 483 // that are typically used for primary keys in a database. 484 // 485 // Choosing not to use the DefaultParameterConverter here because sqlboiler doesn't generate 486 // pointer columns. 487 func Equal(a, b interface{}) bool { 488 if (a == nil && b != nil) || (a != nil && b == nil) { 489 return false 490 } 491 492 // Here we make a fast-path for bytes, because it's the most likely thing 493 // this method will be called with. 494 if ab, ok := a.([]byte); ok { 495 if bb, ok := b.([]byte); ok { 496 return bytes.Equal(ab, bb) 497 } 498 } 499 500 var err error 501 // If either is a sql.Scanner, pull the primitive value out before we get into type checking 502 // since we can't compare complex types anyway. 503 if v, ok := a.(driver.Valuer); ok { 504 a, err = v.Value() 505 if err != nil { 506 panic(fmt.Sprintf("while comparing values, although 'a' implemented driver.Valuer, an error occured when calling it: %+v", err)) 507 } 508 } 509 if v, ok := b.(driver.Valuer); ok { 510 b, err = v.Value() 511 if err != nil { 512 panic(fmt.Sprintf("while comparing values, although 'b' implemented driver.Valuer, an error occured when calling it: %+v", err)) 513 } 514 } 515 516 // Do nil checks again, since a Null type could have returned nil 517 if (a == nil && b != nil) || (a != nil && b == nil) { 518 return false 519 } 520 521 // If either is string and another is numeric, try to parse string as numeric 522 if as, ok := a.(string); ok && isNumeric(b) { 523 a = parseNumeric(as, reflect.TypeOf(b)) 524 } 525 if bs, ok := b.(string); ok && isNumeric(a) { 526 b = parseNumeric(bs, reflect.TypeOf(a)) 527 } 528 529 a = upgradeNumericTypes(a) 530 b = upgradeNumericTypes(b) 531 532 if at, bt := reflect.TypeOf(a), reflect.TypeOf(b); at != bt { 533 panic(fmt.Sprintf("primitive type of a (%s) was not the same primitive type as b (%s)", at.String(), bt.String())) 534 } 535 536 switch t := a.(type) { 537 case int64, float64, bool, string: 538 return a == b 539 case []byte: 540 return bytes.Equal(t, b.([]byte)) 541 case time.Time: 542 return t.Equal(b.(time.Time)) 543 } 544 545 return false 546 } 547 548 // isNumeric tests if i is a numeric value. 549 func isNumeric(i interface{}) bool { 550 switch i.(type) { 551 case int, 552 int8, 553 int16, 554 int32, 555 int64, 556 uint, 557 uint8, 558 uint16, 559 uint32, 560 uint64, 561 float32, 562 float64: 563 return true 564 } 565 return false 566 } 567 568 // parseNumeric tries to parse s as t. 569 // t must be a numeric type. 570 func parseNumeric(s string, t reflect.Type) interface{} { 571 var ( 572 res interface{} 573 err error 574 ) 575 switch t.Kind() { 576 case reflect.Int, 577 reflect.Int8, 578 reflect.Int16, 579 reflect.Int32, 580 reflect.Int64: 581 res, err = strconv.ParseInt(s, 0, t.Bits()) 582 case reflect.Uint, 583 reflect.Uint8, 584 reflect.Uint16, 585 reflect.Uint32, 586 reflect.Uint64: 587 res, err = strconv.ParseUint(s, 0, t.Bits()) 588 case reflect.Float32, 589 reflect.Float64: 590 res, err = strconv.ParseFloat(s, t.Bits()) 591 } 592 if err != nil { 593 panic(fmt.Sprintf("tries to parse %q as %s but got error: %+v", s, t.String(), err)) 594 } 595 return res 596 } 597 598 // Assign assigns a value to another using reflection. 599 // Dst must be a pointer. 600 func Assign(dst, src interface{}) { 601 // Fast path for []byte since it's one of the 602 // most frequent other "ids" we'll be assigning. 603 if db, ok := dst.(*[]byte); ok { 604 if sb, ok := src.([]byte); ok { 605 *db = make([]byte, len(sb)) 606 copy(*db, sb) 607 return 608 } 609 } 610 611 scan, isDstScanner := dst.(sql.Scanner) 612 val, isSrcValuer := src.(driver.Valuer) 613 614 switch { 615 case isDstScanner && isSrcValuer: 616 val, err := val.Value() 617 if err != nil { 618 panic(fmt.Sprintf("tried to call value on %T but got err: %+v", src, err)) 619 } 620 621 err = scan.Scan(val) 622 if err != nil { 623 panic(fmt.Sprintf("tried to call Scan on %T with %#v but got err: %+v", dst, val, err)) 624 } 625 626 case isDstScanner && !isSrcValuer: 627 // Compress any lower width integer types 628 src = upgradeNumericTypes(src) 629 630 if err := scan.Scan(src); err != nil { 631 panic(fmt.Sprintf("tried to call Scan on %T with %#v but got err: %+v", dst, src, err)) 632 } 633 634 case !isDstScanner && isSrcValuer: 635 val, err := val.Value() 636 if err != nil { 637 panic(fmt.Sprintf("tried to call value on %T but got err: %+v", src, err)) 638 } 639 640 assignValue(dst, val) 641 642 default: 643 // We should always be comparing primitives with each other with == in templates 644 // so this method should never be called for say: string, string, or int, int 645 panic("this case should have been handled by something other than this method") 646 } 647 } 648 649 func upgradeNumericTypes(i interface{}) interface{} { 650 switch t := i.(type) { 651 case int: 652 return int64(t) 653 case int8: 654 return int64(t) 655 case int16: 656 return int64(t) 657 case int32: 658 return int64(t) 659 case uint: 660 return int64(t) 661 case uint8: 662 return int64(t) 663 case uint16: 664 return int64(t) 665 case uint32: 666 return int64(t) 667 case uint64: 668 return int64(t) 669 case float32: 670 return float64(t) 671 default: 672 return i 673 } 674 } 675 676 // This whole function makes assumptions that whatever type 677 // dst is, will be compatible with whatever came out of the Valuer. 678 // We handle the types that driver.Value could possibly be. 679 func assignValue(dst interface{}, val driver.Value) { 680 dstType := reflect.TypeOf(dst).Elem() 681 dstVal := reflect.ValueOf(dst).Elem() 682 683 if val == nil { 684 dstVal.Set(reflect.Zero(dstType)) 685 return 686 } 687 688 v := reflect.ValueOf(val) 689 690 switch dstType.Kind() { 691 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 692 dstVal.SetInt(v.Int()) 693 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 694 dstVal.SetInt(int64(v.Uint())) 695 case reflect.Bool: 696 dstVal.SetBool(v.Bool()) 697 case reflect.String: 698 dstVal.SetString(v.String()) 699 case reflect.Float32, reflect.Float64: 700 dstVal.SetFloat(v.Float()) 701 case reflect.Slice: 702 // Assume []byte 703 db, sb := dst.(*[]byte), val.([]byte) 704 *db = make([]byte, len(sb)) 705 copy(*db, sb) 706 case reflect.Struct: 707 // Assume time.Time 708 dstVal.Set(v) 709 } 710 } 711 712 // MustTime retrieves a time value from a valuer. 713 func MustTime(val driver.Valuer) time.Time { 714 v, err := val.Value() 715 if err != nil { 716 panic(fmt.Sprintf("attempted to call value on %T to get time but got an error: %+v", val, err)) 717 } 718 719 if v == nil { 720 return time.Time{} 721 } 722 723 return v.(time.Time) 724 } 725 726 // IsValuerNil returns true if the valuer's value is null. 727 func IsValuerNil(val driver.Valuer) bool { 728 v, err := val.Value() 729 if err != nil { 730 panic(fmt.Sprintf("attempted to call value on %T but got an error: %+v", val, err)) 731 } 732 733 return v == nil 734 } 735 736 // IsNil is a more generic version of IsValuerNil, will check to make sure it's 737 // not a valuer first. 738 func IsNil(val interface{}) bool { 739 if val == nil { 740 return true 741 } 742 743 valuer, ok := val.(driver.Valuer) 744 if ok { 745 return IsValuerNil(valuer) 746 } 747 748 return reflect.ValueOf(val).IsNil() 749 } 750 751 // SetScanner attempts to set a scannable value on a scanner. 752 func SetScanner(scanner sql.Scanner, val driver.Value) { 753 if err := scanner.Scan(val); err != nil { 754 panic(fmt.Sprintf("attempted to call Scan on %T with %#v but got an error: %+v", scanner, val, err)) 755 } 756 } 757 758 // These are sorted by size so that the biggest thing 759 // gets replaced first (think guid/id). This list is copied 760 // from strmangle.uppercaseWords and should hopefully be kept 761 // in sync. 762 var specialWordReplacer = strings.NewReplacer( 763 "ASCII", "Ascii", 764 "GUID", "Guid", 765 "JSON", "Json", 766 "UUID", "Uuid", 767 "UTF8", "Utf8", 768 "ACL", "Acl", 769 "API", "Api", 770 "CPU", "Cpu", 771 "EOF", "Eof", 772 "RAM", "Ram", 773 "SLA", "Sla", 774 "UDP", "Udp", 775 "UID", "Uid", 776 "URI", "Uri", 777 "URL", "Url", 778 "ID", "Id", 779 "IP", "Ip", 780 "UI", "Ui", 781 ) 782 783 // unTitleCase attempts to undo a title-cased string. 784 // 785 // DO NOT USE THIS METHOD IF YOU CAN AVOID IT 786 // 787 // Normally this would be easy but we have to deal with uppercased words 788 // of varying lengths. We almost never use this function so it 789 // can be as badly performing as we want. If people don't want to incur 790 // it's cost they should be able to use the `boil` struct tag to avoid it. 791 // 792 // We did not put this in strmangle because we don't want it being part 793 // of any public API as it's loaded with corner cases and sad performance. 794 func unTitleCase(n string) string { 795 if len(n) == 0 { 796 return "" 797 } 798 799 // Make our words no longer special case 800 n = specialWordReplacer.Replace(n) 801 802 buf := strmangle.GetBuffer() 803 804 first := true 805 806 writeIt := func(s string) { 807 if first { 808 first = false 809 } else { 810 buf.WriteByte('_') 811 } 812 buf.WriteString(strings.ToLower(s)) 813 } 814 815 lastUp := true 816 start := 0 817 for i, r := range n { 818 currentUp := unicode.IsUpper(r) 819 isDigit := unicode.IsDigit(r) 820 821 if !isDigit && !lastUp && currentUp { 822 fragment := n[start:i] 823 writeIt(fragment) 824 start = i 825 } 826 827 if !isDigit && lastUp && !currentUp && i-1-start > 1 { 828 fragment := n[start : i-1] 829 writeIt(fragment) 830 start = i - 1 831 } 832 833 lastUp = currentUp 834 } 835 836 remaining := n[start:] 837 if len(remaining) > 0 { 838 writeIt(remaining) 839 } 840 841 ret := buf.String() 842 strmangle.PutBuffer(buf) 843 return ret 844 }