github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/model/store.go (about) 1 package model 2 3 import ( 4 "context" 5 "encoding/base32" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math" 10 "reflect" 11 "strings" 12 "unicode/utf8" 13 14 "github.com/tickoalcantara12/micro/v3/service/auth" 15 "github.com/tickoalcantara12/micro/v3/service/store" 16 "github.com/stoewer/go-strcase" 17 ) 18 19 type model struct { 20 // the database used for querying 21 database string 22 // the table to use for the model 23 table string 24 // the primary index using id 25 idIndex Index 26 // helps logically separate keys in a model where 27 // multiple `Model`s share the same underlying 28 // physical database. 29 namespace string 30 // the user defined.options.Indexes maintained for queries 31 indexes []Index 32 // options accepted for the model 33 options *Options 34 // the instance of the model 35 instance interface{} 36 } 37 38 // NewModel returns a new model with options or uses internal defaults 39 func NewModel(opts ...Option) Model { 40 var options Options 41 42 for _, o := range opts { 43 o(&options) 44 } 45 46 if options.Store == nil { 47 options.Store = store.DefaultStore 48 } 49 50 if len(options.Indexes) == 0 { 51 options.Indexes = append(options.Indexes, DefaultIndex) 52 } 53 54 return New(nil, &options) 55 } 56 57 // New returns a new model with the given values 58 func New(instance interface{}, options *Options) Model { 59 if options == nil { 60 options = new(Options) 61 } 62 63 // indirect pointer types 64 // so we dont have to deal with pointers vs values down the line 65 if reflect.ValueOf(instance).Kind() == reflect.Ptr { 66 instance = reflect.Indirect(reflect.ValueOf(instance)).Interface() 67 } 68 69 var namespace, database, table string 70 71 // define namespace based on the value passed in 72 if instance != nil { 73 namespace = reflect.TypeOf(instance).String() 74 } 75 76 if len(options.Namespace) > 0 { 77 namespace = options.Namespace 78 } 79 80 if options.Store == nil { 81 options.Store = store.DefaultStore 82 } 83 84 if options.Context == nil { 85 options.Context = context.TODO() 86 } 87 if options.Key == "" { 88 var err error 89 options.Key, err = getKey(instance) 90 if err != nil { 91 // @todo throw panic? make new return error? 92 // CRUFT 93 options.Key = err.Error() 94 } 95 } 96 // the default index 97 idx := DefaultIndex 98 99 if len(options.Key) > 0 { 100 idx = newIndex(options.Key) 101 } 102 103 // set the database 104 database = options.Database 105 table = options.Table 106 107 // set defaults if blank 108 if len(database) == 0 && options.Store != nil { 109 database = options.Store.Options().Database 110 } 111 112 // set defaults if blank 113 if len(table) == 0 && options.Store != nil { 114 table = options.Store.Options().Table 115 } 116 117 return &model{ 118 database: database, 119 table: table, 120 idIndex: idx, 121 instance: instance, 122 namespace: namespace, 123 options: options, 124 } 125 } 126 127 func getKey(instance interface{}) (string, error) { 128 // will be registered later probably 129 if instance == nil { 130 return "", nil 131 } 132 idFields := []string{"ID", "Id", "id"} 133 134 switch v := instance.(type) { 135 case map[string]interface{}: 136 for _, idField := range idFields { 137 if _, ok := v[idField]; ok { 138 return idField, nil 139 } 140 } 141 // To support empty map schema 142 // db initializations, we return the default ID field 143 return "ID", nil 144 default: 145 val := reflect.ValueOf(instance) 146 for _, idField := range idFields { 147 if val.FieldByName(idField).IsValid() { 148 return idField, nil 149 } 150 } 151 } 152 153 return "", errors.New("ID Field not found") 154 } 155 156 // @todo we should correlate the field name with the model 157 // instead of just blindly converting strings 158 func (d *model) getFieldName(field string) string { 159 fieldName := "" 160 if strings.Contains(field, "_") { 161 fieldName = strcase.UpperCamelCase(field) 162 } else { 163 fieldName = strings.Title(field) 164 } 165 if fieldName == "ID" { 166 return d.options.Key 167 } 168 return fieldName 169 } 170 171 func (d *model) getFieldValue(struc interface{}, fieldName string) interface{} { 172 switch v := struc.(type) { 173 case map[string]interface{}: 174 return v[fieldName] 175 } 176 177 fieldName = d.getFieldName(fieldName) 178 r := reflect.ValueOf(struc) 179 f := reflect.Indirect(r).FieldByName(fieldName) 180 181 if !f.IsValid() { 182 return nil 183 } 184 return f.Interface() 185 } 186 187 func (d *model) setFieldValue(struc interface{}, fieldName string, value interface{}) { 188 switch v := struc.(type) { 189 case map[string]interface{}: 190 v[fieldName] = value 191 return 192 } 193 194 fieldName = d.getFieldName(fieldName) 195 r := reflect.ValueOf(struc) 196 197 f := reflect.Indirect(r).FieldByName(fieldName) 198 f.Set(reflect.ValueOf(value)) 199 } 200 201 func (d *model) Context(ctx context.Context) Model { 202 // dereference the opts 203 opts := *d.options 204 opts.Context = ctx 205 206 // retrieve the account from context and override the database 207 acc, ok := auth.AccountFromContext(ctx) 208 if ok { 209 if len(acc.Issuer) > 0 { 210 // set the database to the account issuer 211 opts.Database = acc.Issuer 212 } 213 } 214 215 return &model{ 216 database: opts.Database, 217 table: opts.Table, 218 idIndex: d.idIndex, 219 instance: d.instance, 220 namespace: d.namespace, 221 options: &opts, 222 } 223 } 224 225 // Register an instance type of a model 226 func (d *model) Register(instance interface{}) error { 227 if instance == nil { 228 return ErrorNilInterface 229 } 230 if reflect.ValueOf(instance).Kind() == reflect.Ptr { 231 instance = reflect.Indirect(reflect.ValueOf(instance)).Interface() 232 } 233 if d.options.Key == "" { 234 var err error 235 d.options.Key, err = getKey(instance) 236 if err != nil { 237 return err 238 } 239 } 240 241 // set the namespace 242 d.namespace = reflect.TypeOf(instance).String() 243 // TODO: add.options.Indexes? 244 d.instance = instance 245 246 return nil 247 } 248 249 func (d *model) Create(instance interface{}) error { 250 if reflect.ValueOf(instance).Kind() == reflect.Ptr { 251 instance = reflect.Indirect(reflect.ValueOf(instance)).Interface() 252 } 253 // @todo replace this hack with reflection 254 js, err := json.Marshal(instance) 255 if err != nil { 256 return err 257 } 258 259 // get the old entries so we can compare values 260 // @todo consider some kind of locking (even if it's not distributed) by key here 261 // to avoid 2 read-writes happening at the same time 262 idQuery := d.idIndex.ToQuery(d.getFieldValue(instance, d.idIndex.FieldName)) 263 264 var oldEntry interface{} 265 switch instance.(type) { 266 case map[string]interface{}: 267 oldEntry = map[string]interface{}{} 268 default: 269 oldEntry = reflect.New(reflect.ValueOf(instance).Type()).Interface() 270 } 271 272 err = d.Read(idQuery, &oldEntry) 273 if err != nil && err != ErrorNotFound { 274 return err 275 } 276 277 oldEntryFound := false 278 // map in interface can be non nil but empty 279 // so test for that 280 switch v := oldEntry.(type) { 281 case map[string]interface{}: 282 if len(v) > 0 { 283 oldEntryFound = true 284 } 285 default: 286 if oldEntry != nil { 287 oldEntryFound = true 288 } 289 } 290 291 // Do uniqueness checks before saving any data 292 for _, index := range d.options.Indexes { 293 if !index.Unique { 294 continue 295 } 296 potentialClash := reflect.New(reflect.ValueOf(instance).Type()).Interface() 297 err = d.Read(index.ToQuery(d.getFieldValue(instance, index.FieldName)), &potentialClash) 298 if err != nil && err != ErrorNotFound { 299 return err 300 } 301 302 if err == nil { 303 return errors.New("Unique index violation") 304 } 305 } 306 307 id := d.getFieldValue(instance, d.idIndex.FieldName) 308 for _, index := range append(d.options.Indexes, d.idIndex) { 309 // delete non id index keys to prevent stale index values 310 // ie. 311 // 312 // # prefix slug id 313 // postByTag/hi-there/1 314 // # if slug gets changed to "hello-there" we will have two records 315 // # without removing the old stale index: 316 // postByTag/hi-there/1 317 // postByTag/hello-there/1` 318 // 319 // @todo this check will only work for POD types, ie no slices or maps 320 // but it's not an issue as right now indexes are only supported on POD 321 // types anyway 322 if !indexesMatch(d.idIndex, index) && 323 oldEntryFound && 324 !reflect.DeepEqual(d.getFieldValue(oldEntry, index.FieldName), d.getFieldValue(instance, index.FieldName)) { 325 326 k := d.indexToKey(index, id, oldEntry, true) 327 // TODO: set the table name in the query 328 err = d.options.Store.Delete(k, store.DeleteFrom(d.database, d.table)) 329 if err != nil { 330 return err 331 } 332 } 333 k := d.indexToKey(index, id, instance, true) 334 if d.options.Debug { 335 fmt.Printf("Saving key '%v', value: '%v'\n", k, string(js)) 336 } 337 // TODO: set the table name in the query 338 err = d.options.Store.Write(&store.Record{ 339 Key: k, 340 Value: js, 341 }, store.WriteTo(d.database, d.table)) 342 if err != nil { 343 return err 344 } 345 } 346 return nil 347 } 348 349 // TODO: implement the full functionality. Currently offloads to create. 350 func (d *model) Update(v interface{}) error { 351 return d.Create(v) 352 } 353 354 func (d *model) Read(query Query, resultPointer interface{}) error { 355 t := reflect.TypeOf(resultPointer) 356 357 // check if it's a pointer 358 if v := t.Kind(); v != reflect.Ptr { 359 return fmt.Errorf("Require pointer type. Got %v", v) 360 } 361 362 // retrieve the non pointer type 363 t = t.Elem() 364 365 // if its a slice then use the list query method 366 if t.Kind() == reflect.Slice { 367 return d.list(query, resultPointer) 368 } 369 370 // otherwise continue on as normal 371 read := func(index Index) error { 372 k := d.queryToListKey(index, query) 373 if d.options.Debug { 374 fmt.Printf("Listing key '%v'\n", k) 375 } 376 // TODO: set the table name in the query 377 opts := []store.ReadOption{ 378 store.ReadPrefix(), 379 store.ReadFrom(d.database, d.table), 380 store.ReadLimit(1), 381 } 382 recs, err := d.options.Store.Read(k, opts...) 383 if err != nil { 384 return err 385 } 386 if len(recs) == 0 { 387 return ErrorNotFound 388 } 389 if len(recs) > 1 { 390 return ErrorMultipleRecordsFound 391 } 392 if d.options.Debug { 393 fmt.Printf("Found value '%v'\n", string(recs[0].Value)) 394 } 395 return json.Unmarshal(recs[0].Value, resultPointer) 396 } 397 if query.Type == queryTypeAll { 398 return read(Index{ 399 Type: indexTypeAll, 400 FieldName: d.options.Key, 401 Order: d.idIndex.Order, 402 }) 403 } 404 for _, index := range append(d.options.Indexes, d.idIndex) { 405 if indexMatchesQuery(index, query) { 406 return read(index) 407 } 408 } 409 410 // find a maching query if non exists, take the first one 411 // which applies to the same field regardless of ordering 412 // or padding etc. 413 for _, index := range append(d.options.Indexes, d.idIndex) { 414 if index.FieldName == query.FieldName { 415 return read(index) 416 } 417 } 418 return fmt.Errorf("Read: for query type '%v', field '%v' does not match any indexes", query.Type, query.FieldName) 419 } 420 421 func (d *model) list(query Query, resultSlicePointer interface{}) error { 422 list := func(index Index) error { 423 k := d.queryToListKey(index, query) 424 if d.options.Debug { 425 fmt.Printf("Listing key '%v'\n", k) 426 } 427 428 opts := []store.ReadOption{ 429 store.ReadPrefix(), 430 store.ReadFrom(d.database, d.table), 431 } 432 433 if query.Limit > 0 { 434 opts = append(opts, store.ReadLimit(uint(query.Limit))) 435 } 436 437 if query.Offset > 0 { 438 opts = append(opts, store.ReadOffset(uint(query.Offset))) 439 } 440 recs, err := d.options.Store.Read(k, opts...) 441 if err != nil { 442 return err 443 } 444 // @todo speed this up with an actual buffer 445 jsBuffer := []byte("[") 446 for i, rec := range recs { 447 jsBuffer = append(jsBuffer, rec.Value...) 448 if i < len(recs)-1 { 449 jsBuffer = append(jsBuffer, []byte(",")...) 450 } 451 } 452 jsBuffer = append(jsBuffer, []byte("]")...) 453 if d.options.Debug { 454 fmt.Printf("Found values '%v'\n", string(jsBuffer)) 455 } 456 return json.Unmarshal(jsBuffer, resultSlicePointer) 457 } 458 459 if query.Type == queryTypeAll { 460 return list(Index{ 461 Type: indexTypeAll, 462 FieldName: d.options.Key, 463 Order: d.idIndex.Order, 464 }) 465 } 466 for _, index := range append(d.options.Indexes, d.idIndex) { 467 if indexMatchesQuery(index, query) { 468 return list(index) 469 } 470 } 471 472 // find a maching query if non exists, take the first one 473 // which applies to the same field regardless of ordering 474 // or padding etc. 475 for _, index := range append(d.options.Indexes, d.idIndex) { 476 if index.FieldName == query.FieldName { 477 return list(index) 478 } 479 } 480 481 return fmt.Errorf("List: for query type '%v', field '%v' does not match any indexes", query.Type, query.FieldName) 482 } 483 484 func (d *model) queryToListKey(i Index, q Query) string { 485 if q.Value == nil { 486 return fmt.Sprintf("%v:%v", d.namespace, indexPrefix(i)) 487 } 488 if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" { 489 return fmt.Sprintf("%v:%v:%v", d.namespace, indexPrefix(i), q.Value) 490 } 491 492 var val interface{} 493 switch d.instance.(type) { 494 case map[string]interface{}: 495 val = map[string]interface{}{} 496 default: 497 val = reflect.New(reflect.ValueOf(d.instance).Type()).Interface() 498 } 499 500 if q.Value != nil { 501 d.setFieldValue(val, i.FieldName, q.Value) 502 } 503 return d.indexToKey(i, "", val, false) 504 } 505 506 // appendID true should be used when saving, false when querying 507 // appendID false should also be used for 'id' indexes since they already have the unique 508 // id. The reason id gets appended is make duplicated index keys unique. 509 // ie. 510 // # index # age # id 511 // users/30/1 512 // users/30/2 513 // without ids we could only have one 30 year old user in the index 514 func (d *model) indexToKey(i Index, id interface{}, entry interface{}, appendID bool) string { 515 if i.Type == indexTypeAll { 516 return fmt.Sprintf("%v:%v", d.namespace, indexPrefix(i)) 517 } 518 if i.FieldName == "ID" { 519 i.FieldName = d.options.Key 520 } 521 522 format := "%v:%v" 523 values := []interface{}{d.namespace, indexPrefix(i)} 524 filterFieldValue := d.getFieldValue(entry, i.FieldName) 525 orderFieldValue := d.getFieldValue(entry, i.FieldName) 526 orderFieldKey := i.FieldName 527 528 if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" { 529 orderFieldValue = d.getFieldValue(entry, i.Order.FieldName) 530 orderFieldKey = i.Order.FieldName 531 } 532 533 switch i.Type { 534 case indexTypeEq: 535 // If the filtering field is different than the ordering field, 536 // append the filter key to the key. 537 if i.FieldName != i.Order.FieldName && i.Order.FieldName != "" { 538 format += ":%v" 539 values = append(values, filterFieldValue) 540 } 541 } 542 543 // Handle the ordering part of the key. 544 // The filter and the ordering field might be the same 545 typ := reflect.TypeOf(orderFieldValue) 546 typName := "nil" 547 if typ != nil { 548 typName = typ.String() 549 } 550 format += ":%v" 551 552 switch v := orderFieldValue.(type) { 553 case string: 554 if i.Order.Type != OrderTypeUnordered { 555 values = append(values, d.getOrderedStringFieldKey(i, v)) 556 break 557 } 558 values = append(values, v) 559 case int64: 560 // int64 gets padded to 19 characters as the maximum value of an int64 561 // is 9223372036854775807 562 // @todo handle negative numbers 563 if i.Order.Type == OrderTypeDesc { 564 values = append(values, fmt.Sprintf("%019d", math.MaxInt64-v)) 565 break 566 } 567 values = append(values, fmt.Sprintf("%019d", v)) 568 case float32: 569 // @todo fix display and padding of floats 570 if i.Order.Type == OrderTypeDesc { 571 values = append(values, fmt.Sprintf(i.FloatFormat, i.Float32Max-v)) 572 break 573 } 574 values = append(values, fmt.Sprintf(i.FloatFormat, v)) 575 case float64: 576 // @todo fix display and padding of floats 577 if i.Order.Type == OrderTypeDesc { 578 values = append(values, fmt.Sprintf(i.FloatFormat, i.Float64Max-v)) 579 break 580 } 581 values = append(values, fmt.Sprintf(i.FloatFormat, v)) 582 case int: 583 // int gets padded to the same length as int64 to gain 584 // resiliency in case of model type changes. 585 // This could be removed once migrations are implemented 586 // so savings in space for a type reflect in savings in space in the index too. 587 if i.Order.Type == OrderTypeDesc { 588 values = append(values, fmt.Sprintf("%019d", math.MaxInt32-v)) 589 break 590 } 591 values = append(values, fmt.Sprintf("%019d", v)) 592 case int32: 593 // int gets padded to the same length as int64 to gain 594 // resiliency in case of model type changes. 595 // This could be removed once migrations are implemented 596 // so savings in space for a type reflect in savings in space in the index too. 597 if i.Order.Type == OrderTypeDesc { 598 values = append(values, fmt.Sprintf("%019d", math.MaxInt32-v)) 599 break 600 } 601 values = append(values, fmt.Sprintf("%019d", v)) 602 case bool: 603 if i.Order.Type == OrderTypeDesc { 604 v = !v 605 } 606 values = append(values, v) 607 default: 608 panic("bug in code, unhandled type: " + typName + " for field '" + orderFieldKey + "' on type '" + reflect.TypeOf(d.instance).String() + "'") 609 } 610 611 if appendID { 612 format += ":%v" 613 values = append(values, id) 614 } 615 return fmt.Sprintf(format, values...) 616 } 617 618 // pad, reverse and optionally base32 encode string keys 619 func (d *model) getOrderedStringFieldKey(i Index, fieldValue string) string { 620 runes := []rune{} 621 if i.Order.Type == OrderTypeDesc { 622 for _, char := range fieldValue { 623 runes = append(runes, utf8.MaxRune-char) 624 } 625 } else { 626 for _, char := range fieldValue { 627 runes = append(runes, char) 628 } 629 } 630 631 // padding the string to a fixed length 632 if len(runes) < i.StringOrderPadLength { 633 pad := []rune{} 634 for j := 0; j < i.StringOrderPadLength-len(runes); j++ { 635 if i.Order.Type == OrderTypeDesc { 636 pad = append(pad, utf8.MaxRune) 637 } else { 638 // space is the first non control operator char in ASCII 639 // consequently in Utf8 too so we use it as the minimal character here 640 // https://en.wikipedia.org/wiki/ASCII 641 // 642 // Displays somewhat unfortunately 643 // @todo think about a better min rune value to use here. 644 pad = append(pad, rune(32)) 645 } 646 } 647 runes = append(runes, pad...) 648 } 649 650 var keyPart string 651 bs := []byte(string(runes)) 652 if i.Order.Type == OrderTypeDesc { 653 if i.Base32Encode { 654 // base32 hex should be order preserving 655 // https://stackoverflow.com/questions/53301280/does-base64-encoding-preserve-alphabetical-ordering 656 dst := make([]byte, base32.HexEncoding.EncodedLen(len(bs))) 657 base32.HexEncoding.Encode(dst, bs) 658 // The `=` must be replaced with a lower value than the 659 // normal alphabet of the encoding since we want reverse order. 660 keyPart = strings.ReplaceAll(string(dst), "=", "0") 661 } else { 662 keyPart = string(bs) 663 } 664 } else { 665 keyPart = string(bs) 666 667 } 668 return keyPart 669 } 670 671 func (d *model) Delete(query Query) error { 672 oldEntry := reflect.New(reflect.ValueOf(d.instance).Type()).Interface() 673 switch oldEntry.(type) { 674 case *map[string]interface{}: 675 oldEntry = reflect.Indirect(reflect.ValueOf(oldEntry)).Interface() 676 } 677 err := d.Read(d.idIndex.ToQuery(query.Value), &oldEntry) 678 if err != nil { 679 return err 680 } 681 682 // first delete maintained.options.Indexes then id index 683 // if we delete id index first then the entry wont 684 // be deletable by id again but the maintained.options.Indexes 685 // will be stuck in limbo 686 for _, index := range append(d.options.Indexes, d.idIndex) { 687 key := d.indexToKey(index, d.getFieldValue(oldEntry, d.idIndex.FieldName), oldEntry, true) 688 if d.options.Debug { 689 fmt.Printf("Deleting key '%v'\n", key) 690 } 691 // TODO: set the table to delete from 692 err = d.options.Store.Delete(key, store.DeleteFrom(d.database, d.table)) 693 if err != nil { 694 return err 695 } 696 } 697 return nil 698 }