go-vise

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

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 }