github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/dbs/db.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 3 package dbs 4 5 import ( 6 "context" 7 "database/sql" 8 "errors" 9 "fmt" 10 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 11 "github.com/TeaOSLab/EdgeNode/internal/events" 12 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 13 "github.com/TeaOSLab/EdgeNode/internal/utils/fs" 14 _ "github.com/mattn/go-sqlite3" 15 "os" 16 "strings" 17 "sync" 18 "time" 19 ) 20 21 const ( 22 SyncMode = "OFF" 23 ) 24 25 var errDBIsClosed = errors.New("the database is closed") 26 27 type DB struct { 28 locker *fsutils.Locker 29 rawDB *sql.DB 30 dsn string 31 32 statusLocker sync.Mutex 33 countUpdating int32 34 35 isClosing bool 36 37 enableStat bool 38 39 batches []*Batch 40 } 41 42 func OpenWriter(dsn string) (*DB, error) { 43 return open(dsn, true) 44 } 45 46 func OpenReader(dsn string) (*DB, error) { 47 return open(dsn, false) 48 } 49 50 func open(dsn string, lock bool) (*DB, error) { 51 if teaconst.IsQuiting { 52 return nil, errors.New("can not open database when process is quiting") 53 } 54 55 // decode path 56 var path = dsn 57 var queryIndex = strings.Index(dsn, "?") 58 if queryIndex >= 0 { 59 path = path[:queryIndex] 60 } 61 path = strings.TrimSpace(strings.TrimPrefix(path, "file:")) 62 63 // locker 64 var locker *fsutils.Locker 65 if lock { 66 locker = fsutils.NewLocker(path) 67 err := locker.Lock() 68 if err != nil { 69 remotelogs.Warn("DB", "lock '"+path+"' failed: "+err.Error()) 70 locker = nil 71 } 72 } 73 74 // check if closed successfully last time, if not we recover it 75 var walPath = path + "-wal" 76 _, statWalErr := os.Stat(walPath) 77 var shouldRecover = statWalErr == nil 78 79 // open 80 rawDB, err := sql.Open("sqlite3", dsn) 81 if err != nil { 82 return nil, err 83 } 84 85 if shouldRecover { 86 err = rawDB.Close() 87 if err != nil { 88 return nil, err 89 } 90 91 // open again 92 rawDB, err = sql.Open("sqlite3", dsn) 93 if err != nil { 94 return nil, err 95 } 96 } 97 98 var db = NewDB(rawDB, dsn) 99 db.locker = locker 100 return db, nil 101 } 102 103 func NewDB(rawDB *sql.DB, dsn string) *DB { 104 var db = &DB{ 105 rawDB: rawDB, 106 dsn: dsn, 107 } 108 109 events.OnKey(events.EventQuit, fmt.Sprintf("db_%p", db), func() { 110 _ = db.Close() 111 }) 112 events.OnKey(events.EventTerminated, fmt.Sprintf("db_%p", db), func() { 113 _ = db.Close() 114 }) 115 116 return db 117 } 118 119 func (this *DB) SetMaxOpenConns(n int) { 120 this.rawDB.SetMaxOpenConns(n) 121 } 122 123 func (this *DB) EnableStat(b bool) { 124 this.enableStat = b 125 } 126 127 func (this *DB) Begin() (*sql.Tx, error) { 128 // check database status 129 if this.BeginUpdating() { 130 defer this.EndUpdating() 131 } else { 132 return nil, errDBIsClosed 133 } 134 135 return this.rawDB.Begin() 136 } 137 138 func (this *DB) Prepare(query string) (*Stmt, error) { 139 stmt, err := this.rawDB.Prepare(query) 140 if err != nil { 141 return nil, err 142 } 143 144 var s = NewStmt(this, stmt, query) 145 if this.enableStat { 146 s.EnableStat() 147 } 148 return s, nil 149 } 150 151 func (this *DB) ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) { 152 // check database status 153 if this.BeginUpdating() { 154 defer this.EndUpdating() 155 } else { 156 return nil, errDBIsClosed 157 } 158 159 if this.enableStat { 160 defer SharedQueryStatManager.AddQuery(query).End() 161 } 162 163 return this.rawDB.ExecContext(ctx, query, args...) 164 } 165 166 func (this *DB) Exec(query string, args ...any) (sql.Result, error) { 167 // check database status 168 if this.BeginUpdating() { 169 defer this.EndUpdating() 170 } else { 171 return nil, errDBIsClosed 172 } 173 174 if this.enableStat { 175 defer SharedQueryStatManager.AddQuery(query).End() 176 } 177 return this.rawDB.Exec(query, args...) 178 } 179 180 func (this *DB) Query(query string, args ...any) (*sql.Rows, error) { 181 if this.enableStat { 182 defer SharedQueryStatManager.AddQuery(query).End() 183 } 184 return this.rawDB.Query(query, args...) 185 } 186 187 func (this *DB) QueryRow(query string, args ...any) *sql.Row { 188 if this.enableStat { 189 defer SharedQueryStatManager.AddQuery(query).End() 190 } 191 return this.rawDB.QueryRow(query, args...) 192 } 193 194 // Close the database 195 func (this *DB) Close() error { 196 // check database status 197 this.statusLocker.Lock() 198 if this.isClosing { 199 this.statusLocker.Unlock() 200 return nil 201 } 202 this.isClosing = true 203 this.statusLocker.Unlock() 204 205 // waiting for updating operations to finish 206 var maxLoops = 5_000 207 for { 208 this.statusLocker.Lock() 209 var countUpdating = this.countUpdating 210 this.statusLocker.Unlock() 211 if countUpdating <= 0 { 212 break 213 } 214 time.Sleep(1 * time.Millisecond) 215 216 maxLoops-- 217 if maxLoops <= 0 { 218 break 219 } 220 } 221 222 for _, batch := range this.batches { 223 batch.close() 224 } 225 226 events.Remove(fmt.Sprintf("db_%p", this)) 227 228 defer func() { 229 if this.locker != nil { 230 _ = this.locker.Release() 231 } 232 }() 233 234 // print log 235 /**if len(this.dsn) > 0 { 236 u, _ := url.Parse(this.dsn) 237 if u != nil && len(u.Path) > 0 { 238 remotelogs.Debug("DB", "close '"+u.Path) 239 } 240 }**/ 241 242 return this.rawDB.Close() 243 } 244 245 func (this *DB) BeginUpdating() bool { 246 this.statusLocker.Lock() 247 defer this.statusLocker.Unlock() 248 249 if this.isClosing { 250 return false 251 } 252 253 this.countUpdating++ 254 return true 255 } 256 257 func (this *DB) EndUpdating() { 258 this.statusLocker.Lock() 259 this.countUpdating-- 260 this.statusLocker.Unlock() 261 } 262 263 func (this *DB) RawDB() *sql.DB { 264 return this.rawDB 265 }