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  }