db.go (8777B)
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 generates an iterable dump of all keys matching the given byte prefix. 79 Dump(context.Context, []byte) (*Dumper, error) 80 // DecodeKey decodes the database specific key used for internal storage to the original key given by the caller. 81 DecodeKey(ctx context.Context, key []byte) ([]byte, error) 82 // Start creates a new database transaction. Only relevant for transactional databases. 83 Start(context.Context) error 84 // Stop completes a database transaction. Only relevant for transactional databases. 85 Stop(context.Context) error 86 // Abort cancels a database transaction. Only relevant for transactional databases. 87 Abort(context.Context) 88 // Connection returns the complete connection string used by the database implementation to connect. 89 Connection() string 90 // Base returns the underlying DbBase 91 Base() *DbBase 92 } 93 94 // LookupKey encapsulates two keys for a database entry; one for the default language, the other for the language in the context at which the LookupKey was generated. 95 type LookupKey struct { 96 Default []byte 97 Translation []byte 98 } 99 100 // ToDbKey generates a key to use Db to store a value for a particular context. 101 // 102 // If language is nil, then default language storage context will be used. 103 // 104 // If language is not nil, and the context does not support language, the language value will silently will be ignored. 105 func ToDbKey(typ uint8, b []byte, l *lang.Language) []byte { 106 k := []byte{typ} 107 if l != nil && l.Code != "" && typ&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 108 b = append(b, []byte("_"+l.Code)...) 109 //s += "_" + l.Code 110 } 111 return append(k, b...) 112 } 113 114 // FromDbKey parses the storage key as used in the database implementation to the original key bytes given by the client code. 115 func FromDbKey(b []byte) ([]byte, error) { 116 if len(b) < 2 { 117 return nil, fmt.Errorf("invalid db key") 118 } 119 typ := b[0] 120 b = b[1:] 121 if typ&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 122 if len(b) > 6 { 123 if b[len(b)-4] == '_' { 124 b = b[:len(b)-4] 125 } 126 } 127 } 128 return b, nil 129 } 130 131 // baseDb is a base class for all Db implementations. 132 type baseDb struct { 133 pfx uint8 134 sid []byte 135 lock uint8 136 lang *lang.Language 137 seal bool 138 connStr string 139 known bool 140 logDb Db 141 } 142 143 // DbBase is a base class that must be extended by all db.Db implementers. 144 // 145 // It must be created with NewDbBase() 146 type DbBase struct { 147 *baseDb 148 } 149 150 // NewDbBase instantiates a new DbBase. 151 func NewDbBase() *DbBase { 152 db := &DbBase{ 153 baseDb: &baseDb{ 154 known: true, 155 }, 156 } 157 db.baseDb.defaultLock() 158 return db 159 } 160 161 // AllowUnknownPrefix disables the error generated when the DATATYPE_UNKNOWN prefix is used for storage. 162 func (db *baseDb) AllowUnknownPrefix() bool { 163 known := db.known 164 if !known { 165 return false 166 } 167 db.known = false 168 return true 169 } 170 171 // ensures default locking of read-only entries 172 func (db *baseDb) defaultLock() { 173 db.lock |= safeLock 174 } 175 176 // Safe returns true if the database has been set up to protect read-only data. 177 func (bd *DbBase) Safe() bool { 178 return bd.baseDb.lock&safeLock == safeLock 179 } 180 181 // Prefix returns the current prefix (last set by SetPrefix) 182 func (bd *DbBase) Prefix() uint8 { 183 return bd.baseDb.pfx 184 } 185 186 // SetPrefix implements the Db interface. 187 func (bd *DbBase) SetPrefix(pfx uint8) { 188 bd.baseDb.pfx = pfx 189 } 190 191 // SetLanguage implements the Db interface. 192 func (bd *DbBase) SetLanguage(ln *lang.Language) { 193 bd.baseDb.lang = ln 194 } 195 196 // SetSession implements the Db interface. 197 func (bd *DbBase) SetSession(sessionId string) { 198 if sessionId == "" { 199 bd.baseDb.sid = []byte{} 200 } else { 201 bd.baseDb.sid = append([]byte(sessionId), 0x2E) 202 } 203 } 204 205 // SetLock implements the Db interface. 206 func (bd *DbBase) SetLock(pfx uint8, lock bool) error { 207 if bd.baseDb.seal { 208 return errors.New("SetLock on sealed db") 209 } 210 if pfx == 0 { 211 bd.baseDb.defaultLock() 212 bd.baseDb.seal = true 213 return nil 214 } 215 if lock { 216 bd.baseDb.lock |= pfx 217 } else { 218 bd.baseDb.lock &= ^pfx 219 } 220 return nil 221 } 222 223 // CheckPut returns true if the current selected data type can be written to. 224 func (bd *DbBase) CheckPut() bool { 225 return bd.baseDb.pfx&bd.baseDb.lock == 0 226 } 227 228 // ToSessionKey applies the currently set session id to the key. 229 // 230 // If the key in pfx does not use session, the key is returned unchanged. 231 func (bd *DbBase) ToSessionKey(pfx uint8, key []byte) []byte { 232 var b []byte 233 if pfx > datatype_sessioned_threshold || pfx == DATATYPE_UNKNOWN { 234 b = append([]byte(bd.sid), key...) 235 } else { 236 b = key 237 } 238 return b 239 } 240 241 // FromSessionKey reverses the effect of ToSessionKey. 242 func (bd *DbBase) FromSessionKey(key []byte) ([]byte, error) { 243 if len(bd.baseDb.sid) == 0 { 244 return key, nil 245 } 246 if !bytes.HasPrefix(key, bd.baseDb.sid) { 247 return nil, fmt.Errorf("session id prefix %s does not match key %x", string(bd.baseDb.sid), key) 248 } 249 return bytes.TrimPrefix(key, bd.baseDb.sid), nil 250 } 251 252 // ToKey creates a DbKey within the current session context. 253 // 254 // TODO: hard to read, clean up 255 func (bd *DbBase) ToKey(ctx context.Context, key []byte) (LookupKey, error) { 256 var ln *lang.Language 257 var lk LookupKey 258 //var b []byte 259 db := bd.baseDb 260 if db.known && db.pfx == DATATYPE_UNKNOWN { 261 return lk, errors.New("datatype prefix cannot be UNKNOWN") 262 } 263 //b := ToSessionKey(db.pfx, db.sid, key) 264 b := bd.ToSessionKey(db.pfx, key) 265 lk.Default = ToDbKey(db.pfx, b, nil) 266 if db.pfx&(DATATYPE_MENU|DATATYPE_TEMPLATE|DATATYPE_STATICLOAD) > 0 { 267 if db.lang != nil { 268 ln = db.lang 269 } else { 270 lo, ok := ctx.Value("Language").(lang.Language) 271 if ok { 272 ln = &lo 273 } 274 } 275 logg.TraceCtxf(ctx, "language using", "ln", ln) 276 if ln != nil { 277 lk.Translation = ToDbKey(db.pfx, b, ln) 278 } 279 } 280 logg.TraceCtxf(ctx, "made db lookup key", "lk", lk.Default, "pfx", db.pfx) 281 return lk, nil 282 } 283 284 // DecodeKey implements Db. 285 func (bd *DbBase) DecodeKey(ctx context.Context, key []byte) ([]byte, error) { 286 var err error 287 oldKey := key 288 key, err = FromDbKey(key) 289 if err != nil { 290 return []byte{}, err 291 } 292 key, err = bd.FromSessionKey(key) 293 if err != nil { 294 return []byte{}, err 295 } 296 logg.DebugCtxf(ctx, "decoded key", "key", key, "fromkey", oldKey) 297 return key, nil 298 } 299 300 // Start implements Db. 301 func (bd *DbBase) Start(ctx context.Context) error { 302 return nil 303 } 304 305 // Stop implements Db. 306 func (bd *DbBase) Stop(ctx context.Context) error { 307 return nil 308 } 309 310 // Abort implements Db. 311 func (bd *DbBase) Abort(ctx context.Context) { 312 } 313 314 // Connect implements Db. 315 func (bd *DbBase) Connect(ctx context.Context, connStr string) error { 316 bd.connStr = connStr 317 return nil 318 } 319 320 // Connection implements Db. 321 func (bd *DbBase) Connection() string { 322 return bd.connStr 323 }