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  }