github.com/gogf/gf/v2@v2.7.4/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/gogf/gf.
     6  
     7  package gdb
     8  
     9  import (
    10  	"context"
    11  	"database/sql"
    12  	"fmt"
    13  
    14  	"github.com/gogf/gf/v2/container/gvar"
    15  	"github.com/gogf/gf/v2/text/gregex"
    16  	"github.com/gogf/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  		if err != nil {
   141  			return
   142  		}
   143  	}
   144  	// Schema change.
   145  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   146  		h.link, err = h.Model.db.GetCore().SlaveLink(h.Schema)
   147  		if err != nil {
   148  			return
   149  		}
   150  	}
   151  	return h.Model.db.DoSelect(ctx, h.link, toBeCommittedSql, h.Args...)
   152  }
   153  
   154  // Next calls the next hook handler.
   155  func (h *HookInsertInput) Next(ctx context.Context) (result sql.Result, err error) {
   156  	if h.originalTableName.IsNil() {
   157  		h.originalTableName = gvar.New(h.Table)
   158  	}
   159  	if h.originalSchemaName.IsNil() {
   160  		h.originalSchemaName = gvar.New(h.Schema)
   161  	}
   162  
   163  	if h.handler != nil && !h.handlerCalled {
   164  		h.handlerCalled = true
   165  		return h.handler(ctx, h)
   166  	}
   167  
   168  	// Schema change.
   169  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   170  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   171  		if err != nil {
   172  			return
   173  		}
   174  	}
   175  	return h.Model.db.DoInsert(ctx, h.link, h.Table, h.Data, h.Option)
   176  }
   177  
   178  // Next calls the next hook handler.
   179  func (h *HookUpdateInput) Next(ctx context.Context) (result sql.Result, err error) {
   180  	if h.originalTableName.IsNil() {
   181  		h.originalTableName = gvar.New(h.Table)
   182  	}
   183  	if h.originalSchemaName.IsNil() {
   184  		h.originalSchemaName = gvar.New(h.Schema)
   185  	}
   186  
   187  	if h.handler != nil && !h.handlerCalled {
   188  		h.handlerCalled = true
   189  		if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
   190  			h.removedWhere = true
   191  			h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
   192  		}
   193  		return h.handler(ctx, h)
   194  	}
   195  	if h.removedWhere {
   196  		h.Condition = whereKeyInCondition + h.Condition
   197  	}
   198  	// Schema change.
   199  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   200  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   201  		if err != nil {
   202  			return
   203  		}
   204  	}
   205  	return h.Model.db.DoUpdate(ctx, h.link, h.Table, h.Data, h.Condition, h.Args...)
   206  }
   207  
   208  // Next calls the next hook handler.
   209  func (h *HookDeleteInput) Next(ctx context.Context) (result sql.Result, err error) {
   210  	if h.originalTableName.IsNil() {
   211  		h.originalTableName = gvar.New(h.Table)
   212  	}
   213  	if h.originalSchemaName.IsNil() {
   214  		h.originalSchemaName = gvar.New(h.Schema)
   215  	}
   216  
   217  	if h.handler != nil && !h.handlerCalled {
   218  		h.handlerCalled = true
   219  		if gstr.HasPrefix(h.Condition, whereKeyInCondition) {
   220  			h.removedWhere = true
   221  			h.Condition = gstr.TrimLeftStr(h.Condition, whereKeyInCondition)
   222  		}
   223  		return h.handler(ctx, h)
   224  	}
   225  	if h.removedWhere {
   226  		h.Condition = whereKeyInCondition + h.Condition
   227  	}
   228  	// Schema change.
   229  	if h.Schema != "" && h.Schema != h.originalSchemaName.String() {
   230  		h.link, err = h.Model.db.GetCore().MasterLink(h.Schema)
   231  		if err != nil {
   232  			return
   233  		}
   234  	}
   235  	return h.Model.db.DoDelete(ctx, h.link, h.Table, h.Condition, h.Args...)
   236  }
   237  
   238  // Hook sets the hook functions for current model.
   239  func (m *Model) Hook(hook HookHandler) *Model {
   240  	model := m.getModel()
   241  	model.hookHandler = hook
   242  	return model
   243  }