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 }