github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/replication.go (about) 1 // Copyright 2021 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 sqle 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 "github.com/sirupsen/logrus" 24 25 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 26 "github.com/dolthub/dolt/go/libraries/doltcore/env" 27 "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" 28 "github.com/dolthub/dolt/go/libraries/doltcore/table/editor" 29 "github.com/dolthub/dolt/go/store/types" 30 ) 31 32 func getPushOnWriteHook(ctx context.Context, bThreads *sql.BackgroundThreads, dEnv *env.DoltEnv, logger io.Writer) (doltdb.CommitHook, error) { 33 _, val, ok := sql.SystemVariables.GetGlobal(dsess.ReplicateToRemote) 34 if !ok { 35 return nil, sql.ErrUnknownSystemVariable.New(dsess.ReplicateToRemote) 36 } else if val == "" { 37 return nil, nil 38 } 39 40 remoteName, ok := val.(string) 41 if !ok { 42 return nil, sql.ErrInvalidSystemVariableValue.New(val) 43 } 44 45 remotes, err := dEnv.GetRemotes() 46 if err != nil { 47 return nil, err 48 } 49 50 rem, ok := remotes.Get(remoteName) 51 if !ok { 52 return nil, fmt.Errorf("%w: '%s'", env.ErrRemoteNotFound, remoteName) 53 } 54 55 ddb, err := rem.GetRemoteDB(ctx, types.Format_Default, dEnv) 56 if err != nil { 57 return nil, err 58 } 59 60 tmpDir, err := dEnv.TempTableFilesDir() 61 if err != nil { 62 return nil, err 63 } 64 if _, val, ok = sql.SystemVariables.GetGlobal(dsess.AsyncReplication); ok && val == dsess.SysVarTrue { 65 return doltdb.NewAsyncPushOnWriteHook(bThreads, ddb, tmpDir, logger) 66 } 67 68 return doltdb.NewPushOnWriteHook(ddb, tmpDir), nil 69 } 70 71 // GetCommitHooks creates a list of hooks to execute on database commit. Hooks that cannot be created because of an 72 // error in configuration will not prevent the server from starting, and will instead log errors. 73 func GetCommitHooks(ctx context.Context, bThreads *sql.BackgroundThreads, dEnv *env.DoltEnv, logger io.Writer) ([]doltdb.CommitHook, error) { 74 postCommitHooks := make([]doltdb.CommitHook, 0) 75 76 hook, err := getPushOnWriteHook(ctx, bThreads, dEnv, logger) 77 if err != nil { 78 path, _ := dEnv.FS.Abs(".") 79 logrus.Errorf("error loading replication for database at %s, replication disabled: %v", path, err) 80 postCommitHooks = append(postCommitHooks, doltdb.NewLogHook([]byte(err.Error()+"\n"))) 81 } else if hook != nil { 82 postCommitHooks = append(postCommitHooks, hook) 83 } 84 85 for _, h := range postCommitHooks { 86 _ = h.SetLogger(ctx, logger) 87 } 88 return postCommitHooks, nil 89 } 90 91 // newReplicaDatabase creates a new dsqle.ReadReplicaDatabase. If the doltdb.SkipReplicationErrorsKey global variable is set, 92 // skip errors related to database construction only and return a partially functional dsqle.ReadReplicaDatabase 93 // that will log warnings when attempting to perform replica commands. 94 func newReplicaDatabase(ctx context.Context, name string, remoteName string, dEnv *env.DoltEnv) (ReadReplicaDatabase, error) { 95 opts := editor.Options{ 96 Deaf: dEnv.DbEaFactory(), 97 } 98 99 db, err := NewDatabase(ctx, name, dEnv.DbData(), opts) 100 if err != nil { 101 return ReadReplicaDatabase{}, err 102 } 103 104 rrd, err := NewReadReplicaDatabase(ctx, db, remoteName, dEnv) 105 if err != nil { 106 err = fmt.Errorf("%s from remote '%s'; %w", ErrFailedToLoadReplicaDB.Error(), remoteName, err) 107 return ReadReplicaDatabase{}, err 108 } 109 110 if sqlCtx, ok := ctx.(*sql.Context); ok { 111 sqlCtx.GetLogger().Infof( 112 "replication enabled for database '%s' from remote '%s'", name, remoteName) 113 } 114 115 return rrd, nil 116 } 117 118 func ApplyReplicationConfig(ctx context.Context, bThreads *sql.BackgroundThreads, mrEnv *env.MultiRepoEnv, logger io.Writer, dbs ...dsess.SqlDatabase) ([]dsess.SqlDatabase, error) { 119 outputDbs := make([]dsess.SqlDatabase, len(dbs)) 120 for i, db := range dbs { 121 dEnv := mrEnv.GetEnv(db.Name()) 122 if dEnv == nil { 123 outputDbs[i] = db 124 continue 125 } 126 postCommitHooks, err := GetCommitHooks(ctx, bThreads, dEnv, logger) 127 if err != nil { 128 return nil, err 129 } 130 dEnv.DoltDB.SetCommitHooks(ctx, postCommitHooks) 131 132 if _, remote, ok := sql.SystemVariables.GetGlobal(dsess.ReadReplicaRemote); ok && remote != "" { 133 remoteName, ok := remote.(string) 134 if !ok { 135 return nil, sql.ErrInvalidSystemVariableValue.New(remote) 136 } 137 rdb, err := newReplicaDatabase(ctx, db.Name(), remoteName, dEnv) 138 if err == nil { 139 db = rdb 140 } else { 141 logrus.Errorf("invalid replication configuration, replication disabled: %v", err) 142 } 143 } 144 145 outputDbs[i] = db 146 } 147 return outputDbs, nil 148 }