github.com/aacfactory/fns-contrib/databases/sql@v1.2.84/dac/specifications/column.go (about) 1 package specifications 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/aacfactory/errors" 7 "github.com/aacfactory/fns-contrib/databases/sql/dac/orders" 8 "reflect" 9 "strconv" 10 "strings" 11 ) 12 13 const ( 14 columnTag = "column" 15 discardTagValue = "-" 16 ) 17 18 const ( 19 pkColumn = "pk" 20 incrColumn = "incr" 21 jsonColumn = "json" 22 acbColumn = "acb" 23 actColumn = "act" 24 ambColumn = "amb" 25 amtColumn = "amt" 26 adbColumn = "adb" 27 adtColumn = "adt" 28 aolColumn = "aol" 29 virtualColumn = "vc" 30 referenceColumn = "ref" 31 linkColumn = "link" 32 linksColumn = "links" 33 ) 34 35 const ( 36 UnknownVirtualQueryKind VirtualQueryKind = iota 37 BasicVirtualQuery 38 ObjectVirtualQuery 39 ArrayVirtualQuery 40 AggregateVirtualQuery 41 ) 42 43 type VirtualQueryKind int 44 45 const ( 46 Normal ColumnKind = iota // column 47 Pk // column,pk{,incr} 48 Acb // column,acb 49 Act // column,act 50 Amb // column,amb 51 Amt // column,amt 52 Adb // column,adb 53 Adt // column,adt 54 Aol // column,aol 55 Json // column,json 56 Virtual // ident,vc,basic|object|array|aggregate,query|agg_func 57 Reference // column,ref,target_field 58 Link // ident,link,field+target_field 59 Links // column,links,field+target_field,orders:field@desc+field,length:10 60 ) 61 62 type ColumnKind int 63 64 func (kind ColumnKind) String() string { 65 switch kind { 66 case Normal: 67 return "normal" 68 case Pk: 69 return "pk" 70 case Acb: 71 return "acb" 72 case Act: 73 return "act" 74 case Amb: 75 return "amb" 76 case Amt: 77 return "amt" 78 case Adb: 79 return "adb" 80 case Adt: 81 return "adt" 82 case Aol: 83 return "aol" 84 case Json: 85 return "json" 86 case Virtual: 87 return "virtual" 88 case Reference: 89 return "reference" 90 case Link: 91 return "link" 92 case Links: 93 return "links" 94 } 95 return "???" 96 } 97 98 const ( 99 UnknownType ColumnTypeName = iota 100 StringType 101 BoolType 102 IntType 103 FloatType 104 DatetimeType 105 DateType 106 TimeType 107 BytesType 108 ByteType 109 JsonType 110 ScanType 111 MappingType 112 ) 113 114 type ColumnTypeName int 115 116 type ColumnType struct { 117 Name ColumnTypeName 118 Value reflect.Type 119 Mapping *Specification 120 Options []string 121 } 122 123 func (ct *ColumnType) String() string { 124 switch ct.Name { 125 case UnknownType: 126 return "unknown" 127 case StringType: 128 return "string" 129 case BoolType: 130 return "bool" 131 case IntType: 132 return "int" 133 case FloatType: 134 return "float" 135 case DatetimeType: 136 return "datetime" 137 case DateType: 138 return "date" 139 case TimeType: 140 return "time" 141 case ByteType: 142 return "byte" 143 case BytesType: 144 return "bytes" 145 case JsonType: 146 return "json" 147 case ScanType: 148 return "scan" 149 case MappingType: 150 return fmt.Sprintf("mapping(%s, %s)", ct.Mapping.Key, fmt.Sprintf("%+v", ct.Options)) 151 } 152 return "???" 153 } 154 155 // Column 156 // 'column:"{name},{kind},{options}"' 157 type Column struct { 158 FieldIdx []int 159 Field string 160 Name string 161 JsonIdent string 162 Kind ColumnKind 163 Type ColumnType 164 ValueWriter ValueWriter 165 } 166 167 func (column *Column) Incr() bool { 168 if len(column.Type.Options) > 0 { 169 return column.Type.Options[0] == incrColumn 170 } 171 return false 172 } 173 174 func (column *Column) Virtual() (kind VirtualQueryKind, query string, ok bool) { 175 if column.Kind == Virtual { 176 switch column.Type.Options[0] { 177 case "basic": 178 kind = BasicVirtualQuery 179 case "object": 180 kind = ObjectVirtualQuery 181 case "array": 182 kind = ArrayVirtualQuery 183 break 184 case "agg", "aggregate": 185 kind = AggregateVirtualQuery 186 break 187 default: 188 kind = UnknownVirtualQueryKind 189 break 190 } 191 query = column.Type.Options[1] 192 ok = true 193 } 194 return 195 } 196 197 func (column *Column) Reference() (awayField string, mapping *Specification, ok bool) { 198 ok = column.Kind == Reference 199 if ok { 200 awayField = column.Type.Options[0] 201 mapping = column.Type.Mapping 202 } 203 return 204 } 205 206 func (column *Column) Link() (hostField string, awayField string, mapping *Specification, ok bool) { 207 ok = column.Kind == Link 208 if ok { 209 hostField = column.Type.Options[0] 210 awayField = column.Type.Options[1] 211 mapping = column.Type.Mapping 212 } 213 return 214 } 215 216 func (column *Column) Links() (hostField string, awayField string, mapping *Specification, order orders.Orders, length int, ok bool) { 217 ok = column.Kind == Links 218 if ok { 219 hostField = column.Type.Options[0] 220 awayField = column.Type.Options[1] 221 mapping = column.Type.Mapping 222 if optLen := len(column.Type.Options); optLen > 2 { 223 for i := 2; i < optLen; i++ { 224 option := strings.TrimSpace(column.Type.Options[i]) 225 // orders: 226 if idx := strings.Index(strings.ToLower(option), "orders:"); idx == 0 { 227 option = option[7:] 228 items := strings.Split(option, "+") 229 for _, item := range items { 230 item = strings.TrimSpace(item) 231 pos := strings.IndexByte(item, '@') 232 if pos == -1 { 233 order = orders.Asc(item) 234 } else { 235 field := strings.TrimSpace(item[0:pos]) 236 kind := strings.ToLower(strings.TrimSpace(item[pos+1:])) 237 if kind == "desc" { 238 order = orders.Desc(field) 239 } else { 240 order = orders.Asc(field) 241 } 242 } 243 } 244 } 245 // length: 246 if idx := strings.Index(strings.ToLower(option), "length:"); idx == 0 { 247 option = strings.TrimSpace(option[7:]) 248 length, _ = strconv.Atoi(option) 249 } 250 } 251 } 252 } 253 return 254 } 255 256 func (column *Column) Valid() bool { 257 if column.Type.Name == UnknownType { 258 return false 259 } 260 if column.Incr() { 261 return column.Type.Name == IntType 262 } 263 ok := false 264 switch column.Kind { 265 case Acb, Amb, Adb: 266 ok = column.Type.Name == IntType || column.Type.Name == StringType 267 break 268 case Act, Amt, Adt: 269 ok = column.Type.Name == DatetimeType || column.Type.Name == IntType 270 break 271 case Aol: 272 ok = column.Type.Name == IntType 273 break 274 case Reference, Link: 275 ok = column.Type.Value.Kind() == reflect.Struct || 276 (column.Type.Value.Kind() == reflect.Ptr && column.Type.Value.Elem().Kind() == reflect.Struct) 277 break 278 case Links: 279 ok = column.Type.Value.Kind() == reflect.Slice && 280 (column.Type.Value.Elem().Kind() == reflect.Struct || 281 (column.Type.Value.Elem().Kind() == reflect.Ptr && column.Type.Value.Elem().Elem().Kind() == reflect.Struct)) 282 break 283 default: 284 if column.Incr() { 285 ok = column.Type.Name == IntType 286 break 287 } 288 ok = true 289 break 290 } 291 return ok 292 } 293 294 func (column *Column) String() (s string) { 295 kind := column.Kind.String() 296 if column.Kind == Virtual { 297 kind = kind + fmt.Sprintf("(%+v)", column.Type.Options) 298 } else if column.Kind == Json { 299 if column.Type.Value.ConvertibleTo(bytesType) { 300 kind = kind + "(bytes)" 301 } else { 302 kind = kind + fmt.Sprintf("(%s)", column.Type.Value.String()) 303 } 304 } 305 s = fmt.Sprintf( 306 "%s => field:%s, kind: %s, type: %s", 307 column.Name, 308 column.Field, 309 kind, column.Type.String(), 310 ) 311 return 312 } 313 314 func (column *Column) WriteValue(field reflect.Value, value any) (err error) { 315 err = column.ValueWriter.Write(value, field) 316 return 317 } 318 319 func (column *Column) ReadValue(sv reflect.Value) (fv reflect.Value) { 320 for i := len(column.FieldIdx) - 1; i > -1; i-- { 321 sv = sv.Field(column.FieldIdx[i]) 322 } 323 fv = sv 324 return 325 } 326 327 func newColumn(ctx context.Context, rt reflect.StructField, idx []int) (column *Column, err error) { 328 tag, hasTag := rt.Tag.Lookup(columnTag) 329 if !hasTag { 330 return 331 } 332 tag = strings.TrimSpace(tag) 333 if tag == discardTagValue { 334 return 335 } 336 var vw ValueWriter 337 kind := Normal 338 typ := ColumnType{ 339 Name: UnknownType, 340 Value: rt.Type, 341 Mapping: nil, 342 Options: make([]string, 0, 1), 343 } 344 items := strings.Split(tag, ",") 345 346 name := strings.TrimSpace(items[0]) 347 if len(items) > 1 { 348 items = items[1:] 349 kv := strings.ToLower(strings.TrimSpace(items[0])) 350 switch kv { 351 case pkColumn: 352 kind = Pk 353 if len(items) > 1 { 354 if strings.ToLower(items[1]) == incrColumn { 355 switch rt.Type.Kind() { 356 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 357 vw = &IntValue{} 358 break 359 default: 360 err = errors.Warning("sql: type of incr pk column failed must be int64").WithMeta("field", rt.Name) 361 return 362 } 363 typ.Name = IntType 364 typ.Options = append(typ.Options, incrColumn) 365 } 366 } 367 if typ.Name == UnknownType { 368 switch rt.Type.Kind() { 369 case reflect.String: 370 typ.Name = StringType 371 vw = &StringValue{} 372 break 373 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 374 typ.Name = IntType 375 vw = &IntValue{} 376 break 377 default: 378 err = errors.Warning("sql: type of pk column failed must be int64 or string").WithMeta("field", rt.Name) 379 return 380 } 381 } 382 break 383 case acbColumn: 384 kind = Acb 385 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 386 if err != nil { 387 err = errors.Warning("sql: type of acb column failed must be int64 or string").WithCause(err).WithMeta("field", rt.Name) 388 return 389 } 390 if typ.Name != StringType && typ.Name != IntType { 391 err = errors.Warning("sql: type of acb column failed must be int64 or string").WithMeta("field", rt.Name) 392 return 393 } 394 break 395 case actColumn: 396 kind = Act 397 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 398 if err != nil { 399 err = errors.Warning("sql: type of act column failed must be time.Time or int64").WithCause(err).WithMeta("field", rt.Name) 400 return 401 } 402 if typ.Name != DatetimeType && typ.Name != IntType { 403 err = errors.Warning("sql: type of act column failed must be time.Time or int64").WithMeta("field", rt.Name) 404 return 405 } 406 break 407 case ambColumn: 408 kind = Amb 409 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 410 if err != nil { 411 err = errors.Warning("sql: type of amb column failed must be int64 or string").WithCause(err).WithMeta("field", rt.Name) 412 return 413 } 414 if typ.Name != StringType && typ.Name != IntType { 415 err = errors.Warning("sql: type of amb column failed must be int64 or string").WithMeta("field", rt.Name) 416 return 417 } 418 break 419 case amtColumn: 420 kind = Amt 421 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 422 if err != nil { 423 err = errors.Warning("sql: type of amt column failed must be time.Time or int64").WithCause(err).WithMeta("field", rt.Name) 424 return 425 } 426 if typ.Name != DatetimeType && typ.Name != IntType { 427 err = errors.Warning("sql: type of amt column failed must be time.Time or int64").WithMeta("field", rt.Name) 428 return 429 } 430 break 431 case adbColumn: 432 kind = Adb 433 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 434 if err != nil { 435 err = errors.Warning("sql: type of adb column failed must be int64 or string").WithCause(err).WithMeta("field", rt.Name) 436 return 437 } 438 if typ.Name != StringType && typ.Name != IntType { 439 err = errors.Warning("sql: type of adb column failed must be int64 or string").WithMeta("field", rt.Name) 440 return 441 } 442 break 443 case adtColumn: 444 kind = Adt 445 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 446 if err != nil { 447 err = errors.Warning("sql: type of adt column failed must be time.Time or int64").WithCause(err).WithMeta("field", rt.Name) 448 return 449 } 450 if typ.Name != DatetimeType && typ.Name != IntType { 451 err = errors.Warning("sql: type of adt column failed must be time.Time or int64").WithMeta("field", rt.Name) 452 return 453 } 454 break 455 case aolColumn: 456 kind = Aol 457 if rt.Type.Kind() == reflect.Int64 { 458 typ.Name = IntType 459 vw = &IntValue{} 460 } else { 461 err = errors.Warning("sql: type of aol column failed must be int64").WithMeta("field", rt.Name) 462 return 463 } 464 break 465 case incrColumn: 466 if rt.Type.Kind() == reflect.Int64 { 467 typ.Name = IntType 468 vw = &IntValue{} 469 } else { 470 err = errors.Warning("sql: type of incr column failed must be int64").WithMeta("field", rt.Name) 471 return 472 } 473 typ.Options = append(typ.Options, incrColumn) 474 case jsonColumn: 475 kind = Json 476 typ.Name = JsonType 477 vw = &JsonValue{ 478 ValueType: rt.Type, 479 } 480 break 481 case virtualColumn: 482 if len(items) < 3 { 483 err = errors.Warning("sql: scan virtual column failed, kind and query are required").WithMeta("field", rt.Name) 484 return 485 } 486 kind = Virtual 487 vck := strings.ToLower(strings.TrimSpace(items[1])) 488 valid := vck == "basic" || vck == "object" || vck == "array" || vck == "agg" || vck == "aggregate" 489 if !valid { 490 err = errors.Warning("sql: scan virtual column failed, kind is invalid").WithMeta("field", rt.Name) 491 return 492 } 493 typ.Options = append(typ.Options, vck, strings.TrimSpace(items[2])) 494 if vck == "object" || vck == "array" { 495 typ.Name = JsonType 496 vw = &JsonValue{ 497 ValueType: rt.Type, 498 } 499 } else { 500 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 501 if err != nil { 502 err = errors.Warning("sql: scan virtual column failed, kind is invalid").WithCause(err).WithMeta("field", rt.Name) 503 return 504 } 505 } 506 break 507 case referenceColumn: 508 if len(items) < 2 { 509 err = errors.Warning("sql: scan reference column failed, mapping is required").WithMeta("field", rt.Name) 510 return 511 } 512 kind = Reference 513 typ.Options = append(typ.Options, strings.TrimSpace(items[1])) 514 typ.Name = MappingType 515 vw = &MappingValue{ 516 ValueType: rt.Type, 517 } 518 switch rt.Type.Kind() { 519 case reflect.Struct: 520 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type).Interface()) 521 if err != nil { 522 err = errors.Warning("sql: scan reference column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("reference column type must be implement Table")).WithCause(err) 523 return 524 } 525 break 526 case reflect.Ptr: 527 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type.Elem()).Interface()) 528 if err != nil { 529 err = errors.Warning("sql: scan reference column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("reference column type must be implement Table")).WithCause(err) 530 return 531 } 532 break 533 default: 534 err = errors.Warning("sql: scan reference column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("reference column type must be struct or ptr struct")) 535 return 536 } 537 break 538 case linkColumn: 539 if len(items) < 2 { 540 err = errors.Warning("sql: scan link column failed, mapping is required").WithMeta("field", rt.Name) 541 return 542 } 543 544 mr := strings.Split(items[1], "+") 545 if len(mr) != 2 { 546 err = errors.Warning("sql: scan link column failed, mapping is invalid").WithMeta("field", rt.Name) 547 return 548 } 549 550 kind = Link 551 typ.Options = append(typ.Options, strings.TrimSpace(mr[0])) 552 typ.Options = append(typ.Options, strings.TrimSpace(mr[1])) 553 554 typ.Name = MappingType 555 vw = &MappingValue{ 556 ValueType: rt.Type, 557 } 558 switch rt.Type.Kind() { 559 case reflect.Struct: 560 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type).Interface()) 561 if err != nil { 562 err = errors.Warning("sql: scan link column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("link column type must be implement Table")).WithCause(err) 563 return 564 } 565 break 566 case reflect.Ptr: 567 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type.Elem()).Interface()) 568 if err != nil { 569 err = errors.Warning("sql: scan link column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("link column type must be implement Table")).WithCause(err) 570 return 571 } 572 break 573 default: 574 err = errors.Warning("sql: scan link column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("link column type must be struct or ptr struct")) 575 return 576 } 577 break 578 case linksColumn: 579 if len(items) < 2 { 580 err = errors.Warning("sql: scan links column failed, mapping is required").WithMeta("field", rt.Name) 581 return 582 } 583 mr := strings.Split(items[1], "+") 584 if len(mr) != 2 { 585 err = errors.Warning("sql: scan links column failed, mapping is invalid").WithMeta("field", rt.Name) 586 return 587 } 588 589 kind = Links 590 typ.Options = append(typ.Options, strings.TrimSpace(mr[0])) 591 typ.Options = append(typ.Options, strings.TrimSpace(mr[1])) 592 593 if len(items) > 2 { 594 typ.Options = append(typ.Options, items[2:]...) 595 } 596 597 typ.Name = MappingType 598 vw = &MappingValue{ 599 ValueType: rt.Type, 600 } 601 if rt.Type.Kind() != reflect.Slice { 602 err = errors.Warning("sql: scan links column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("links column type must be slice struct or slice ptr struct")) 603 return 604 } 605 switch rt.Type.Elem().Kind() { 606 case reflect.Struct: 607 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type.Elem()).Interface()) 608 if err != nil { 609 err = errors.Warning("sql: scan links column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("links column type must be implement Table")).WithCause(err) 610 return 611 } 612 break 613 case reflect.Ptr: 614 typ.Mapping, err = GetSpecification(ctx, reflect.Zero(rt.Type.Elem().Elem()).Interface()) 615 if err != nil { 616 err = errors.Warning("sql: scan links column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("links column type must be implement Table")).WithCause(err) 617 return 618 } 619 break 620 default: 621 err = errors.Warning("sql: scan links column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("links column type must be struct or ptr struct")) 622 return 623 } 624 break 625 default: 626 err = errors.Warning("sql: unknown column options").WithMeta("field", rt.Name).WithMeta("tag", tag) 627 return 628 } 629 } 630 if typ.Name == UnknownType { 631 if rt.Type.ConvertibleTo(scannerType) && rt.Type.ConvertibleTo(jsonMarshalerType) { 632 vw = &ScanValue{} 633 typ.Name = ScanType 634 } else { 635 vw, typ.Name, err = NewBasicValueWriter(rt.Type) 636 if err != nil { 637 err = errors.Warning("sql: invalid column").WithCause(err).WithMeta("field", rt.Name).WithMeta("tag", tag) 638 return 639 } 640 } 641 } 642 643 // json 644 jsonTag, hasJsonTag := rt.Tag.Lookup(jsonColumn) 645 if hasJsonTag { 646 if idx := strings.IndexByte(jsonTag, ','); idx > 0 { 647 jsonTag = jsonTag[0:idx] 648 } 649 jsonTag = strings.TrimSpace(jsonTag) 650 } else { 651 jsonTag = rt.Name 652 } 653 654 column = &Column{ 655 FieldIdx: idx, 656 Field: rt.Name, 657 Name: name, 658 JsonIdent: jsonTag, 659 Kind: kind, 660 Type: typ, 661 ValueWriter: vw, 662 } 663 664 if !column.Valid() { 665 err = errors.Warning("sql: new column failed").WithMeta("field", rt.Name).WithCause(fmt.Errorf("%v is not supported", typ.Value)) 666 return 667 } 668 return 669 }