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 }