github.com/sttk/sabi@v0.5.0/dax.go (about)

     1  // Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved.
     2  // This program is free software under MIT License.
     3  // See the file LICENSE in this distribution for more details.
     4  
     5  package sabi
     6  
     7  import (
     8  	"reflect"
     9  	"sync"
    10  )
    11  
    12  type /* error reasons */ (
    13  	// FailToStartUpGlobalDaxSrcs is an error reason which indicates that some
    14  	// dax sources failed to start up.
    15  	// The field: Errors is a map of which keys are the registered names of
    16  	// DaxSrc(s) which failed to start up, and of which values are Err having
    17  	// their error reasons.
    18  	FailToStartUpGlobalDaxSrcs struct {
    19  		Errors map[string]Err
    20  	}
    21  
    22  	// DaxSrcIsNotFound is an error reason which indicates that a specified data
    23  	// source is not found.
    24  	// The field: Name is a registered name of a DaxSrc not found.
    25  	DaxSrcIsNotFound struct {
    26  		Name string
    27  	}
    28  
    29  	// FailToCreateDaxConn is an error reason which indicates that it's failed to
    30  	// create a new connection to a data store.
    31  	// The field: Name is a registered name of a DaxSrc which failed to create a
    32  	// DaxConn.
    33  	FailToCreateDaxConn struct {
    34  		Name string
    35  	}
    36  
    37  	// FailToCastConn is an error reason which indicates that it is failed to
    38  	// cast type of a DaxConn.
    39  	// The field: Name is a registered name of a DaxSrc which created the target
    40  	// DaxConn.
    41  	// And the fields: FromType and ToType are the types of the source DaxConn
    42  	// and the destination DaxConn.
    43  	FailToCastDaxConn struct {
    44  		Name, FromType, ToType string
    45  	}
    46  
    47  	// FailToCommitDaxConn is an error reason which indicates that some
    48  	// connections failed to commit.
    49  	// The field: Errors is a map of which keys are the registered names of
    50  	// DaxConn which failed to commit, and of which values are Err having their
    51  	// error reasons.
    52  	FailToCommitDaxConn struct {
    53  		Errors map[string]Err
    54  	}
    55  )
    56  
    57  // DaxConn is an interface which represents a connection to a data store, and
    58  // defines methods: Commit, Rollback and Close to work in a tranaction process.
    59  type DaxConn interface {
    60  	Commit() Err
    61  	Rollback()
    62  	Close()
    63  }
    64  
    65  // DaxSrc is an interface which represents a data connection source for a data
    66  // store like database, etc., and creates a DaxConn which is a connection to a
    67  // data store.
    68  // This interface defines a method: CreateDaxConn to creates a DaxConn instance
    69  // and returns its pointer.
    70  // This interface also defines methods: SetUp and End, which makes available
    71  // and frees this dax source.
    72  type DaxSrc interface {
    73  	CreateDaxConn() (DaxConn, Err)
    74  	SetUp() Err
    75  	End()
    76  }
    77  
    78  // Dax is an interface for a set of data access methods.
    79  // This method gets a DaxConn which is a connection to a data store by
    80  // specified name.
    81  // If a DaxConn is found, this method returns it, but not found, creates a new
    82  // one with a local or global DaxSrc associated with same name.
    83  // If there are both local and global DaxSrc with same name, the local DaxSrc
    84  // is used.
    85  type Dax interface {
    86  	getDaxConn(name string) (DaxConn, Err)
    87  }
    88  
    89  var (
    90  	isGlobalDaxSrcsFixed bool              = false
    91  	globalDaxSrcMap      map[string]DaxSrc = make(map[string]DaxSrc)
    92  	globalDaxSrcMutex    sync.Mutex
    93  )
    94  
    95  // AddGlobalDaxSrc registers a global DaxSrc with its name to make enable to
    96  // use DaxSrc in all transactions.
    97  // This method ignores to add a global DaxSrc when its name is already
    98  // registered.
    99  // In addition, this method ignores to add any more global DaxSrc(s) after
   100  // calling FixGlobalDaxSrcs function.
   101  func AddGlobalDaxSrc(name string, ds DaxSrc) {
   102  	globalDaxSrcMutex.Lock()
   103  	defer globalDaxSrcMutex.Unlock()
   104  
   105  	if !isGlobalDaxSrcsFixed {
   106  		_, exists := globalDaxSrcMap[name]
   107  		if !exists {
   108  			globalDaxSrcMap[name] = ds
   109  		}
   110  	}
   111  }
   112  
   113  // StartUpGlobalDaxSrcs is a function to forbid adding more global dax sources
   114  // and to make available the registered global dax sources by calling Setup
   115  // method of each DaxSrc.
   116  // If even one DaxSrc fail to execute its SstUp method, this function
   117  // executes Free methods of all global DaxSrc(s) and returns sabi.Err.
   118  func StartUpGlobalDaxSrcs() Err {
   119  	isGlobalDaxSrcsFixed = true
   120  
   121  	ch := make(chan namedErr)
   122  
   123  	for name, ds := range globalDaxSrcMap {
   124  		go func(name string, ds DaxSrc, ch chan namedErr) {
   125  			err := ds.SetUp()
   126  			ne := namedErr{name: name, err: err}
   127  			ch <- ne
   128  		}(name, ds, ch)
   129  	}
   130  
   131  	errs := make(map[string]Err)
   132  	n := len(globalDaxSrcMap)
   133  	for i := 0; i < n; i++ {
   134  		select {
   135  		case ne := <-ch:
   136  			if !ne.err.IsOk() {
   137  				errs[ne.name] = ne.err
   138  			}
   139  		}
   140  	}
   141  
   142  	if len(errs) > 0 {
   143  		ShutdownGlobalDaxSrcs()
   144  		return NewErr(FailToStartUpGlobalDaxSrcs{Errors: errs})
   145  	}
   146  
   147  	return Ok()
   148  }
   149  
   150  // ShutdownGlobalDaxSrcs is a function to terminate all global dax sources.
   151  func ShutdownGlobalDaxSrcs() {
   152  	var wg sync.WaitGroup
   153  	wg.Add(len(globalDaxSrcMap))
   154  
   155  	for _, ds := range globalDaxSrcMap {
   156  		go func(ds DaxSrc) {
   157  			defer wg.Done()
   158  			ds.End()
   159  		}(ds)
   160  	}
   161  
   162  	wg.Wait()
   163  }
   164  
   165  // DaxBase is an interface which works as a front of an implementation as a
   166  // base of data connection sources, and defines methods: SetUpLocalDaxSrc and
   167  // FreeLocalDaxSrc.
   168  //
   169  // SetUpLocalDaxSrc method registered a DaxSrc with a name in this
   170  // implementation, but  ignores to add a local DaxSrc when its name is already
   171  // registered.
   172  // In addition, this method ignores to add local DaxSrc(s) while the
   173  // transaction is processing.
   174  //
   175  // This interface inherits Dax interface to get a DaxConn by a name.
   176  // Also, this has unexported methods for a transaction process.
   177  type DaxBase interface {
   178  	Dax
   179  	SetUpLocalDaxSrc(name string, ds DaxSrc) Err
   180  	FreeLocalDaxSrc(name string)
   181  	FreeAllLocalDaxSrcs()
   182  	begin()
   183  	commit() Err
   184  	rollback()
   185  	end()
   186  }
   187  
   188  type daxBaseImpl struct {
   189  	isLocalDaxSrcsFixed bool
   190  	localDaxSrcMap      map[string]DaxSrc
   191  	daxConnMap          map[string]DaxConn
   192  	daxConnMutex        sync.Mutex
   193  }
   194  
   195  // NewDaxBase is a function which creates a new DaxBase.
   196  func NewDaxBase() DaxBase {
   197  	return &daxBaseImpl{
   198  		isLocalDaxSrcsFixed: false,
   199  		localDaxSrcMap:      make(map[string]DaxSrc),
   200  		daxConnMap:          make(map[string]DaxConn),
   201  	}
   202  }
   203  
   204  func (base *daxBaseImpl) SetUpLocalDaxSrc(name string, ds DaxSrc) Err {
   205  	base.daxConnMutex.Lock()
   206  	defer base.daxConnMutex.Unlock()
   207  
   208  	if !base.isLocalDaxSrcsFixed {
   209  		_, exists := base.localDaxSrcMap[name]
   210  		if !exists {
   211  			err := ds.SetUp()
   212  			if !err.IsOk() {
   213  				return err
   214  			}
   215  			base.localDaxSrcMap[name] = ds
   216  		}
   217  	}
   218  
   219  	return Ok()
   220  }
   221  
   222  func (base *daxBaseImpl) FreeLocalDaxSrc(name string) {
   223  	base.daxConnMutex.Lock()
   224  	defer base.daxConnMutex.Unlock()
   225  
   226  	if !base.isLocalDaxSrcsFixed {
   227  		ds, exists := base.localDaxSrcMap[name]
   228  		if exists {
   229  			delete(base.localDaxSrcMap, name)
   230  			ds.End()
   231  		}
   232  	}
   233  }
   234  
   235  func (base *daxBaseImpl) FreeAllLocalDaxSrcs() {
   236  	base.daxConnMutex.Lock()
   237  	defer base.daxConnMutex.Unlock()
   238  
   239  	if !base.isLocalDaxSrcsFixed {
   240  		for _, ds := range base.localDaxSrcMap {
   241  			ds.End()
   242  		}
   243  
   244  		base.localDaxSrcMap = make(map[string]DaxSrc)
   245  	}
   246  }
   247  
   248  func (base *daxBaseImpl) getDaxConn(name string) (DaxConn, Err) {
   249  	conn := base.daxConnMap[name]
   250  	if conn != nil {
   251  		return conn, Ok()
   252  	}
   253  
   254  	ds := base.localDaxSrcMap[name]
   255  	if ds == nil {
   256  		ds = globalDaxSrcMap[name]
   257  	}
   258  	if ds == nil {
   259  		return nil, NewErr(DaxSrcIsNotFound{Name: name})
   260  	}
   261  
   262  	base.daxConnMutex.Lock()
   263  	defer base.daxConnMutex.Unlock()
   264  
   265  	conn = base.daxConnMap[name]
   266  	if conn != nil {
   267  		return conn, Ok()
   268  	}
   269  
   270  	var err Err
   271  	conn, err = ds.CreateDaxConn()
   272  	if !err.IsOk() {
   273  		return nil, NewErr(FailToCreateDaxConn{Name: name}, err)
   274  	}
   275  
   276  	base.daxConnMap[name] = conn
   277  
   278  	return conn, Ok()
   279  }
   280  
   281  func (base *daxBaseImpl) begin() {
   282  	base.isLocalDaxSrcsFixed = true
   283  	isGlobalDaxSrcsFixed = true
   284  }
   285  
   286  type namedErr struct {
   287  	name string
   288  	err  Err
   289  }
   290  
   291  func (base *daxBaseImpl) commit() Err {
   292  	ch := make(chan namedErr)
   293  
   294  	for name, conn := range base.daxConnMap {
   295  		go func(name string, conn DaxConn, ch chan namedErr) {
   296  			err := conn.Commit()
   297  			ne := namedErr{name: name, err: err}
   298  			ch <- ne
   299  		}(name, conn, ch)
   300  	}
   301  
   302  	errs := make(map[string]Err)
   303  	n := len(base.daxConnMap)
   304  	for i := 0; i < n; i++ {
   305  		select {
   306  		case ne := <-ch:
   307  			if !ne.err.IsOk() {
   308  				errs[ne.name] = ne.err
   309  			}
   310  		}
   311  	}
   312  
   313  	if len(errs) > 0 {
   314  		return NewErr(FailToCommitDaxConn{Errors: errs})
   315  	}
   316  
   317  	return Ok()
   318  }
   319  
   320  func (base *daxBaseImpl) rollback() {
   321  	var wg sync.WaitGroup
   322  	wg.Add(len(base.daxConnMap))
   323  
   324  	for _, conn := range base.daxConnMap {
   325  		go func(conn DaxConn) {
   326  			defer wg.Done()
   327  			conn.Rollback()
   328  		}(conn)
   329  	}
   330  
   331  	wg.Wait()
   332  }
   333  
   334  func (base *daxBaseImpl) end() {
   335  	var wg sync.WaitGroup
   336  	wg.Add(len(base.daxConnMap))
   337  
   338  	for _, conn := range base.daxConnMap {
   339  		go func(conn DaxConn) {
   340  			defer wg.Done()
   341  			conn.Close()
   342  		}(conn)
   343  	}
   344  
   345  	base.daxConnMap = make(map[string]DaxConn)
   346  
   347  	wg.Wait()
   348  
   349  	base.isLocalDaxSrcsFixed = false
   350  }
   351  
   352  // GetDaxConn is a function to cast type of DaxConn instance.
   353  // If it's failed to cast to a destination type, this function returns an Err
   354  // of a reason: FailToGetDaxConn.
   355  func GetDaxConn[C DaxConn](dax Dax, name string) (C, Err) {
   356  	conn, err := dax.getDaxConn(name)
   357  	if err.IsOk() {
   358  		casted, ok := conn.(C)
   359  		if ok {
   360  			return casted, err
   361  		}
   362  
   363  		var from string
   364  		t := reflect.TypeOf(conn)
   365  		if t.Kind() == reflect.Ptr {
   366  			t = t.Elem()
   367  			from = "*" + t.Name() + " (" + t.PkgPath() + ")"
   368  		} else {
   369  			from = t.Name() + " (" + t.PkgPath() + ")"
   370  		}
   371  
   372  		var to string
   373  		t = reflect.TypeOf(casted)
   374  		if t.Kind() == reflect.Ptr {
   375  			t = t.Elem()
   376  			to = "*" + t.Name() + " (" + t.PkgPath() + ")"
   377  		} else {
   378  			to = t.Name() + " (" + t.PkgPath() + ")"
   379  		}
   380  		err = NewErr(FailToCastDaxConn{Name: name, FromType: from, ToType: to})
   381  	}
   382  
   383  	return *new(C), err
   384  }