db.go (7064B)
1 package db 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 9 "git.defalsify.org/vise.git/lang" 10 ) 11 12 const ( 13 safeLock = DATATYPE_BIN | DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD 14 ) 15 16 const ( 17 // Invalid datatype, must raise error if attempted used. 18 DATATYPE_UNKNOWN = 0 19 // Bytecode 20 DATATYPE_BIN = 1 21 // Menu symbol 22 DATATYPE_MENU = 2 23 // Template symbol 24 DATATYPE_TEMPLATE = 4 25 // Static LOAD symbols 26 DATATYPE_STATICLOAD = 8 27 // State and cache from persister 28 DATATYPE_STATE = 16 29 // Application data 30 DATATYPE_USERDATA = 32 31 ) 32 33 const ( 34 datatype_sessioned_threshold = DATATYPE_STATICLOAD 35 ) 36 37 // Db abstracts all data storage and retrieval as a key-value store 38 type Db interface { 39 // Connect prepares the storage backend for use. 40 // 41 // If called more than once, consecutive calls should be ignored. 42 Connect(ctx context.Context, connStr string) error 43 // MUST be called before termination after a Connect(). 44 Close(context.Context) error 45 // Get retrieves the value belonging to a key. 46 // 47 // Errors if the key does not exist, or if the retrieval otherwise fails. 48 Get(ctx context.Context, key []byte) ([]byte, error) 49 // Put stores a value under a key. 50 // 51 // Any existing value will be replaced. 52 // 53 // Errors if the value could not be stored. 54 Put(ctx context.Context, key []byte, val []byte) error 55 // SetPrefix sets the storage context prefix to use for consecutive Get and Put operations. 56 SetPrefix(pfx uint8) 57 // SetSession sets the session context to use for consecutive Get and Put operations. 58 // 59 // Session only affects the following datatypes: 60 // * DATATYPE_STATE 61 // * DATATYPE_USERSTART 62 SetSession(sessionId string) 63 // SetLock disables modification of data that is readonly in the vm context. 64 // 65 // If called with typ value 0, it will permanently lock all readonly members. 66 SetLock(typ uint8, locked bool) error 67 // Safe returns true if db is safe for use with a vm. 68 Safe() bool 69 // SetLanguage sets the language context to use on consecutive gets or puts 70 // 71 // Language only affects the following datatypes: 72 // * DATATYPE_MENU 73 // * DATATYPE_TEMPLATE 74 // * DATATYPE_STATICLOAD 75 SetLanguage(*lang.Language) 76 // Prefix returns the current active datatype prefix 77 Prefix() uint8 78 Dump(context.Context, []byte) (*Dumper, error) 79 DecodeKey(ctx context.Context, key []byte) ([]byte, error) 80 Start(context.Context) error 81 Stop(context.Context) error 82 Abort(context.Context) 83 Connection() string 84 } 85 86 type LookupKey struct { 87 Default []byte 88 Translation []byte 89 } 90 91 // ToDbKey generates a key to use Db to store a value for a particular context. 92 // 93 // If language is nil, then default language storage context will be used. 94 // 95 // If language is not nil, and the context does not support language, the language value will silently will be ignored. 96 func ToDbKey(typ uint8, b []byte, l *lang.Language) []byte { 97 k := []byte{typ} 98 if l != nil && l.Code != "" && typ&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 99 b = append(b, []byte("_"+l.Code)...) 100 //s += "_" + l.Code 101 } 102 return append(k, b...) 103 } 104 105 func FromDbKey(b []byte) ([]byte, error) { 106 if len(b) < 2 { 107 return nil, fmt.Errorf("invalid db key") 108 } 109 typ := b[0] 110 b = b[1:] 111 if typ&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 112 if len(b) > 6 { 113 if b[len(b)-4] == '_' { 114 b = b[:len(b)-4] 115 } 116 } 117 } 118 return b, nil 119 } 120 121 // baseDb is a base class for all Db implementations. 122 type baseDb struct { 123 pfx uint8 124 sid []byte 125 lock uint8 126 lang *lang.Language 127 seal bool 128 connStr string 129 } 130 131 // DbBase is a base class that must be extended by all db.Db implementers. 132 // 133 // It must be created with NewDbBase() 134 type DbBase struct { 135 *baseDb 136 } 137 138 // NewDbBase instantiates a new DbBase. 139 func NewDbBase() *DbBase { 140 db := &DbBase{ 141 baseDb: &baseDb{}, 142 } 143 db.baseDb.defaultLock() 144 return db 145 } 146 147 // ensures default locking of read-only entries 148 func (db *baseDb) defaultLock() { 149 db.lock |= safeLock 150 } 151 152 func (bd *DbBase) Safe() bool { 153 return bd.baseDb.lock&safeLock == safeLock 154 } 155 156 func (bd *DbBase) Prefix() uint8 { 157 return bd.baseDb.pfx 158 } 159 160 // SetPrefix implements the Db interface. 161 func (bd *DbBase) SetPrefix(pfx uint8) { 162 bd.baseDb.pfx = pfx 163 } 164 165 // SetLanguage implements the Db interface. 166 func (bd *DbBase) SetLanguage(ln *lang.Language) { 167 bd.baseDb.lang = ln 168 } 169 170 // SetSession implements the Db interface. 171 func (bd *DbBase) SetSession(sessionId string) { 172 if sessionId == "" { 173 bd.baseDb.sid = []byte{} 174 } else { 175 bd.baseDb.sid = append([]byte(sessionId), 0x2E) 176 } 177 } 178 179 // SetLock implements the Db interface. 180 func (bd *DbBase) SetLock(pfx uint8, lock bool) error { 181 if bd.baseDb.seal { 182 return errors.New("SetLock on sealed db") 183 } 184 if pfx == 0 { 185 bd.baseDb.defaultLock() 186 bd.baseDb.seal = true 187 return nil 188 } 189 if lock { 190 bd.baseDb.lock |= pfx 191 } else { 192 bd.baseDb.lock &= ^pfx 193 } 194 return nil 195 } 196 197 // CheckPut returns true if the current selected data type can be written to. 198 func (bd *DbBase) CheckPut() bool { 199 return bd.baseDb.pfx&bd.baseDb.lock == 0 200 } 201 202 func (bd *DbBase) ToSessionKey(pfx uint8, key []byte) []byte { 203 var b []byte 204 if pfx > datatype_sessioned_threshold { 205 b = append([]byte(bd.sid), key...) 206 } else { 207 b = key 208 } 209 return b 210 } 211 212 func (bd *DbBase) FromSessionKey(key []byte) ([]byte, error) { 213 if len(bd.baseDb.sid) == 0 { 214 return key, nil 215 } 216 if !bytes.HasPrefix(key, bd.baseDb.sid) { 217 return nil, fmt.Errorf("session id prefix %s does not match key %x", string(bd.baseDb.sid), key) 218 } 219 return bytes.TrimPrefix(key, bd.baseDb.sid), nil 220 } 221 222 // ToKey creates a DbKey within the current session context. 223 // 224 // TODO: hard to read, clean up 225 func (bd *DbBase) ToKey(ctx context.Context, key []byte) (LookupKey, error) { 226 var ln *lang.Language 227 var lk LookupKey 228 //var b []byte 229 db := bd.baseDb 230 if db.pfx == DATATYPE_UNKNOWN { 231 return lk, errors.New("datatype prefix cannot be UNKNOWN") 232 } 233 //b := ToSessionKey(db.pfx, db.sid, key) 234 b := bd.ToSessionKey(db.pfx, key) 235 lk.Default = ToDbKey(db.pfx, b, nil) 236 if db.pfx&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 237 if db.lang != nil { 238 ln = db.lang 239 } else { 240 lo, ok := ctx.Value("Language").(lang.Language) 241 if ok { 242 ln = &lo 243 } 244 } 245 logg.TraceCtxf(ctx, "language using", "ln", ln) 246 if ln != nil { 247 lk.Translation = ToDbKey(db.pfx, b, ln) 248 } 249 } 250 logg.TraceCtxf(ctx, "made db lookup key", "lk", lk.Default, "pfx", db.pfx) 251 return lk, nil 252 } 253 254 func (bd *DbBase) DecodeKey(ctx context.Context, key []byte) ([]byte, error) { 255 var err error 256 oldKey := key 257 key, err = FromDbKey(key) 258 if err != nil { 259 return []byte{}, err 260 } 261 key, err = bd.FromSessionKey(key) 262 if err != nil { 263 return []byte{}, err 264 } 265 logg.DebugCtxf(ctx, "decoded key", "key", key, "fromkey", oldKey) 266 return key, nil 267 } 268 269 func (bd *DbBase) Start(ctx context.Context) error { 270 return nil 271 } 272 273 func (bd *DbBase) Stop(ctx context.Context) error { 274 return nil 275 } 276 277 func (bd *DbBase) Abort(ctx context.Context) { 278 } 279 280 func (bd *DbBase) Connect(ctx context.Context, connStr string) error { 281 bd.connStr = connStr 282 return nil 283 } 284 285 func (bd *DbBase) Connection() string { 286 return bd.connStr 287 }