github.com/bhojpur/cache@v0.0.4/pkg/temporal/database.go (about)

     1  package temporal
     2  
     3  // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved.
     4  
     5  // Permission is hereby granted, free of charge, to any person obtaining a copy
     6  // of this software and associated documentation files (the "Software"), to deal
     7  // in the Software without restriction, including without limitation the rights
     8  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     9  // copies of the Software, and to permit persons to whom the Software is
    10  // furnished to do so, subject to the following conditions:
    11  
    12  // The above copyright notice and this permission notice shall be included in
    13  // all copies or substantial portions of the Software.
    14  
    15  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    16  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    17  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    18  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    19  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    20  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    21  // THE SOFTWARE.
    22  
    23  import "C"
    24  import (
    25  	"bytes"
    26  	"encoding/binary"
    27  	"fmt"
    28  	"strings"
    29  
    30  	memcache "github.com/bhojpur/cache/pkg/memory"
    31  )
    32  
    33  type MemCacheDB struct {
    34  	db *memcache.DB
    35  }
    36  
    37  func (mem *MemCacheDB) open(config MemCacheConfig) error {
    38  	var err error
    39  	mode := config.Mode
    40  	if mode == 0 {
    41  		mode = 0600
    42  	}
    43  	mem.db, err = memcache.Open(config.Path, mode, nil)
    44  	return err
    45  }
    46  
    47  func (mem MemCacheDB) Close() error {
    48  	return mem.db.Close()
    49  }
    50  
    51  // This function converts a floating point number to a bytearray
    52  func timeToByteArr(timeVal int64) []byte {
    53  	buff := make([]byte, 8)
    54  	binary.BigEndian.PutUint64(buff, uint64(timeVal))
    55  
    56  	return buff
    57  
    58  }
    59  
    60  // This function converts a bytearray floating point number
    61  func byteArrToTime(byteArr []byte) int64 {
    62  	//This is set to bigendian so that the timestamp is sorted in binary format.
    63  	timeVal := int64(binary.BigEndian.Uint64(byteArr))
    64  	return timeVal
    65  }
    66  
    67  func (mem MemCacheDB) Create(name string) error {
    68  	if name == "" {
    69  		return fmt.Errorf("time Series record with Empty name")
    70  	}
    71  	return mem.db.Update(func(tx *memcache.Tx) error {
    72  		_, err := tx.CreateBucket([]byte(name))
    73  		if err != nil {
    74  			return fmt.Errorf("create bucket: %s", err)
    75  		}
    76  		return nil
    77  	})
    78  }
    79  func (mem MemCacheDB) Add(name string, timeseries TimeSeries) error {
    80  	if name == "" {
    81  		return fmt.Errorf("Time Series record with empty name")
    82  	}
    83  	if err := mem.db.Batch(func(tx *memcache.Tx) error {
    84  		b, err := tx.CreateBucketIfNotExists([]byte(name))
    85  		if err != nil {
    86  			return err
    87  		}
    88  		for _, entry := range timeseries {
    89  
    90  			err = b.Put(timeToByteArr(entry.Time), entry.Value)
    91  			if err != nil {
    92  				return err
    93  			}
    94  		}
    95  		return nil
    96  	}); err != nil {
    97  		return err
    98  	}
    99  	return nil
   100  }
   101  
   102  func (mem MemCacheDB) Query(q Query) (timeSeries TimeSeries, nextEntry *int64, err error) {
   103  	timeSeries = make([]TimeEntry, 0, q.MaxEntries)
   104  
   105  	nextEntry = nil
   106  	err = mem.db.View(func(tx *memcache.Tx) error {
   107  
   108  		b := tx.Bucket([]byte(q.Series))
   109  		if b == nil {
   110  			return fmt.Errorf("Bucket:%v does not exist", q.Series)
   111  		}
   112  
   113  		c := b.Cursor()
   114  
   115  		//Default case : If the sorting is descending
   116  		first := q.To
   117  		last := q.From
   118  		next := c.Prev
   119  		loopCondition := func(val int64, last int64) bool {
   120  			return val >= last
   121  		}
   122  		//else
   123  		if strings.Compare(q.Sort, ASC) == 0 {
   124  			first = q.From
   125  			last = q.To
   126  			next = c.Next
   127  			loopCondition = func(val int64, last int64) bool {
   128  				return val <= last
   129  			}
   130  
   131  		}
   132  
   133  		count := 0
   134  		// Iterate over the time values
   135  		var k, v []byte
   136  		for k, v = c.Seek(timeToByteArr(first)); k != nil && loopCondition(byteArrToTime(k), last) && count < q.MaxEntries; k, v = next() {
   137  			record := TimeEntry{byteArrToTime(k), v}
   138  			timeSeries = append(timeSeries, record)
   139  			count = count + 1
   140  		}
   141  		if count == q.MaxEntries && k != nil && loopCondition(byteArrToTime(k), last) {
   142  			ne := byteArrToTime(k)
   143  			nextEntry = &ne
   144  		}
   145  		return nil
   146  	})
   147  
   148  	if err != nil {
   149  		return TimeSeries{}, nil, err
   150  	}
   151  
   152  	return timeSeries, nextEntry, nil
   153  }
   154  
   155  func (mem MemCacheDB) QueryOnChannel(q Query) (<-chan TimeEntry, chan *int64, chan error) {
   156  	resultCh := make(chan TimeEntry, 10)
   157  	errorCh := make(chan error)
   158  	nextEntryChan := make(chan *int64)
   159  
   160  	go func() {
   161  		var nextEntry *int64
   162  		err := mem.db.View(func(tx *memcache.Tx) error {
   163  
   164  			b := tx.Bucket([]byte(q.Series))
   165  			if b == nil {
   166  				return fmt.Errorf("Bucket:%v does not exist", q.Series)
   167  			}
   168  
   169  			c := b.Cursor()
   170  			count := 0
   171  			if q.Sort == DESC {
   172  				k, v := c.Seek(timeToByteArr(q.To))
   173  				if k == nil { //if the seek value is beyond the last entry then go to the last entry
   174  					k, v = c.Last()
   175  				}
   176  
   177  				start := timeToByteArr(q.From)
   178  				// Iterate over the time values
   179  				for ; k != nil && bytes.Compare(k, start) >= 0 && count != q.MaxEntries; k, v = c.Prev() {
   180  					record := TimeEntry{byteArrToTime(k), v}
   181  					resultCh <- record
   182  					count++
   183  				}
   184  				if count == q.MaxEntries && k != nil && bytes.Compare(k, start) >= 0 {
   185  					ne := byteArrToTime(k)
   186  					nextEntry = &ne
   187  				}
   188  			} else {
   189  				k, v := c.Seek(timeToByteArr(q.From))
   190  				last := timeToByteArr(q.To)
   191  				// Iterate over the time values
   192  				for ; k != nil && bytes.Compare(k, last) <= 0 && count != q.MaxEntries; k, v = c.Next() {
   193  					record := TimeEntry{byteArrToTime(k), v}
   194  					resultCh <- record
   195  					count = count + 1
   196  				}
   197  				if count == q.MaxEntries && k != nil && bytes.Compare(k, last) <= 0 {
   198  					ne := byteArrToTime(k)
   199  					nextEntry = &ne
   200  				}
   201  			}
   202  
   203  			return nil
   204  		})
   205  
   206  		//make sure you close the resultchannel before error channel
   207  		close(resultCh)
   208  		nextEntryChan <- nextEntry
   209  
   210  		if err != nil {
   211  			errorCh <- err
   212  		}
   213  		close(errorCh)
   214  	}()
   215  
   216  	return resultCh, nextEntryChan, errorCh
   217  }
   218  
   219  func (mem MemCacheDB) GetPages(q Query) ([]int64, int, error) {
   220  	keyList := make([]int64, 0, 100)
   221  	count := 0
   222  
   223  	err := mem.db.View(func(tx *memcache.Tx) error {
   224  		b := tx.Bucket([]byte(q.Series))
   225  		if b == nil {
   226  			return fmt.Errorf("Bucket:%v does not exist", q.Series)
   227  		}
   228  
   229  		c := b.Cursor()
   230  
   231  		first := q.To
   232  		last := q.From
   233  		next := c.Prev
   234  		loopCondition := func(val int64, last int64) bool {
   235  			return val >= last
   236  		}
   237  		if strings.Compare(q.Sort, ASC) == 0 {
   238  			first = q.From
   239  			last = q.To
   240  			next = c.Next
   241  			loopCondition = func(val int64, last int64) bool {
   242  				return val <= last
   243  			}
   244  
   245  		}
   246  
   247  		// Iterate over the time values
   248  		var k []byte
   249  
   250  		for k, _ = c.Seek(timeToByteArr(first)); k != nil && loopCondition(byteArrToTime(k), last); k, _ = next() {
   251  			if count%q.MaxEntries == 0 {
   252  				keyList = append(keyList, byteArrToTime(k))
   253  			}
   254  			count = count + 1
   255  		}
   256  		return nil
   257  	})
   258  
   259  	if err != nil {
   260  		return nil, 0, err
   261  	}
   262  	return keyList, count, nil
   263  }
   264  
   265  func (mem MemCacheDB) Get(series string) (TimeSeries, error) {
   266  	timeSeries := make([]TimeEntry, 0, 100)
   267  	err := mem.db.View(func(tx *memcache.Tx) error {
   268  
   269  		b := tx.Bucket([]byte(series))
   270  		if b == nil {
   271  			return fmt.Errorf("Bucket:%v does not exist", series)
   272  		}
   273  
   274  		err := b.ForEach(func(k, v []byte) error {
   275  
   276  			record := TimeEntry{byteArrToTime(k), v}
   277  			// TODO: 1. It is an inefficient way of keeping the slices.
   278  			// This has to be addressed during the pagination implementation
   279  			timeSeries = append(timeSeries, record)
   280  			return nil
   281  		})
   282  		if err != nil {
   283  			return err
   284  		}
   285  		return err
   286  	})
   287  
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	return timeSeries, err
   293  }
   294  
   295  func (mem MemCacheDB) GetOnChannel(series string) (<-chan TimeEntry, chan error) {
   296  
   297  	resultCh := make(chan TimeEntry, 10)
   298  	errorCh := make(chan error)
   299  	go func() {
   300  		//defer close(resultCh)
   301  		defer close(errorCh)
   302  		err := mem.db.View(func(tx *memcache.Tx) error {
   303  
   304  			b := tx.Bucket([]byte(series))
   305  			if b == nil {
   306  				return fmt.Errorf("Bucket:%v does not exist", series)
   307  			}
   308  
   309  			err := b.ForEach(func(k, v []byte) error {
   310  				record := TimeEntry{byteArrToTime(k), v}
   311  				resultCh <- record
   312  				return nil
   313  			})
   314  			if err != nil {
   315  				return err
   316  			}
   317  			return err
   318  		})
   319  		// make sure you close the resultchannel before error channel
   320  		close(resultCh)
   321  		if err != nil {
   322  			errorCh <- err
   323  			return
   324  		}
   325  	}()
   326  
   327  	return resultCh, errorCh
   328  }
   329  
   330  func (mem MemCacheDB) Delete(series string) error {
   331  	return mem.db.Update(func(tx *memcache.Tx) error {
   332  		err := tx.DeleteBucket([]byte(series))
   333  		if err == memcache.ErrBucketNotFound {
   334  			return ErrSeriesNotFound
   335  		}
   336  		return err
   337  	})
   338  }