go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

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 }