github.com/mgulsoy/arnedb@v1.2.2-alpha/coll.go (about) 1 package arnedb 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/fs" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 ) 18 19 const firstChunkName = "00.json" 20 const maxChunkSize = 1024 * 1024 // 1MB 21 const recordSepChar = 10 // --> \n 22 const recordSepStr = "\n" 23 24 // RecordInstance represents a record instance read from data file. It is actually a map. 25 type RecordInstance map[string]interface{} 26 27 // QueryPredicate is a function type receiving row instance and returning bool 28 type QueryPredicate func(instance RecordInstance) bool 29 30 // QueryPredicateAsInterface is a function type receiving record instance as given type and returning bool 31 type QueryPredicateAsInterface func(instance interface{}) bool 32 33 // UpdateFunc alters the data matched by predicate 34 type UpdateFunc func(ptrRecord *RecordInstance) *RecordInstance 35 36 // Add function appends data into a collection 37 func (coll *Coll) Add(data interface{}) error { 38 39 // Kolleksiyonlar chunkXX.json adı verilen yığınlara ayrılır. Her bir yığın max 1 MB büyüklüğe kadar 40 // büyüyebilir. 41 42 payload, err := json.Marshal(data) 43 if err != nil { 44 // veriyi paketlemekte sorun 45 return errors.New(fmt.Sprintf("cannot marshal data: %s", err.Error())) 46 } 47 48 // Coll var mı ona bakılır. Yoksa hata... 49 _, err = os.Stat(coll.dbpath) 50 if os.IsNotExist(err) { 51 return errors.New(fmt.Sprintf("collection does not exist: %s", err.Error())) 52 } 53 54 // Coll var. En son chunk bulunur. 55 lastChunk, err := coll.createChunk() 56 if err != nil { 57 return err 58 } 59 60 // Elimizde en son chunk var. 61 chunkPath := filepath.Join(coll.dbpath, (*lastChunk).Name()) 62 f, err := os.OpenFile(chunkPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 63 if err != nil { 64 return errors.New(fmt.Sprintf("cannot open chunk to add data: %s", err.Error())) 65 } 66 67 // Kayıt sonu karakteri eklenir 68 payload = append(payload, byte(recordSepChar)) 69 write, err := f.Write(payload) 70 if err != nil { 71 return errors.New(fmt.Sprintf("cannot append chunk: %s", err.Error())) 72 } 73 74 if write != len(payload) { 75 return errors.New(fmt.Sprintf("append to chunk failed with: %d bytes diff", len(payload)-write)) 76 } 77 err = f.Close() 78 if err != nil { 79 return errors.New(fmt.Sprintf("append failed to clos file: %s", err.Error())) 80 } 81 82 // işlem başarılı 83 return nil 84 } 85 86 // AddAll function appends multiple data into a collection. If one fails, no data will be committed to storage. Thus, 87 // this function acts like a transaction. This function is a variadic function which accepts a SLICE as an argument: 88 // 89 // d := []RecordInstance{ a, b, c} 90 // AddAll(d...) 91 // 92 // Or can be called like: 93 // 94 // AddAll(d1,d2,d3) 95 func (coll *Coll) AddAll(data ...RecordInstance) (int, error) { 96 97 n := 0 98 _, err := os.Stat(coll.dbpath) 99 if os.IsNotExist(err) { 100 return n, errors.New(fmt.Sprintf("collection does not exist: %s", err.Error())) 101 } 102 103 // Coll var. En son chunk bulunur. 104 lastChunk, err := coll.createChunk() 105 if err != nil { 106 return n, err 107 } 108 109 bufferStore := make([]byte, 512*len(data)) // her eleman için 512 byte ayır 110 buffer := bytes.NewBuffer(bufferStore) 111 buffer.Reset() 112 113 // Ekleme işlemini hafızada gerçekleştir. 114 // TODO: Test payload allocation performance 115 for _, dataElement := range data { 116 payload, err := json.Marshal(dataElement) 117 if err != nil { 118 // veriyi paketlemekte sorun 119 return 0, errors.New(fmt.Sprintf("cannot marshal data: %s", err.Error())) 120 } 121 122 // Tampon belleğe kaydı ekle 123 buffer.Write(payload) 124 // Kayıt sonu karakterini ekle 125 buffer.WriteString(recordSepStr) 126 n++ 127 } 128 129 // Buraya kadar kod kırılmamışsa diske yazabiliriz. 130 // Elimizde en son chunk var. 131 chunkPath := filepath.Join(coll.dbpath, (*lastChunk).Name()) 132 f, err := os.OpenFile(chunkPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) 133 if err != nil { 134 return 0, errors.New(fmt.Sprintf("cannot open chunk to add data: %s", err.Error())) 135 } 136 137 // Şimdilik yazılan byte sayısı ile ilgilenmiyoruz 138 _, err = buffer.WriteTo(f) 139 if err != nil { 140 _ = f.Close() 141 return 0, errors.New(fmt.Sprintf("cannot append chunk: %s", err.Error())) 142 } 143 144 err = f.Close() 145 if err != nil { 146 return 0, errors.New(fmt.Sprintf("append failed to close file: %s", err.Error())) 147 } 148 149 // işlem başarılı 150 return n, nil 151 } 152 153 // GetFirst function queries and gets the first match of the query. 154 // The function returns nil if no data found. 155 func (coll *Coll) GetFirst(predicate QueryPredicate) (result RecordInstance, err error) { 156 chunks, err := coll.getChunks() 157 if err != nil { 158 return nil, err 159 } 160 161 if len(chunks) == 0 { 162 // İçeride hiç veri yok 163 return nil, nil 164 } 165 166 var f *os.File 167 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 168 // Hata olursa isimli return value'ları buna göre düzenleriz. 169 defer func() { 170 if r := recover(); r != nil { 171 //fmt.Errorf("recover??? %+v", r) 172 result = nil 173 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 174 if f != nil { // dosya kapanmamışsa kapat 175 _ = f.Close() 176 } 177 } 178 }() 179 180 var data RecordInstance 181 for _, chunk := range chunks { 182 // Veri aranır. Bunun için bütün chunklara bakılır 183 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 184 f, err = os.Open(chunkPath) 185 if err != nil { 186 return nil, err 187 } 188 189 scn := bufio.NewScanner(f) 190 dataMatched := false 191 for scn.Scan() { 192 line := scn.Bytes() 193 if len(line) == 0 { 194 continue 195 } 196 _ = json.Unmarshal(line, &data) // TODO: Handle error 197 dataMatched = predicate(data) 198 if dataMatched { 199 break 200 } 201 } 202 _ = f.Close() // TODO: Handle error 203 f = nil // temizle 204 if dataMatched { 205 return data, nil 206 } 207 } 208 209 return nil, nil 210 } 211 212 // GetFirstAs function queries given coll and gets the first match of the query. This function uses generics. 213 // Returns nil if no data found. 214 func GetFirstAs[T any](coll *Coll, predicate func(i *T) bool) (result *T, err error) { 215 chunks, err := coll.getChunks() 216 if err != nil { 217 return nil, err // marks not found 218 } 219 220 if len(chunks) == 0 { 221 // İçeride hiç veri yok 222 return nil, nil // no data 223 } 224 225 var f *os.File 226 defer func() { // predicate içindeki hatayı yakala 227 if r := recover(); r != nil { 228 //fmt.Errorf("recover??? %+v", r) 229 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 230 if f != nil { // dosya kapanmamışsa kapat 231 _ = f.Close() 232 } 233 } 234 }() 235 236 for _, chunk := range chunks { 237 // Veri aranır. Bunun için bütün chunklara bakılır 238 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 239 f, err = os.Open(chunkPath) 240 if err != nil { 241 return nil, err 242 } 243 244 dec := json.NewDecoder(f) 245 var m T 246 predicateResult := false 247 for { 248 err = dec.Decode(&m) 249 if err == io.EOF { 250 // eof 251 break 252 } else if err != nil { 253 //return false, err 254 continue // skip this record 255 } 256 predicateResult = predicate(&m) 257 if predicateResult == true { 258 break 259 } 260 } 261 262 _ = f.Close() // TODO: Handle error 263 f = nil // temizle 264 265 if predicateResult == true { 266 return &m, nil 267 } 268 } 269 270 return nil, nil 271 272 } 273 274 // GetAllAs function queries given coll and returns all for the predicate match. This function uses generics. 275 // Returns a slice of data pointers. If nothing is found then empty slice is returned 276 func GetAllAs[T any](coll *Coll, predicate func(i *T) bool) (result []*T, err error) { 277 chunks, err := coll.getChunks() 278 if err != nil { 279 return nil, err // marks not found 280 } 281 282 result = make([]*T, 0) // sonuç için bir kolleksiyon ayarla 283 284 if len(chunks) == 0 { 285 // İçeride hiç veri yok 286 return result, nil // no data 287 } 288 289 var f *os.File 290 defer func() { // predicate içindeki hatayı yakala 291 if r := recover(); r != nil { 292 //fmt.Errorf("recover??? %+v", r) 293 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 294 if f != nil { // dosya kapanmamışsa kapat 295 _ = f.Close() 296 } 297 } 298 }() 299 300 for _, chunk := range chunks { 301 // Veri aranır. Bunun için bütün chunklara bakılır 302 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 303 f, err = os.Open(chunkPath) 304 if err != nil { 305 return nil, err 306 } 307 308 dec := json.NewDecoder(f) 309 predicateResult := false 310 for { 311 var m T 312 err = dec.Decode(&m) 313 if err == io.EOF { 314 // eof 315 break 316 } else if err != nil { 317 //return false, err 318 continue // skip this record 319 } 320 predicateResult = predicate(&m) 321 if predicateResult == true { 322 result = append(result, &m) 323 } 324 } 325 326 _ = f.Close() // TODO: Handle error 327 f = nil // temizle 328 329 return result, nil 330 } 331 332 return result, nil 333 } 334 335 // GetFirstAsInterface function queries and gets the first match of the query. The query result can be found in the 336 // holder argument. The function returns a boolean value indicating data is found or not. 337 func (coll *Coll) GetFirstAsInterface(predicate QueryPredicateAsInterface, holder interface{}) (found bool, err error) { 338 chunks, err := coll.getChunks() 339 if err != nil { 340 return false, err // marks not found 341 } 342 343 if len(chunks) == 0 { 344 // İçeride hiç veri yok 345 return false, nil // no data 346 } 347 348 var f *os.File 349 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 350 // Hata olursa isimli return value'ları buna göre düzenleriz. 351 defer func() { 352 if r := recover(); r != nil { 353 //fmt.Errorf("recover??? %+v", r) 354 found = false 355 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 356 if f != nil { // dosya kapanmamışsa kapat 357 _ = f.Close() 358 } 359 } 360 }() 361 362 for _, chunk := range chunks { 363 // Veri aranır. Bunun için bütün chunklara bakılır 364 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 365 f, err = os.Open(chunkPath) 366 if err != nil { 367 return false, err 368 } 369 370 scn := bufio.NewScanner(f) 371 dataMatched := false 372 for scn.Scan() { 373 line := scn.Bytes() 374 if len(line) == 0 { 375 continue 376 } 377 err = json.Unmarshal(line, holder) 378 if err != nil { 379 // error on unmarshal operation 380 continue // skip this record 381 } 382 el := holder 383 dataMatched = predicate(el) // evaluate predicate 384 if dataMatched { 385 found = true 386 break 387 } 388 } 389 _ = f.Close() // TODO: Handle error 390 f = nil // temizle 391 if dataMatched { 392 return true, nil 393 } 394 } 395 396 holder = nil // reset holder. 397 398 return false, nil 399 } 400 401 // GetAll function queries and gets all the matches of the query predicate. 402 func (coll *Coll) GetAll(predicate QueryPredicate) (result []RecordInstance, err error) { 403 404 chunks, err := coll.getChunks() 405 if err != nil { 406 return nil, err 407 } 408 409 if len(chunks) == 0 { 410 // İçeride hiç veri yok 411 return nil, nil 412 } 413 414 var f *os.File 415 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 416 // Hata olursa isimli return value'ları buna göre düzenleriz. 417 defer func() { 418 if r := recover(); r != nil { 419 //fmt.Errorf("recover??? %+v", r) 420 result = nil 421 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 422 if f != nil { // dosya kapanmamışsa kapat 423 _ = f.Close() 424 } 425 } 426 }() 427 428 result = make([]RecordInstance, 0) 429 dataMatched := false 430 431 for _, chunk := range chunks { 432 // Veri aranır. Bunun için bütün chunklara bakılır 433 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 434 f, err = os.Open(chunkPath) 435 if err != nil { 436 return nil, err 437 } 438 439 scn := bufio.NewScanner(f) 440 for scn.Scan() { 441 line := scn.Bytes() 442 if len(line) == 0 { 443 continue 444 } 445 var data RecordInstance 446 _ = json.Unmarshal(line, &data) // TODO: Handle error 447 dataMatched = predicate(data) 448 if dataMatched { 449 result = append(result, data) 450 } 451 } 452 _ = f.Close() // TODO: Handle error 453 f = nil // temizle 454 } 455 456 return result, nil 457 } 458 459 // Count function returns the count of matched records with the predicate function 460 func (coll *Coll) Count(predicate QueryPredicate) (n int, err error) { 461 n = 0 462 chunks, err := coll.getChunks() 463 if err != nil { 464 return n, err 465 } 466 467 if len(chunks) == 0 { 468 // İçeride hiç veri yok 469 return n, nil 470 } 471 472 var f *os.File 473 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 474 // Hata olursa isimli return value'ları buna göre düzenleriz. 475 defer func() { 476 if r := recover(); r != nil { 477 //fmt.Errorf("recover??? %+v", r) 478 n = 0 479 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 480 if f != nil { // dosya kapanmamışsa kapat 481 _ = f.Close() 482 } 483 } 484 }() 485 486 dataMatched := false 487 for _, chunk := range chunks { 488 // Veri aranır. Bunun için bütün chunklara bakılır 489 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 490 f, err = os.Open(chunkPath) 491 if err != nil { 492 return 0, err 493 } 494 495 scn := bufio.NewScanner(f) 496 for scn.Scan() { 497 line := scn.Bytes() 498 if len(line) == 0 { 499 continue 500 } 501 var data RecordInstance 502 _ = json.Unmarshal(line, &data) // TODO: Handle error 503 dataMatched = predicate(data) 504 if dataMatched { 505 n++ 506 } 507 } 508 _ = f.Close() // TODO: Handle error 509 f = nil // cleanup 510 } 511 512 return n, nil 513 } 514 515 // GetAllAsInterface function queries and gets all the matches of the query predicate. Returns the 516 // number of record found or 0 if not. Data is sent into harvestCallback function. So you can harvest 517 // the data. There is no generics in GO. So user must handle the type conversion. 518 func (coll *Coll) GetAllAsInterface(predicate QueryPredicateAsInterface, harvestCallback QueryPredicateAsInterface, holder interface{}) (n int, err error) { 519 520 n = 0 // init 521 chunks, err := coll.getChunks() 522 if err != nil { 523 return n, err 524 } 525 526 if len(chunks) == 0 { 527 // İçeride hiç veri yok 528 return n, nil 529 } 530 531 var f *os.File 532 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 533 // Hata olursa isimli return value'ları buna göre düzenleriz. 534 defer func() { 535 if r := recover(); r != nil { 536 //fmt.Errorf("recover??? %+v", r) 537 n = 0 538 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 539 if f != nil { // dosya kapanmamışsa kapat 540 _ = f.Close() 541 } 542 } 543 }() 544 545 dataMatched := false 546 for _, chunk := range chunks { 547 // Veri aranır. Bunun için bütün chunklara bakılır 548 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 549 f, err = os.Open(chunkPath) 550 if err != nil { 551 return 0, err 552 } 553 554 scn := bufio.NewScanner(f) 555 for scn.Scan() { 556 line := scn.Bytes() 557 if len(line) == 0 { 558 continue 559 } 560 561 err = json.Unmarshal(line, holder) // TODO: Handle error 562 if err != nil { 563 // if an error occurs skip it 564 continue 565 } 566 dataMatched = predicate(holder) 567 if dataMatched { 568 harvestCallback(holder) 569 n++ 570 } 571 } 572 _ = f.Close() // TODO: Handle error 573 f = nil // temizle 574 } 575 576 return n, nil 577 } 578 579 // DeleteFirst function deletes the first match of the predicate and returns the count of deleted 580 // records. n = 1 if a deletion occurred, n = 0 if none. 581 func (coll *Coll) DeleteFirst(predicate QueryPredicate) (n int, err error) { 582 chunks, err := coll.getChunks() 583 n = 0 584 if err != nil { 585 return n, err 586 } 587 588 if len(chunks) == 0 { 589 // İçeride hiç veri yok 590 return n, nil 591 } 592 593 var f *os.File 594 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 595 // Hata olursa isimli return value'ları buna göre düzenleriz. 596 defer func() { 597 if r := recover(); r != nil { 598 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 599 if f != nil { // dosya kapanmamışsa kapat 600 _ = f.Close() 601 } 602 } 603 }() 604 var data RecordInstance 605 var bufferStore = make([]byte, 2*1024*1024) // 2 mb buffer 606 buffer := bytes.NewBuffer(bufferStore) 607 608 for _, chunk := range chunks { 609 // Veri aranır. Bunun için bütün chunklara bakılır 610 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 611 f, err = os.Open(chunkPath) 612 if err != nil { 613 return n, err 614 } 615 616 scn := bufio.NewScanner(f) 617 buffer.Reset() 618 dataMatched := false 619 anyMatchesOccured := false 620 621 for scn.Scan() { 622 line := scn.Bytes() 623 if len(line) == 0 { 624 dataMatched = false 625 } else { 626 _ = json.Unmarshal(line, &data) // TODO: Handle error 627 dataMatched = predicate(data) 628 } 629 if !dataMatched { 630 // predicate sonucu olumsuz. Bu durumda orjinal data yerine yazılır. 631 buffer.Write(line) 632 buffer.WriteString(recordSepStr) 633 } else { 634 // Sonuç bulunmuş. Bu sonuç yerine \n yazılır. Satır numarası değişmez! Fakat bu işlem sadece 635 // ilk sonuç için yapılır. 636 // Satır numarası daha sonradan indexleme için kullanılacak! 637 if !anyMatchesOccured { 638 buffer.WriteString(recordSepStr) 639 } 640 } 641 anyMatchesOccured = anyMatchesOccured || dataMatched 642 } 643 _ = f.Close() // TODO: Handle error 644 f = nil // temizle 645 if anyMatchesOccured { 646 //dosyada düzeltme yapılmış demektir. Bu durumda buffer, işlem yapılan chunk üzerine yazılır. 647 f, err = os.Create(chunkPath) // Truncate file 648 if err != nil { 649 return n, err 650 } 651 //_, err := f.Write(bufferStore) 652 _, err = buffer.WriteTo(f) 653 if err != nil { 654 // yazma hatası 655 return n, err 656 } 657 _ = f.Close() 658 f = nil 659 n++ 660 break // Chunk loop kır. 661 } 662 } //end chunks 663 664 return n, nil 665 } 666 667 // DeleteAll function deletes all the matches of the predicate and returns the count of deletions. 668 // n = 0 if no deletions occurred. 669 func (coll *Coll) DeleteAll(predicate QueryPredicate) (n int, err error) { 670 chunks, err := coll.getChunks() 671 n = 0 672 if err != nil { 673 return n, err 674 } 675 676 if len(chunks) == 0 { 677 // İçeride hiç veri yok 678 return n, nil 679 } 680 681 var f *os.File 682 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 683 // Hata olursa isimli return value'ları buna göre düzenleriz. 684 defer func() { 685 if r := recover(); r != nil { 686 n = 0 687 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 688 if f != nil { // dosya kapanmamışsa kapat 689 _ = f.Close() 690 } 691 } 692 }() 693 var data RecordInstance 694 var bufferStore = make([]byte, 2*1024*1024) // 2 mb buffer 695 buffer := bytes.NewBuffer(bufferStore) 696 697 for _, chunk := range chunks { 698 // Veri aranır. Bunun için bütün chunklara bakılır 699 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 700 f, err = os.Open(chunkPath) 701 if err != nil { 702 return 0, err 703 } 704 705 scn := bufio.NewScanner(f) 706 buffer.Reset() 707 dataMatched := false 708 anyMatchesOccured := false 709 for scn.Scan() { 710 line := scn.Bytes() 711 if len(line) == 0 { 712 dataMatched = false 713 } else { 714 // Satır boş değil 715 _ = json.Unmarshal(line, &data) // TODO: Handle error 716 dataMatched = predicate(data) 717 } 718 719 if !dataMatched { 720 // predicate sonucu olumsuz. Bu durumda orjinal data yerine yazılır. 721 buffer.Write(line) 722 } else { 723 n++ 724 } 725 buffer.WriteString(recordSepStr) 726 727 anyMatchesOccured = anyMatchesOccured || dataMatched 728 } 729 _ = f.Close() // TODO: Handle error 730 f = nil // temizle 731 if anyMatchesOccured { 732 //dosyada düzeltme yapılmış demektir. Bu durumda buffer, işlem yapılan chunk üzerine yazılır. 733 f, err = os.Create(chunkPath) // Truncate file 734 if err != nil { 735 return 0, err 736 } 737 //_, err := f.Write(bufferStore) 738 _, err = buffer.WriteTo(f) 739 if err != nil { 740 // yazma hatası 741 return 0, err 742 } 743 _ = f.Close() 744 f = nil 745 } 746 } //end chunks 747 748 return n, nil 749 } 750 751 // ReplaceFirst replaces the first match of the predicate with the newData and returns 752 // the count of updates. Obviously the return value is 1 if update is successful and 0 if not. 753 // Update is the most costly operation. The library does not provide a method to update parts of a 754 // document since document is not known to the system. 755 func (coll *Coll) ReplaceFirst(predicate QueryPredicate, newData interface{}) (n int, err error) { 756 return coll.replacer(predicate, newData, false) 757 } 758 759 // ReplaceAll replaces all the matches of the predicate with the newData and returns the 760 // count of updates. 761 // Replace is the most costly operation. The library does not provide a method to update parts of a 762 // document since document is not known to the system. 763 func (coll *Coll) ReplaceAll(predicate QueryPredicate, newData interface{}) (n int, err error) { 764 return coll.replacer(predicate, newData, true) 765 } 766 767 // UpdateFirst updates the first match of predicate in place with the data provided by the 768 // updateFunction 769 func (coll *Coll) UpdateFirst(predicate QueryPredicate, updateFunction UpdateFunc) (n int, err error) { 770 return coll.updater(predicate, updateFunction, false) 771 } 772 773 // UpdateAll updates all the matches of the predicate in place with the data provided by the 774 // updateFunction 775 func (coll *Coll) UpdateAll(predicate QueryPredicate, updateFunction UpdateFunc) (n int, err error) { 776 return coll.updater(predicate, updateFunction, true) 777 } 778 779 func (coll *Coll) updater(pred QueryPredicate, uf UpdateFunc, updateAll bool) (n int, err error) { 780 chunks, err := coll.getChunks() 781 n = 0 782 if err != nil { 783 return n, err // hiç update yok 784 } 785 786 if len(chunks) == 0 { 787 // İçeride hiç veri yok 788 return n, nil 789 } 790 791 var f *os.File 792 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 793 // Hata olursa isimli return value'ları buna göre düzenleriz. 794 defer func() { 795 if r := recover(); r != nil { 796 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 797 if f != nil { // dosya kapanmamışsa kapat 798 _ = f.Close() 799 } 800 } 801 }() 802 803 var data RecordInstance 804 var bufferStore = make([]byte, 2*1024*1024) // 2 mb buffer 805 buffer := bytes.NewBuffer(bufferStore) 806 807 for _, chunk := range chunks { 808 // Veri aranır. Bunun için bütün chunklara bakılır 809 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 810 f, err = os.Open(chunkPath) 811 if err != nil { 812 return n, err 813 } 814 815 scn := bufio.NewScanner(f) 816 buffer.Reset() 817 predicateMatched := false 818 anyMatchesOccured := false 819 820 // chunk verisi taranır ve bütün kayıtlar mem buffer içine yazılır. 821 // Bu durumda kayıt değişikliği yerinde yapılır. 822 for scn.Scan() { 823 line := scn.Bytes() 824 if len(line) == 0 { 825 predicateMatched = false 826 } else { 827 _ = json.Unmarshal(line, &data) // TODO: Handle error 828 predicateMatched = pred(data) 829 830 if predicateMatched && (!anyMatchesOccured || updateAll) { 831 newData := uf(&data) 832 newDataBytes, err := json.Marshal(newData) 833 if err != nil { 834 panic(fmt.Sprintf("updateFunction result cannot be marshalled: %s", err.Error())) 835 } 836 buffer.Write(newDataBytes) 837 } else { 838 buffer.Write(line) 839 } 840 } 841 842 buffer.WriteString(recordSepStr) 843 anyMatchesOccured = anyMatchesOccured || predicateMatched 844 } 845 _ = f.Close() // TODO: Handle error 846 f = nil // temizle 847 if anyMatchesOccured { 848 // Kayıt bir dosyada bulunmuş ve silinmiş demektir. 849 // Bu durumda buffer, işlem yapılan chunk üzerine yazılır. 850 f, err = os.Create(chunkPath) // Truncate file 851 if err != nil { 852 return n, err 853 } 854 //_, err := f.Write(bufferStore) 855 _, err = buffer.WriteTo(f) 856 if err != nil { 857 // yazma hatası 858 return n, err 859 } 860 _ = f.Close() 861 n++ // bu aşamada veri commit olmuş, değişiklik gerçekleşmiştir. 862 f = nil 863 break // Chunk loop kır. 864 } 865 } //end chunks 866 867 return n, nil 868 } 869 870 func (coll *Coll) replacer(pred QueryPredicate, nData interface{}, replaceAll bool) (n int, err error) { 871 chunks, err := coll.getChunks() 872 n = 0 873 if err != nil { 874 return n, err // hiç update yok 875 } 876 877 if len(chunks) == 0 { 878 // İçeride hiç veri yok 879 return n, nil 880 } 881 882 // Yeni kayıt kontrol edilir 883 newDataBytes, err := json.Marshal(nData) 884 if err != nil { 885 // Yeni kayıt dönüştürülemiyor demektir. 886 return n, err 887 } 888 889 var f *os.File 890 // Burada predicate içinde oluşabilecek olan hatayı yakalarız. 891 // Hata olursa isimli return value'ları buna göre düzenleriz. 892 defer func() { 893 if r := recover(); r != nil { 894 err = errors.New(fmt.Sprintf("predicate error: %s", r.(error).Error())) 895 if f != nil { // dosya kapanmamışsa kapat 896 _ = f.Close() 897 } 898 } 899 }() 900 901 var data RecordInstance 902 var bufferStore = make([]byte, 2*1024*1024) // 2 mb buffer 903 buffer := bytes.NewBuffer(bufferStore) 904 905 for _, chunk := range chunks { 906 // Veri aranır. Bunun için bütün chunklara bakılır 907 chunkPath := filepath.Join(coll.dbpath, chunk.Name()) 908 f, err = os.Open(chunkPath) 909 if err != nil { 910 return n, err 911 } 912 913 scn := bufio.NewScanner(f) 914 buffer.Reset() 915 predicateMatched := false 916 anyMatchesOccured := false 917 918 // chunk verisi taranır ve bütün kayıtlar mem buffer içine yazılır. 919 // Bu durumda kayıt değişikliği yerinde yapılır. 920 for scn.Scan() { 921 line := scn.Bytes() 922 if len(line) == 0 { 923 predicateMatched = false 924 } else { 925 _ = json.Unmarshal(line, &data) // TODO: Handle error 926 predicateMatched = pred(data) 927 928 if predicateMatched && (!anyMatchesOccured || replaceAll) { 929 // Eğer daha önce bir değişiklik olmamışsa ve predicate eşleme yaptıysa 930 // yani ilk defa bir eşleme gerçekleşiyorsa... 931 buffer.Write(newDataBytes) 932 } else { 933 buffer.Write(line) 934 } 935 } 936 937 buffer.WriteString(recordSepStr) 938 anyMatchesOccured = anyMatchesOccured || predicateMatched 939 } 940 _ = f.Close() // TODO: Handle error 941 f = nil // temizle 942 if anyMatchesOccured { 943 // Kayıt bir dosyada bulunmuş ve silinmiş demektir. 944 // Bu durumda buffer, işlem yapılan chunk üzerine yazılır. 945 f, err = os.Create(chunkPath) // Truncate file 946 if err != nil { 947 return n, err 948 } 949 //_, err := f.Write(bufferStore) 950 _, err = buffer.WriteTo(f) 951 if err != nil { 952 // yazma hatası 953 return n, err 954 } 955 _ = f.Close() 956 n++ // bu aşamada veri commit olmuş, değişiklik gerçekleşmiştir. 957 f = nil 958 break // Chunk loop kır. 959 } 960 } //end chunks 961 962 return n, nil 963 } 964 965 // createChunk Creates a new chunk for storing data 966 func (coll *Coll) createChunk() (*fs.FileInfo, error) { 967 lastChunk, _ := coll.getLastChunk() 968 if lastChunk == nil { 969 // Diskte başka chunk yok 970 chunkPath := filepath.Join(coll.dbpath, firstChunkName) 971 f, err := os.Create(chunkPath) 972 if err != nil { 973 // Dosya oluşturmada hata 974 return nil, err 975 } 976 fstat, _ := f.Stat() 977 defer f.Close() 978 return &fstat, nil 979 } 980 981 // lastChunk var. Bu durumda dosya boyutu kontrol edilir. Eğer maxChunkSize'dan büyükse yeni bir chunk yapılır. 982 if (*lastChunk).Size() > maxChunkSize { 983 // yeni bir chunk yap 984 chunkNrStr := strings.Split((*lastChunk).Name(), ".")[0] 985 chunkNr, err := strconv.ParseUint(chunkNrStr, 16, 32) 986 if err != nil { 987 //dosya adı ile ilgili bir problem 988 return nil, errors.New(fmt.Sprintf("cannot get chunk nr: %s", err.Error())) 989 } 990 //yeni chunk yap 991 chunkNr += 1 992 newChunkName := fmt.Sprintf("%02x.json", chunkNr) 993 chunkPath := filepath.Join(coll.dbpath, newChunkName) 994 f, err := os.Create(chunkPath) 995 if err != nil { 996 return nil, errors.New(fmt.Sprintf("cannot create chunk: %s", err.Error())) 997 } 998 fstat, _ := f.Stat() 999 defer f.Close() 1000 lastChunk = &fstat 1001 } 1002 1003 // Kontroller tamam. Bu chunk kullanılabilir. 1004 return lastChunk, nil 1005 } 1006 1007 // getChunks checks disk storage and returns the chunk files if any. 1008 func (coll *Coll) getChunks() ([]fs.FileInfo, error) { 1009 fileElements, err := ioutil.ReadDir(coll.dbpath) 1010 if err != nil { 1011 return nil, errors.New(fmt.Sprintf("cannot read chunks: %s", err.Error())) 1012 } 1013 1014 // Dosya adları kontrol edilir. 1015 reFileName, _ := regexp.Compile("^[\\da-fA-F]{2}\\.json$") 1016 resultArray := make([]fs.FileInfo, len(fileElements)) 1017 idx := 0 1018 for _, finfo := range fileElements { 1019 if !finfo.IsDir() { 1020 if reFileName.MatchString(finfo.Name()) { 1021 resultArray[idx] = finfo 1022 idx += 1 1023 } 1024 } 1025 } 1026 1027 return resultArray[:idx], nil 1028 } 1029 1030 // getLastChunk returns a chunk to store data if there are any. 1031 func (coll *Coll) getLastChunk() (*fs.FileInfo, error) { 1032 chunks, err := ioutil.ReadDir(coll.dbpath) 1033 if err != nil { 1034 return nil, errors.New(fmt.Sprintf("cannot read chunks: %s", err.Error())) 1035 } 1036 1037 var lastChunk *fs.FileInfo = nil 1038 reFileName, _ := regexp.Compile("^[\\da-fA-F]{2}\\.json$") 1039 1040 for _, finfo := range chunks { 1041 if !finfo.IsDir() { 1042 //aradığımız dosya 1043 if reFileName.MatchString(finfo.Name()) { 1044 lastChunk = &finfo 1045 } 1046 } 1047 } 1048 1049 return lastChunk, nil 1050 }