github.com/scottcagno/storage@v1.8.0/pkg/lsmt/sstable/ss-table.go (about) 1 package sstable 2 3 import ( 4 "fmt" 5 "github.com/scottcagno/storage/pkg/lsmt/binary" 6 "github.com/scottcagno/storage/pkg/util" 7 "io" 8 "os" 9 "path/filepath" 10 "sort" 11 "strconv" 12 ) 13 14 func DataFileNameFromIndex(index int64) string { 15 hexa := strconv.FormatInt(index, 16) 16 return fmt.Sprintf("%s%010s%s", filePrefix, hexa, dataFileSuffix) 17 } 18 19 func IndexFromDataFileName(name string) (int64, error) { 20 hexa := name[len(filePrefix) : len(name)-len(dataFileSuffix)] 21 return strconv.ParseInt(hexa, 16, 32) 22 } 23 24 type SSTable struct { 25 path string 26 file *os.File 27 open bool 28 index *SSTIndex 29 } 30 31 func OpenSSTable(base string, index int64) (*SSTable, error) { 32 // make sure we are working with absolute paths 33 base, err := filepath.Abs(base) 34 if err != nil { 35 return nil, err 36 } 37 // sanitize any path separators 38 base = filepath.ToSlash(base) 39 // create any directories if they are not there 40 err = os.MkdirAll(base, os.ModeDir) 41 if err != nil { 42 return nil, err 43 } 44 // create new data file path 45 path := filepath.Join(base, DataFileNameFromIndex(index)) 46 // open data file 47 file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0666) 48 if err != nil { 49 return nil, err 50 } 51 // init sstable gindex 52 ssi, err := OpenSSTIndex(base, index) 53 if err != nil { 54 return nil, err 55 } 56 // init and return SSTable 57 sst := &SSTable{ 58 path: path, // path is the filepath for the data 59 file: file, // file is the file descriptor for the data 60 open: true, // open reports the status of the file 61 index: ssi, // SSIndex is an SSTableIndex file 62 } 63 return sst, nil 64 } 65 66 func (sst *SSTable) errorCheckFileAndIndex() error { 67 // make sure file is not closed 68 if !sst.open { 69 return binary.ErrFileClosed 70 } 71 // make sure gindex is open 72 if sst.index == nil { 73 util.DEBUG("DEBUG: [SSTable.Index error]\n") 74 return ErrSSTIndexNotFound 75 } 76 return nil 77 } 78 79 func (sst *SSTable) Read(key string) (*binary.Entry, error) { 80 // error check 81 err := sst.errorCheckFileAndIndex() 82 if err != nil { 83 return nil, err 84 } 85 // find gindex using key 86 i, err := sst.index.Find(key) 87 if err != nil { 88 return nil, err 89 } 90 // use gindex offset to read data 91 e, err := binary.DecodeEntryAt(sst.file, i.Offset) 92 if err != nil { 93 return nil, err 94 } 95 // found it 96 return e, nil 97 } 98 99 func (sst *SSTable) ReadIndex(key string) (*binary.Index, error) { 100 // error check 101 err := sst.errorCheckFileAndIndex() 102 if err != nil { 103 return nil, err 104 } 105 // find gindex using key 106 i, err := sst.index.Find(key) 107 if err != nil { 108 return nil, err 109 } 110 // found it 111 return i, nil 112 } 113 114 func (sst *SSTable) ReadAt(offset int64) (*binary.Entry, error) { 115 // error check 116 err := sst.errorCheckFileAndIndex() 117 if err != nil { 118 return nil, err 119 } 120 // use gindex offset to read data 121 e, err := binary.DecodeEntryAt(sst.file, offset) 122 if err != nil { 123 return nil, err 124 } 125 // found it 126 return e, nil 127 } 128 129 func (sst *SSTable) Write(e *binary.Entry) error { 130 // error check 131 err := sst.errorCheckFileAndIndex() 132 if err != nil { 133 return err 134 } 135 // write entry to data file 136 offset, err := binary.EncodeEntry(sst.file, e) 137 if err != nil { 138 return err 139 } 140 // write entry to ss-index 141 err = sst.index.Write(e.Key, offset) 142 if err != nil { 143 return err 144 } 145 return nil 146 } 147 148 func (sst *SSTable) WriteBatch(b *binary.Batch) error { 149 // error check 150 err := sst.errorCheckFileAndIndex() 151 if err != nil { 152 return err 153 } 154 // error check batch 155 if b == nil { 156 return ErrSSTEmptyBatch 157 } 158 // check to see if batch is sorted 159 if !sort.IsSorted(b) { 160 // if not, sort 161 sort.Stable(b) 162 } 163 // range batch and write 164 for i := range b.Entries { 165 // entry 166 e := b.Entries[i] 167 // write entry to data file 168 offset, err := binary.EncodeEntry(sst.file, e) 169 if err != nil { 170 return err 171 } 172 // write entry info to gindex file 173 err = sst.index.Write(e.Key, offset) 174 if err != nil { 175 return err 176 } 177 } 178 return nil 179 } 180 181 func (sst *SSTable) Scan(iter func(e *binary.Entry) bool) error { 182 // error check 183 err := sst.errorCheckFileAndIndex() 184 if err != nil { 185 return err 186 } 187 for { 188 // decode next data entry 189 e, err := binary.DecodeEntry(sst.file) 190 if err != nil { 191 if err == io.EOF || err == io.ErrUnexpectedEOF { 192 break 193 } 194 return err 195 } 196 if !iter(e) { 197 break 198 } 199 } 200 return nil 201 } 202 203 func (sst *SSTable) ScanAt(offset int64, iter func(e *binary.Entry) bool) error { 204 // error check 205 err := sst.errorCheckFileAndIndex() 206 if err != nil { 207 return err 208 } 209 // get current offset, so we can return here when were done 210 cur, err := binary.Offset(sst.file) 211 if err != nil { 212 return err 213 } 214 // seek to provided location 215 _, err = sst.file.Seek(offset, io.SeekStart) 216 if err != nil { 217 return err 218 } 219 for { 220 // decode next data entry 221 e, err := binary.DecodeEntry(sst.file) 222 if err != nil { 223 if err == io.EOF || err == io.ErrUnexpectedEOF { 224 break 225 } 226 return err 227 } 228 if !iter(e) { 229 break 230 } 231 } 232 // go back to where the file was at the beginning 233 _, err = sst.file.Seek(cur, io.SeekStart) 234 if err != nil { 235 return err 236 } 237 return nil 238 } 239 240 func (sst *SSTable) Sync() error { 241 err := sst.file.Sync() 242 if err != nil { 243 return err 244 } 245 return nil 246 } 247 248 func (sst *SSTable) Close() error { 249 if sst.open { 250 err := sst.file.Sync() 251 if err != nil { 252 return err 253 } 254 err = sst.file.Close() 255 if err != nil { 256 return err 257 } 258 } 259 if sst.index != nil { 260 err := sst.index.Close() 261 if err != nil { 262 return err 263 } 264 } 265 sst.open = false 266 return nil 267 } 268 269 func between(data, first, last string) bool { 270 return first <= data && data <= last 271 } 272 273 func (sst *SSTable) KeyInTableRange(k string) bool { 274 return between(k, sst.index.first, sst.index.last) 275 }