db.go (5216B)
1 package db 2 3 import ( 4 "context" 5 "errors" 6 7 "git.defalsify.org/vise.git/lang" 8 ) 9 10 const ( 11 safeLock = DATATYPE_BIN | DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD 12 ) 13 14 const ( 15 // Invalid datatype, must raise error if attempted used. 16 DATATYPE_UNKNOWN = 0 17 // Bytecode 18 DATATYPE_BIN = 1 19 // Menu symbol 20 DATATYPE_MENU = 2 21 // Template symbol 22 DATATYPE_TEMPLATE = 4 23 // Static LOAD symbols 24 DATATYPE_STATICLOAD = 8 25 // State and cache from persister 26 DATATYPE_STATE = 16 27 // Application data 28 DATATYPE_USERDATA = 32 29 ) 30 31 const ( 32 datatype_sessioned_threshold = DATATYPE_STATICLOAD 33 ) 34 35 // Db abstracts all data storage and retrieval as a key-value store 36 type Db interface { 37 // Connect prepares the storage backend for use. 38 // 39 // If called more than once, consecutive calls should be ignored. 40 Connect(ctx context.Context, connStr string) error 41 // Close implements io.Closer. 42 // 43 // MUST be called before termination after a Connect(). 44 Close() 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 } 79 80 type LookupKey struct { 81 Default []byte 82 Translation []byte 83 } 84 85 // ToDbKey generates a key to use Db to store a value for a particular context. 86 // 87 // If language is nil, then default language storage context will be used. 88 // 89 // If language is not nil, and the context does not support language, the language value will silently will be ignored. 90 func ToDbKey(typ uint8, b []byte, l *lang.Language) []byte { 91 k := []byte{typ} 92 if l != nil && l.Code != "" && typ & (DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD) > 0 { 93 b = append(b, []byte("_" + l.Code)...) 94 //s += "_" + l.Code 95 } 96 return append(k, b...) 97 } 98 99 // baseDb is a base class for all Db implementations. 100 type baseDb struct { 101 pfx uint8 102 sid []byte 103 lock uint8 104 lang *lang.Language 105 seal bool 106 } 107 108 // DbBase is a base class that must be extended by all db.Db implementers. 109 // 110 // It must be created with NewDbBase() 111 type DbBase struct { 112 *baseDb 113 } 114 115 // NewDbBase instantiates a new DbBase. 116 func NewDbBase() *DbBase { 117 db := &DbBase{ 118 baseDb: &baseDb{}, 119 } 120 db.baseDb.defaultLock() 121 return db 122 } 123 124 // ensures default locking of read-only entries 125 func(db *baseDb) defaultLock() { 126 db.lock |= safeLock 127 } 128 129 func(bd *DbBase) Safe() bool { 130 return bd.baseDb.lock & safeLock == safeLock 131 } 132 133 func(bd *DbBase) Prefix() uint8 { 134 return bd.baseDb.pfx 135 } 136 137 // SetPrefix implements the Db interface. 138 func(bd *DbBase) SetPrefix(pfx uint8) { 139 bd.baseDb.pfx = pfx 140 } 141 142 // SetLanguage implements the Db interface. 143 func(bd *DbBase) SetLanguage(ln *lang.Language) { 144 bd.baseDb.lang = ln 145 } 146 // SetSession implements the Db interface. 147 func(bd *DbBase) SetSession(sessionId string) { 148 bd.baseDb.sid = append([]byte(sessionId), 0x2E) 149 } 150 151 // SetLock implements the Db interface. 152 func(bd *DbBase) SetLock(pfx uint8, lock bool) error { 153 if bd.baseDb.seal { 154 return errors.New("SetLock on sealed db") 155 } 156 if pfx == 0 { 157 bd.baseDb.defaultLock() 158 bd.baseDb.seal = true 159 return nil 160 } 161 if lock { 162 bd.baseDb.lock |= pfx 163 } else { 164 bd.baseDb.lock &= ^pfx 165 } 166 return nil 167 } 168 169 // CheckPut returns true if the current selected data type can be written to. 170 func(bd *DbBase) CheckPut() bool { 171 return bd.baseDb.pfx & bd.baseDb.lock == 0 172 } 173 174 175 // ToKey creates a DbKey within the current session context. 176 // 177 // TODO: hard to read, clean up 178 func(bd *DbBase) ToKey(ctx context.Context, key []byte) (LookupKey, error) { 179 var ln *lang.Language 180 var lk LookupKey 181 var b []byte 182 db := bd.baseDb 183 if db.pfx == DATATYPE_UNKNOWN { 184 return lk, errors.New("datatype prefix cannot be UNKNOWN") 185 } 186 if (db.pfx > datatype_sessioned_threshold) { 187 b = append(db.sid, key...) 188 } else { 189 b = key 190 } 191 lk.Default = ToDbKey(db.pfx, b, nil) 192 if db.pfx & (DATATYPE_MENU | DATATYPE_TEMPLATE | DATATYPE_STATICLOAD) > 0 { 193 if db.lang != nil { 194 ln = db.lang 195 } else { 196 lo, ok := ctx.Value("Language").(lang.Language) 197 if ok { 198 ln = &lo 199 } 200 } 201 logg.TraceCtxf(ctx, "language using", "ln", ln) 202 if ln != nil { 203 lk.Translation = ToDbKey(db.pfx, b, ln) 204 } 205 } 206 return lk, nil 207 }