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 }