go-vise

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

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 }