github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/doltdb/hooksdatabase.go (about) 1 // Copyright 2022 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package doltdb 16 17 import ( 18 "context" 19 "io" 20 "sync" 21 22 "github.com/dolthub/dolt/go/store/datas" 23 "github.com/dolthub/dolt/go/store/hash" 24 "github.com/dolthub/dolt/go/store/types" 25 ) 26 27 type hooksDatabase struct { 28 datas.Database 29 postCommitHooks []CommitHook 30 rsc *ReplicationStatusController 31 } 32 33 // CommitHook is an abstraction for executing arbitrary commands after atomic database commits 34 type CommitHook interface { 35 // Execute is arbitrary read-only function whose arguments are new Dataset commit into a specific Database 36 Execute(ctx context.Context, ds datas.Dataset, db datas.Database) (func(context.Context) error, error) 37 // HandleError is an bridge function to handle Execute errors 38 HandleError(ctx context.Context, err error) error 39 // SetLogger lets clients specify an output stream for HandleError 40 SetLogger(ctx context.Context, wr io.Writer) error 41 // ExecuteForWorkingSets returns whether or not the hook should be executed for working set updates 42 ExecuteForWorkingSets() bool 43 } 44 45 // NotifyWaitFailedCommitHook is an optional interface that can be implemented by CommitHooks. 46 // If a commit hook supports this interface, it can be notified if waiting for 47 // replication in the callback returned by |Execute| failed to complete in time 48 // or returned an error. 49 type NotifyWaitFailedCommitHook interface { 50 NotifyWaitFailed() 51 } 52 53 func (db hooksDatabase) SetCommitHooks(ctx context.Context, postHooks []CommitHook) hooksDatabase { 54 db.postCommitHooks = make([]CommitHook, len(postHooks)) 55 copy(db.postCommitHooks, postHooks) 56 return db 57 } 58 59 func (db hooksDatabase) SetCommitHookLogger(ctx context.Context, wr io.Writer) hooksDatabase { 60 for _, h := range db.postCommitHooks { 61 h.SetLogger(ctx, wr) 62 } 63 return db 64 } 65 66 func (db hooksDatabase) withReplicationStatusController(rsc *ReplicationStatusController) hooksDatabase { 67 db.rsc = rsc 68 return db 69 } 70 71 func (db hooksDatabase) PostCommitHooks() []CommitHook { 72 toret := make([]CommitHook, len(db.postCommitHooks)) 73 copy(toret, db.postCommitHooks) 74 return toret 75 } 76 77 func (db hooksDatabase) ExecuteCommitHooks(ctx context.Context, ds datas.Dataset, onlyWS bool) { 78 var wg sync.WaitGroup 79 rsc := db.rsc 80 var ioff int 81 if rsc != nil { 82 ioff = len(rsc.Wait) 83 rsc.Wait = append(rsc.Wait, make([]func(context.Context) error, len(db.postCommitHooks))...) 84 rsc.NotifyWaitFailed = append(rsc.NotifyWaitFailed, make([]func(), len(db.postCommitHooks))...) 85 } 86 for il, hook := range db.postCommitHooks { 87 if !onlyWS || hook.ExecuteForWorkingSets() { 88 i := il 89 hook := hook 90 wg.Add(1) 91 go func() { 92 defer wg.Done() 93 f, err := hook.Execute(ctx, ds, db) 94 if err != nil { 95 hook.HandleError(ctx, err) 96 } 97 if rsc != nil { 98 rsc.Wait[i+ioff] = f 99 if nf, ok := hook.(NotifyWaitFailedCommitHook); ok { 100 rsc.NotifyWaitFailed[i+ioff] = nf.NotifyWaitFailed 101 } else { 102 rsc.NotifyWaitFailed[i+ioff] = func() {} 103 } 104 } 105 }() 106 } 107 } 108 wg.Wait() 109 if rsc != nil { 110 j := ioff 111 for i := ioff; i < len(rsc.Wait); i++ { 112 if rsc.Wait[i] != nil { 113 rsc.Wait[j] = rsc.Wait[i] 114 rsc.NotifyWaitFailed[j] = rsc.NotifyWaitFailed[i] 115 j++ 116 } 117 } 118 rsc.Wait = rsc.Wait[:j] 119 rsc.NotifyWaitFailed = rsc.NotifyWaitFailed[:j] 120 } 121 } 122 123 func (db hooksDatabase) CommitWithWorkingSet( 124 ctx context.Context, 125 commitDS, workingSetDS datas.Dataset, 126 val types.Value, workingSetSpec datas.WorkingSetSpec, 127 prevWsHash hash.Hash, opts datas.CommitOptions, 128 ) (datas.Dataset, datas.Dataset, error) { 129 commitDS, workingSetDS, err := db.Database.CommitWithWorkingSet( 130 ctx, 131 commitDS, 132 workingSetDS, 133 val, 134 workingSetSpec, 135 prevWsHash, 136 opts) 137 if err == nil { 138 db.ExecuteCommitHooks(ctx, commitDS, false) 139 } 140 return commitDS, workingSetDS, err 141 } 142 143 func (db hooksDatabase) Commit(ctx context.Context, ds datas.Dataset, v types.Value, opts datas.CommitOptions) (datas.Dataset, error) { 144 ds, err := db.Database.Commit(ctx, ds, v, opts) 145 if err == nil { 146 db.ExecuteCommitHooks(ctx, ds, false) 147 } 148 return ds, err 149 } 150 151 func (db hooksDatabase) WriteCommit(ctx context.Context, ds datas.Dataset, commit *datas.Commit) (datas.Dataset, error) { 152 ds, err := db.Database.WriteCommit(ctx, ds, commit) 153 if err == nil { 154 db.ExecuteCommitHooks(ctx, ds, false) 155 } 156 return ds, err 157 } 158 159 func (db hooksDatabase) SetHead(ctx context.Context, ds datas.Dataset, newHeadAddr hash.Hash, ws string) (datas.Dataset, error) { 160 ds, err := db.Database.SetHead(ctx, ds, newHeadAddr, ws) 161 if err == nil { 162 db.ExecuteCommitHooks(ctx, ds, false) 163 } 164 return ds, err 165 } 166 167 func (db hooksDatabase) FastForward(ctx context.Context, ds datas.Dataset, newHeadAddr hash.Hash, workingSetPath string) (datas.Dataset, error) { 168 ds, err := db.Database.FastForward(ctx, ds, newHeadAddr, workingSetPath) 169 if err == nil { 170 db.ExecuteCommitHooks(ctx, ds, false) 171 } 172 return ds, err 173 } 174 175 func (db hooksDatabase) Delete(ctx context.Context, ds datas.Dataset, workingSetPath string) (datas.Dataset, error) { 176 ds, err := db.Database.Delete(ctx, ds, workingSetPath) 177 if err == nil { 178 db.ExecuteCommitHooks(ctx, datas.NewHeadlessDataset(ds.Database(), ds.ID()), false) 179 } 180 return ds, err 181 } 182 183 func (db hooksDatabase) UpdateWorkingSet(ctx context.Context, ds datas.Dataset, workingSet datas.WorkingSetSpec, prevHash hash.Hash) (datas.Dataset, error) { 184 ds, err := db.Database.UpdateWorkingSet(ctx, ds, workingSet, prevHash) 185 if err == nil { 186 db.ExecuteCommitHooks(ctx, ds, true) 187 } 188 return ds, err 189 } 190 191 func (db hooksDatabase) Tag(ctx context.Context, ds datas.Dataset, commitAddr hash.Hash, opts datas.TagOptions) (datas.Dataset, error) { 192 ds, err := db.Database.Tag(ctx, ds, commitAddr, opts) 193 if err == nil { 194 db.ExecuteCommitHooks(ctx, ds, false) 195 } 196 return ds, err 197 }