package kvstore import ( "database/sql" "sync" "time" ) type writer struct { db *sql.DB modQ chan modJob stop chan struct{} wg sync.WaitGroup } func newWriter(db *sql.DB) *writer { return &writer{ db: db, stop: make(chan struct{}, 1), modQ: make(chan modJob, 1024), } } func (w *writer) Start(maxSeqNum uint64) { w.wg.Add(1) go w.run(maxSeqNum) } func (w *writer) Stop() { select { case w.stop <- struct{}{}: default: } w.wg.Wait() } func (w *writer) Store(collection string, id uint64, data []byte) { job := modJob{ Collection: collection, ID: id, Store: true, Data: data, Ready: &sync.WaitGroup{}, } job.Ready.Add(1) w.modQ <- job job.Ready.Wait() } func (w *writer) Delete(collection string, id uint64) { job := modJob{ Collection: collection, ID: id, Store: false, Ready: &sync.WaitGroup{}, } job.Ready.Add(1) w.modQ <- job job.Ready.Wait() } func (w *writer) StoreAsync(collection string, id uint64, data []byte) { w.modQ <- modJob{ Collection: collection, ID: id, Store: true, Data: data, } } func (w *writer) DeleteAsync(collection string, id uint64) { w.modQ <- modJob{ Collection: collection, ID: id, Store: false, } } func (w *writer) run(maxSeqNum uint64) { defer w.wg.Done() var ( job modJob tx *sql.Tx insertData *sql.Stmt insertKV *sql.Stmt deleteData *sql.Stmt deleteKV *sql.Stmt insertLog *sql.Stmt err error newSeqNum uint64 now int64 wgs = make([]*sync.WaitGroup, 10) ) BEGIN: insertData = nil deleteData = nil newSeqNum = maxSeqNum wgs = wgs[:0] select { case job = <-w.modQ: case <-w.stop: return } tx, err = w.db.Begin() must(err) now = time.Now().Unix() insertLog, err = tx.Prepare(sqlInsertLog) must(err) LOOP: if job.Ready != nil { wgs = append(wgs, job.Ready) } newSeqNum++ if job.Store { goto STORE } else { goto DELETE } STORE: if insertData == nil { insertData, err = tx.Prepare(sqlInsertData) must(err) insertKV, err = tx.Prepare(sqlInsertKV) must(err) } _, err = insertData.Exec(newSeqNum, job.Data) must(err) _, err = insertKV.Exec(job.Collection, job.ID, newSeqNum) must(err) _, err = insertLog.Exec(newSeqNum, now, job.Collection, job.ID, true) must(err) goto NEXT DELETE: if deleteData == nil { deleteData, err = tx.Prepare(sqlDeleteData) must(err) deleteKV, err = tx.Prepare(sqlDeleteKV) must(err) } _, err = deleteData.Exec(job.Collection, job.ID) must(err) _, err = deleteKV.Exec(job.Collection, job.ID) must(err) _, err = insertLog.Exec(newSeqNum, now, job.Collection, job.ID, false) must(err) goto NEXT NEXT: select { case job = <-w.modQ: goto LOOP default: } goto COMMIT COMMIT: must(tx.Commit()) maxSeqNum = newSeqNum for i := range wgs { wgs[i].Done() } goto BEGIN }