gdbm.go (3268B)
1 package gdbm 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 9 gdbm "github.com/graygnuorg/go-gdbm" 10 11 "git.defalsify.org/vise.git/db" 12 ) 13 14 // gdbmDb is a gdbm backend implementation of the Db interface. 15 type gdbmDb struct { 16 *db.DbBase 17 conn *gdbm.Database 18 readOnly bool 19 prefix uint8 20 it gdbm.DatabaseIterator 21 itBase []byte 22 } 23 24 // Creates a new gdbm backed Db implementation. 25 func NewGdbmDb() *gdbmDb { 26 db := &gdbmDb{ 27 DbBase: db.NewDbBase(), 28 } 29 return db 30 } 31 32 // Base implements Db 33 func (gdb *gdbmDb) Base() *db.DbBase { 34 return gdb.DbBase 35 } 36 37 // WithReadOnly sets database as read only. 38 // 39 // There may exist more than one instance of read-only 40 // databases to the same file at the same time. 41 // However, only one single write database. 42 // 43 // Readonly cannot be set when creating a new database. 44 func (gdb *gdbmDb) WithReadOnly() *gdbmDb { 45 gdb.readOnly = true 46 return gdb 47 } 48 49 // String implements the string interface. 50 func (gdb *gdbmDb) String() string { 51 fn, err := gdb.conn.FileName() 52 if err != nil { 53 fn = "??" 54 } 55 return "gdbmdb: " + fn 56 } 57 58 // Connect implements Db 59 func (gdb *gdbmDb) Connect(ctx context.Context, connStr string) error { 60 if gdb.conn != nil { 61 logg.WarnCtxf(ctx, "already connected", "conn", gdb.conn) 62 return nil 63 } 64 var db *gdbm.Database 65 cfg := gdbm.DatabaseConfig{ 66 FileName: connStr, 67 Flags: gdbm.OF_NOLOCK | gdbm.OF_PREREAD, 68 FileMode: 0600, 69 } 70 71 _, err := os.Stat(connStr) 72 if err != nil { 73 if !errors.Is(err.(*os.PathError).Unwrap(), os.ErrNotExist) { 74 return fmt.Errorf("db path lookup err: %v", err) 75 } 76 if gdb.readOnly { 77 return fmt.Errorf("cannot open new database readonly") 78 } 79 cfg.Mode = gdbm.ModeReader | gdbm.ModeWrcreat 80 } else { 81 cfg.Mode = gdbm.ModeReader 82 if !gdb.readOnly { 83 cfg.Mode |= gdbm.ModeWriter 84 } 85 } 86 db, err = gdbm.OpenConfig(cfg) 87 if err != nil { 88 return fmt.Errorf("db open err: %v", err) 89 } 90 logg.DebugCtxf(ctx, "gdbm connected", "connstr", connStr) 91 gdb.conn = db 92 gdb.DbBase.Connect(ctx, connStr) 93 return nil 94 } 95 96 // Put implements Db 97 func (gdb *gdbmDb) Put(ctx context.Context, key []byte, val []byte) error { 98 if !gdb.CheckPut() { 99 return errors.New("unsafe put and safety set") 100 } 101 lk, err := gdb.ToKey(ctx, key) 102 if err != nil { 103 return err 104 } 105 logg.TraceCtxf(ctx, "gdbm put", "key", key, "lk", lk, "val", val) 106 if lk.Translation != nil { 107 return gdb.conn.Store(lk.Translation, val, true) 108 } 109 return gdb.conn.Store(lk.Default, val, true) 110 } 111 112 // Get implements Db 113 func (gdb *gdbmDb) Get(ctx context.Context, key []byte) ([]byte, error) { 114 var v []byte 115 lk, err := gdb.ToKey(ctx, key) 116 if err != nil { 117 return nil, err 118 } 119 if lk.Translation != nil { 120 v, err = gdb.conn.Fetch(lk.Translation) 121 if err != nil { 122 if !errors.Is(gdbm.ErrItemNotFound, err) { 123 return nil, err 124 } 125 } 126 return v, nil 127 } 128 v, err = gdb.conn.Fetch(lk.Default) 129 logg.TraceCtxf(ctx, "gdbm get", "key", key, "lk", lk.Default) 130 if err != nil { 131 if errors.Is(gdbm.ErrItemNotFound, err) { 132 return nil, db.NewErrNotFound(key) 133 } 134 return nil, err 135 } 136 logg.TraceCtxf(ctx, "gdbm get", "key", key, "lk", lk, "val", v) 137 return v, nil 138 } 139 140 // Close implements Db 141 func (gdb *gdbmDb) Close(ctx context.Context) error { 142 logg.TraceCtxf(ctx, "closing gdbm", "path", gdb.conn) 143 return gdb.conn.Close() 144 }