github.com/wangyougui/gf/v2@v2.6.5/database/gdb/gdb_model_hook.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  package gdb
     8  
     9  import (
    10  	"context"
    11  	"database/sql"
    12  	"fmt"
    13  
    14  	"github.com/wangyougui/gf/v2/container/gvar"
    15  	"github.com/wangyougui/gf/v2/text/gregex"
    16  	"github.com/wangyougui/gf/v2/text/gstr"
    17  )
    18  
    19  type (
    20  	HookFuncSelect func(ctx context.Context, in *HookSelectInput) (result Result, err error)
    21  	HookFuncInsert func(ctx context.Context, in *HookInsertInput) (result sql.Result, err error)
    22  	HookFuncUpdate func(ctx context.Context, in *HookUpdateInput) (result sql.Result, err error)
    23  	HookFuncDelete func(ctx context.Context, in *HookDeleteInput) (result sql.Result, err error)
    24  )
    25  
    26  // HookHandler manages all supported hook functions for Model.
    27  type HookHandler struct {
    28  	Select HookFuncSelect
    29  	Insert HookFuncInsert
    30  	Update HookFuncUpdate
    31  	Delete HookFuncDelete
    32  }
    33  
    34  // internalParamHook manages all internal parameters for hook operations.
    35  // The `internal` obviously means you cannot access these parameters outside this package.
    36  type internalParamHook struct {
    37  	link               Link      // Connection object from third party sql driver.
    38  	handlerCalled      bool      // Simple mark for custom handler called, in case of recursive calling.
    39  	removedWhere       bool      // Removed mark for condition string that was removed `WHERE` prefix.
    40  	originalTableName  *gvar.Var // The original table name.
    41  	originalSchemaName *gvar.Var // The original schema name.
    42  }
    43  
    44  type internalParamHookSelect struct {
    45  	internalParamHook
    46  	handler HookFuncSelect
    47  }
    48  
    49  type internalParamHookInsert struct {
    50  	internalParamHook
    51  	handler HookFuncInsert
    52  }
    53  
    54  type internalParamHookUpdate struct {
    55  	internalParamHook
    56  	handler HookFuncUpdate
    57  }
    58  
    59  type internalParamHookDelete struct {
    60  	internalParamHook
    61  	handler HookFuncDelete
    62  }
    63  
    64  // HookSelectInput holds the parameters for select hook operation.
    65  // Note that, COUNT statement will also be hooked by this feature,
    66  // which is usually not be interesting for upper business hook handler.
    67  type HookSelectInput struct {
    68  	internalParamHookSelect
    69  	Model  *Model        // Current operation Model.
    70  	Table  string        // The table name that to be used. Update this attribute to change target table name.
    71  	Schema string        // The schema name that to be used. Update this attribute to change target schema name.
    72  	Sql    string        // The sql string that to be committed.
    73  	Args   []interface{} // The arguments of sql.
    74  }
    75  
    76  // HookInsertInput holds the parameters for insert hook operation.
    77  type HookInsertInput struct {
    78  	internalParamHookInsert
    79  	Model  *Model         // Current operation Model.
    80  	Table  string         // The table name that to be used. Update this attribute to change target table name.
    81  	Schema string         // The schema name that to be used. Update this attribute to change target schema name.
    82  	Data   List           // The data records list to be inserted/saved into table.
    83  	Option DoInsertOption // The extra option for data inserting.
    84  }
    85  
    86  // HookUpdateInput holds the parameters for update hook operation.
    87  type HookUpdateInput struct {
    88  	internalParamHookUpdate
    89  	Model     *Model        // Current operation Model.
    90  	Table     string        // The table name that to be used. Update this attribute to change target table name.
    91  	Schema    string        // The schema name that to be used. Update this attribute to change target schema name.
    92  	Data      interface{}   // Data can be type of: map[string]interface{}/string. You can use type assertion on `Data`.
    93  	Condition string        // The where condition string for updating.
    94  	Args      []interface{} // The arguments for sql place-holders.
    95  }
    96  
    97  // HookDeleteInput holds the parameters for delete hook operation.
    98  type HookDeleteInput struct {
    99  	internalParamHookDelete
   100  	Model     *Model        // Current operation Model.
   101  	Table     string        // The table name that to be used. Update this attribute to change target table name.
   102  	Schema    string        // The schema name that to be used. Update this attribute to change target schema name.
   103  	Condition string        // The where condition string for deleting.
   104  	Args      []interface{} // The arguments for sql place-holders.
   105  }
   106  
   107  const (
   108  	whereKeyInCondition = " WHERE "
   109  )
   110  
   111  // IsTransaction checks and returns whether current operation is during transaction.
   112  func (h *internalParamHook) IsTransaction() bool {
   113  	return h.link.IsTransaction()
   114  }
   115  
   116  // Next calls the next hook handler.
   117  func (h *HookSelectInput) Next(ctx context.Context) (result Result, err error) {
   118  	if h.originalTableName.IsNil() {
   119  		h.originalTableName = gvar.New(h.Table)
   120  	}
   121  	if h.originalSchemaName.IsNil() {
   122  		h.originalSchemaName = gvar.New(h.Schema)
   123  	}
   124  	// Custom hook handler call.
   125  	if h.handler != nil && !h.handlerCalled {
   126  		h.handlerCalled = true
   127  		return h.handler(ctx, h)
   128  	}
   129  	var toBeCommittedSql = h.Sql
   130  	// Table change.
   131  	if h.Table != h.originalTableName.String() {
   132  		toBeCommittedSql, err = gregex.ReplaceStringFuncMatch(
   133  			`(?i) FROM ([\S]+)`,
   134  			toBeCommittedSql,
   135  			func(match []string) string {
   136  				charL, charR := h.Model.db.GetChars()
   137  				return fmt.Sprintf(` FROM %s%s%s`, charL, h.Table, charR)
   138  			},
   139  		)
   140  	}
   141  	// Schema change.
   142  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   143  		h.link, err = h.Model.db.GetCore().SlaveLink(h.Schema)
   144  		if err != nil {
   145  			return
   146  		}
   147  	}
   148  	return h.Model.db.DoSelect(ctx, h.link, toBeCommittedSql, h.Args...)
   149  }
   150  
   151  // Next calls the next hook handler.
   152  func (h *HookInsertInput) Next(ctx context.Context) (result sql.Result, err error) {
   153  	if h.originalTableName.IsNil() {
   154  		h.originalTableName = gvar.New(h.Table)
   155  	}
   156  	if h.originalSchemaName.IsNil() {
   157  		h.originalSchemaName = gvar.New(h.Schema)
   158  	}
   159  
   160  	if h.handler != nil && !h.handlerCalled {
   161  		h.handlerCalled = true
   162  		return h.handler(ctx, h)
   163  	}
   164  
   165  	// Schema change.
   166  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   167  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   168  		if err != nil {
   169  			return
   170  		}
   171  	}
   172  	return h.Model.db.DoInsert(ctx, h.link, h.Table, h.Data, h.Option)
   173  }
   174  
   175  // Next calls the next hook handler.
   176  func (h *HookUpdateInput) Next(ctx context.Context) (result sql.Result, err error) {
   177  	if h.originalTableName.IsNil() {
   178  		h.originalTableName = gvar.New(h.Table)
   179  	}
   180  	if h.originalSchemaName.IsNil() {
   181  		h.originalSchemaName = gvar.New(h.Schema)
   182  	}
   183  
   184  	if h.handler != nil && !h.handlerCalled {
   185  		h.handlerCalled = true
   186  		if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
   187  			h.removedWhere = true
   188  			h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
   189  		}
   190  		return h.handler(ctx, h)
   191  	}
   192  	if h.removedWhere {
   193  		h.Condition = whereKeyInCondition + h.Condition
   194  	}
   195  	// Schema change.
   196  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   197  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   198  		if err != nil {
   199  			return
   200  		}
   201  	}
   202  	return h.Model.db.DoUpdate(ctx, h.link, h.Table, h.Data, h.Condition, h.Args...)
   203  }
   204  
   205  // Next calls the next hook handler.
   206  func (h *HookDeleteInput) Next(ctx context.Context) (result sql.Result, err error) {
   207  	if h.originalTableName.IsNil() {
   208  		h.originalTableName = gvar.New(h.Table)
   209  	}
   210  	if h.originalSchemaName.IsNil() {
   211  		h.originalSchemaName = gvar.New(h.Schema)
   212  	}
   213  
   214  	if h.handler != nil && !h.handlerCalled {
   215  		h.handlerCalled = true
   216  		if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
   217  			h.removedWhere = true
   218  			h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
   219  		}
   220  		return h.handler(ctx, h)
   221  	}
   222  	if h.removedWhere {
   223  		h.Condition = whereKeyInCondition + h.Condition
   224  	}
   225  	// Schema change.
   226  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   227  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   228  		if err != nil {
   229  			return
   230  		}
   231  	}
   232  	return h.Model.db.DoDelete(ctx, h.link, h.Table, h.Condition, h.Args...)
   233  }
   234  
   235  // Hook sets the hook functions for current model.
   236  func (m *Model) Hook(hook HookHandler) *Model {
   237  	model := m.getModel()
   238  	model.hookHandler = hook
   239  	return model
   240  }