github.com/christoph-karpowicz/db_mediator@v0.0.0-20210207102849-61a28a1071d8/internal/server/synch/synch.go (about) 1 /* 2 Package synch handles all data sychronization. 3 */ 4 package synch 5 6 import ( 7 "errors" 8 "fmt" 9 "io/ioutil" 10 "log" 11 "strconv" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/christoph-karpowicz/db_mediator/internal/server/cfg" 17 "github.com/christoph-karpowicz/db_mediator/internal/server/db" 18 ) 19 20 // Synch represents an individual synchronzation configration. 21 // It holds all configuration from an .yaml file, raw and parsed. 22 type Synch struct { 23 id string 24 cfg *cfg.SynchConfig 25 dbStore *dbStore 26 mappings []*Mapping 27 Links []*Link 28 counters *counters 29 stype synchType 30 running bool 31 initial bool 32 simulation bool 33 currentIteration *iteration 34 result *Result 35 } 36 37 // Init prepares the synchronization by fetching all necessary data 38 // and parsing it. 39 func (s *Synch) Init(DBMap map[string]*db.Database, stype string) string { 40 tStart := time.Now() 41 s.id = s.getNewSynchID() 42 stypeField, err := FindSynchType(stype) 43 if err != nil { 44 panic(err) 45 } 46 s.stype = stypeField 47 s.dbStore = &dbStore{} 48 s.result = &Result{} 49 50 if s.counters == nil { 51 s.counters = newCounters() 52 s.dbStore.Init(DBMap, s.cfg.Nodes) 53 s.parseCfgLinks() 54 s.parseCfgMappings() 55 s.parseCfgMatcher() 56 } 57 58 fmt.Println("Synch init finished in: ", time.Since(tStart).String()) 59 return s.id 60 } 61 62 func (s *Synch) getNewSynchID() string { 63 return s.cfg.Name + "-" + strconv.FormatInt(time.Now().UnixNano(), 10) 64 } 65 66 // GetConfig returns the synch config struct. 67 func (s *Synch) GetConfig() *cfg.SynchConfig { 68 return s.cfg 69 } 70 71 // GetIteration returns the synch's current iteration. 72 func (s *Synch) GetIteration() *iteration { 73 return s.currentIteration 74 } 75 76 // GetNodes returns all nodes between which 77 // synchronization takes place. 78 func (s *Synch) GetNodes() map[string]*node { 79 return s.dbStore.nodes 80 } 81 82 func (s *Synch) GetRawMappings() []map[string]string { 83 rawMappings := make([]map[string]string, len(s.mappings)) 84 for i, mapping := range s.mappings { 85 rawMappings[i] = mapping.raw 86 } 87 return rawMappings 88 } 89 90 // GetType returns the type of the synch. 91 func (s *Synch) GetType() synchType { 92 return s.stype 93 } 94 95 func (s *Synch) IsInitial() bool { 96 return s.initial 97 } 98 99 // SetInitial sets the initial struct field indicating whether 100 // it's the first run of the synch. 101 func (s *Synch) SetInitial(ini bool) { 102 s.initial = ini 103 } 104 105 func (s *Synch) IsRunning() bool { 106 return s.running 107 } 108 109 func (s *Synch) IsSimulation() bool { 110 return s.simulation 111 } 112 113 func (s *Synch) SetSimulation(sim bool) { 114 s.simulation = sim 115 } 116 117 // pairData pairs together records that are going to be synchronized. 118 func (s *Synch) pairData() { 119 var wg sync.WaitGroup 120 121 for i := range s.Links { 122 var lnk *Link = s.Links[i] 123 124 wg.Add(1) 125 go lnk.createPairs(&wg) 126 wg.Wait() 127 } 128 } 129 130 func (s *Synch) parseCfgLinks() { 131 var ch chan bool 132 ch = make(chan bool) 133 134 for i, mapping := range s.cfg.Link { 135 go s.parseLink(mapping, i, ch) 136 } 137 138 for i := 0; i < len(s.cfg.Link); i++ { 139 <-ch 140 } 141 } 142 143 func (s *Synch) parseLink(mpngStr string, i int, c chan bool) { 144 rawLink, err := cfg.ParseLink(mpngStr) 145 if err != nil { 146 panic(err) 147 } 148 149 in := createLink(s, rawLink) 150 s.Links = append(s.Links, in) 151 152 c <- true 153 } 154 155 func (s *Synch) parseCfgMappings() { 156 var ch chan bool 157 ch = make(chan bool) 158 159 for i, mapping := range s.cfg.Map { 160 go s.parseMapping(mapping, i, ch) 161 } 162 163 for i := 0; i < len(s.cfg.Map); i++ { 164 <-ch 165 } 166 } 167 168 func (s *Synch) parseMapping(mpngStr string, i int, c chan bool) { 169 rawMpng, err := cfg.ParseMapping(mpngStr) 170 if err != nil { 171 panic(err) 172 } 173 174 mpng := createMapping(s, rawMpng) 175 s.mappings = append(s.mappings, mpng) 176 177 c <- true 178 } 179 180 func (s *Synch) parseCfgMatcher() { 181 matcherMethod := s.GetConfig().Match.Method 182 183 switch matcherMethod { 184 case "ids": 185 parsedMatcher, err := cfg.ParseIdsMatcherMethod(s.GetConfig().Match.Args) 186 if err != nil { 187 panic(err) 188 } 189 190 for _, arg := range parsedMatcher { 191 node, found := s.dbStore.nodes[arg[0]] 192 if !found { 193 panic(errors.New("node name not found")) 194 } 195 196 node.setMatchColumn(arg[1]) 197 } 198 default: 199 panic(errors.New("unknown match method")) 200 } 201 } 202 203 // selectData selects all records from all tables and filters them to get the relevant records. 204 func (s *Synch) selectData() { 205 for i := range s.Links { 206 var lnk *Link = s.Links[i] 207 208 sourceRawActiveRecords := (*lnk.source.db).Select(lnk.source.tbl.name, lnk.sourceWhere) 209 targetRawActiveRecords := (*lnk.target.db).Select(lnk.target.tbl.name, lnk.targetWhere) 210 211 // if !s.initial { 212 // lnk.sourceOldActiveRecords = lnk.sourceActiveRecords 213 // lnk.targetOldActiveRecords = lnk.targetActiveRecords 214 // } 215 216 lnk.sourceTable.setActiveRecords(sourceRawActiveRecords) 217 lnk.targetTable.setActiveRecords(targetRawActiveRecords) 218 219 lnk.sourceTable.activeRecords.setActiveIn(lnk) 220 lnk.targetTable.activeRecords.setActiveIn(lnk) 221 } 222 223 s.counters.selects++ 224 } 225 226 // Run executes a single run of the synchronization. 227 func (s *Synch) Run() { 228 s.running = true 229 230 s.resetIteration() 231 s.selectData() 232 s.pairData() 233 s.synchronize() 234 s.resetLinks() 235 s.finishIteration() 236 } 237 238 func (s *Synch) resetIteration() { 239 s.currentIteration = newIteration(s) 240 } 241 242 func (s *Synch) finishIteration() { 243 s.currentIteration.flush() 244 } 245 246 // Stop stops the synch. 247 func (s *Synch) Stop() { 248 s.running = false 249 } 250 251 // synchronize loops over all pairs in all mappings and invokes their synchronize function. 252 func (s *Synch) synchronize() { 253 for i := range s.Links { 254 var lnk *Link = s.Links[i] 255 256 for k := range lnk.pairs { 257 var pair *Pair = lnk.pairs[k] 258 _, err := pair.Synchronize() 259 if err != nil { 260 log.Println(err) 261 } 262 } 263 } 264 } 265 266 func (s *Synch) resetLinks() { 267 for i := range s.Links { 268 s.Links[i].reset() 269 } 270 } 271 272 func (s *Synch) Flush() *Result { 273 operationsToJSON := s.result.operationsToJSONSlice() 274 operationsToJSONString := strings.Join(operationsToJSON, "\n") 275 if s.IsSimulation() { 276 s.result.setSimulationPath(s.id) 277 } else { 278 s.result.setLogPath(s.id) 279 } 280 if s.stype == ONE_OFF { 281 if len(s.result.Operations) == 0 { 282 s.result.Message = fmt.Sprintf("There are no database operations to be carried out.") 283 return s.result 284 } else if s.IsSimulation() { 285 s.result.Message = fmt.Sprintf("Simulation report saved to file: %s", s.result.path) 286 } else { 287 s.result.Message = fmt.Sprintf("One-off synchronization report saved to file: %s", s.result.path) 288 } 289 } else { 290 if len(s.result.Operations) == 0 { 291 s.result.Message = fmt.Sprintf("Synchronization \"%s\" stopped. No database operations have been carried out.", s.cfg.Name) 292 return s.result 293 } 294 s.result.Message = fmt.Sprintf("Synchronization \"%s\" stopped. Ongoing synchronization report saved to file: %s", s.cfg.Name, s.result.path) 295 } 296 err := ioutil.WriteFile(s.result.path, []byte(operationsToJSONString), 0644) 297 if err != nil { 298 panic(err) 299 } 300 return s.result 301 } 302 303 // Reset clears data preparing the Synch for the next run. 304 func (s *Synch) Reset() { 305 s.stype = 0 306 s.SetInitial(false) 307 for _, lnk := range s.Links { 308 lnk.reset() 309 } 310 s.counters.reset() 311 }