github.com/dolthub/go-mysql-server@v0.18.0/eventscheduler/enabled_event.go (about) 1 // Copyright 2023 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package eventscheduler 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 "sync" 22 "time" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 ) 26 27 // enabledEvent is used for storing a list of events that are enabled in EventScheduler. 28 type enabledEvent struct { 29 edb sql.EventDatabase 30 event sql.EventDefinition 31 nextExecutionAt time.Time 32 username string 33 address string 34 } 35 36 var _ fmt.Stringer = (*enabledEvent)(nil) 37 38 // newEnabledEvent returns new enabledEvent object and whether it is created successfully. An event 39 // with ENABLE status might NOT be created if the event SCHEDULE is ended/expired. If the event is expired, 40 // then this function either updates its status in the database or drops it from the database. 41 func newEnabledEvent(ctx *sql.Context, edb sql.EventDatabase, event sql.EventDefinition, curTime time.Time) (*enabledEvent, bool, error) { 42 if event.Status == sql.EventStatus_Enable.String() { 43 nextExecution, eventEnded, err := event.GetNextExecutionTime(curTime) 44 if err != nil { 45 return nil, false, err 46 } else if !eventEnded { 47 username, address, err := getUsernameAndAddressFromDefiner(event.Definer) 48 if err != nil { 49 return nil, false, err 50 } 51 return &enabledEvent{ 52 edb: edb, 53 event: event, 54 nextExecutionAt: nextExecution, 55 username: username, 56 address: address, 57 }, true, nil 58 } else { 59 if event.OnCompletionPreserve { 60 event.Status = sql.EventStatus_Disable.String() 61 _, err = edb.UpdateEvent(ctx, event.Name, event) 62 if err != nil { 63 return nil, false, err 64 } 65 } else { 66 err = edb.DropEvent(ctx, event.Name) 67 if err != nil { 68 return nil, false, err 69 } 70 } 71 } 72 } 73 return nil, false, nil 74 } 75 76 // getUsernameAndAddressFromDefiner returns username and address parsed from given definer value of an EventDefinition. 77 func getUsernameAndAddressFromDefiner(definer string) (string, string, error) { 78 // make sure definer has username and address information here 79 ua := strings.Split(definer, "@") 80 if len(ua) != 2 { 81 return "", "", fmt.Errorf("invalid definer for the event") 82 } 83 84 username := strings.TrimSuffix(strings.TrimPrefix(ua[0], "`"), "`") 85 username = strings.TrimSuffix(strings.TrimPrefix(username, "'"), "'") 86 87 address := strings.TrimSuffix(strings.TrimPrefix(ua[1], "`"), "`") 88 address = strings.TrimSuffix(strings.TrimPrefix(address, "'"), "'") 89 90 return username, address, nil 91 } 92 93 // String implements the fmt.Stringer interface 94 func (e *enabledEvent) String() string { 95 return fmt.Sprintf("next execution at: %v, event database: %s, event definition: %v", e.nextExecutionAt, e.edb.Name(), e.event) 96 } 97 98 // name returns 'database_name.event_name' used as a key for mapping unique events. 99 func (e *enabledEvent) name() string { 100 return fmt.Sprintf("%s.%s", e.edb.Name(), e.event.Name) 101 } 102 103 // updateEventAfterExecution updates the event's LastExecuted metadata with given execution time and returns whether 104 // the event is expired. If the event is not expired, this function updates the given enabledEvent with the next 105 // execution time. If expired, it updates the event's metadata in the database or drop the event from the database. 106 func (e *enabledEvent) updateEventAfterExecution(ctx *sql.Context, edb sql.EventDatabase, executionTime time.Time) (bool, error) { 107 var nextExecutionAt time.Time 108 var ended bool 109 var err error 110 if e.event.HasExecuteAt { 111 // one-time event is ended after one execution 112 ended = true 113 } else { 114 nextExecutionAt, ended, err = e.event.GetNextExecutionTime(time.Now()) 115 if err != nil { 116 return ended, err 117 } 118 } 119 120 if ended { 121 if e.event.OnCompletionPreserve { 122 e.event.Status = sql.EventStatus_Disable.String() 123 } else { 124 err = edb.DropEvent(ctx, e.event.Name) 125 if err != nil { 126 return ended, err 127 } 128 return true, nil 129 } 130 } else { 131 e.nextExecutionAt = nextExecutionAt 132 } 133 134 e.event.LastExecuted = executionTime 135 // update the database stored event with LastExecuted and Status metadata update if applicable. 136 _, err = edb.UpdateEvent(ctx, e.event.Name, e.event) 137 if err != nil { 138 return ended, err 139 } 140 141 return ended, nil 142 } 143 144 // enabledEventsList is a list of enabled events of all databases that the eventExecutor 145 // uses to execute them at the scheduled time. 146 type enabledEventsList struct { 147 mu *sync.Mutex 148 eventsList []*enabledEvent 149 } 150 151 // newEnabledEventsList returns new enabledEventsList object with the given 152 // enabledEvent list and sorts it by the nextExecutionAt time. 153 func newEnabledEventsList(list []*enabledEvent) *enabledEventsList { 154 newList := &enabledEventsList{ 155 mu: &sync.Mutex{}, 156 eventsList: list, 157 } 158 sort.SliceStable(newList.eventsList, func(i, j int) bool { 159 return list[i].nextExecutionAt.Sub(list[j].nextExecutionAt).Seconds() < 1 160 }) 161 return newList 162 } 163 164 // clear sets the current list to empty list. 165 func (l *enabledEventsList) clear() { 166 l.mu.Lock() 167 defer l.mu.Unlock() 168 l.eventsList = nil 169 } 170 171 // len returns the length of the current list. 172 func (l *enabledEventsList) len() int { 173 l.mu.Lock() 174 defer l.mu.Unlock() 175 return len(l.eventsList) 176 } 177 178 // getNextExecutionTime returns the execution time of the first enabledEvent in the current list. 179 func (l *enabledEventsList) getNextExecutionTime() (time.Time, bool) { 180 l.mu.Lock() 181 defer l.mu.Unlock() 182 if len(l.eventsList) == 0 { 183 return time.Time{}, false 184 } 185 return l.eventsList[0].nextExecutionAt, true 186 } 187 188 // peek returns the first element from the list, without removing it from the list. 189 func (l *enabledEventsList) peek() *enabledEvent { 190 l.mu.Lock() 191 defer l.mu.Unlock() 192 if len(l.eventsList) == 0 { 193 return nil 194 } 195 return l.eventsList[0] 196 } 197 198 // pop returns the first element and removes it from the list. 199 func (l *enabledEventsList) pop() *enabledEvent { 200 l.mu.Lock() 201 defer l.mu.Unlock() 202 if len(l.eventsList) == 0 { 203 return nil 204 } 205 firstInList := l.eventsList[0] 206 l.eventsList = l.eventsList[1:] 207 return firstInList 208 } 209 210 // add adds the event to the list and sorts the list. 211 func (l *enabledEventsList) add(event *enabledEvent) { 212 l.mu.Lock() 213 defer l.mu.Unlock() 214 l.eventsList = append(l.eventsList, event) 215 sort.SliceStable(l.eventsList, func(i, j int) bool { 216 return l.eventsList[i].nextExecutionAt.Sub(l.eventsList[j].nextExecutionAt).Seconds() < 1 217 }) 218 } 219 220 // remove removes the event from the list, 221 // the list order stays the same. 222 func (l *enabledEventsList) remove(key string) { 223 l.mu.Lock() 224 defer l.mu.Unlock() 225 for i, e := range l.eventsList { 226 if e.name() == key { 227 l.eventsList = append(l.eventsList[:i], l.eventsList[i+1:]...) 228 return 229 } 230 } 231 } 232 233 // String implements the fmt.Stringer interface 234 func (l *enabledEventsList) String() string { 235 return fmt.Sprintf("event list: %v", l.eventsList) 236 } 237 238 // remove removes all events of the given database from the list, 239 // the list order stays the same. 240 func (l *enabledEventsList) removeSchemaEvents(dbName string) { 241 l.mu.Lock() 242 defer l.mu.Unlock() 243 for i, e := range l.eventsList { 244 if e.edb.Name() == dbName { 245 l.eventsList = append(l.eventsList[:i], l.eventsList[i+1:]...) 246 } 247 } 248 } 249 250 // runningEventsStatus stores whether the event is currently running and 251 // needs to be re-added after execution. When currently running event is 252 // updated or dropped, it should not be re-added to the enabledEventsList 253 // after execution. 254 type runningEventsStatus struct { 255 mu *sync.Mutex 256 status map[string]bool 257 reAdd map[string]bool 258 } 259 260 // newRunningEventsStatus returns new empty runningEventsStatus object. 261 func newRunningEventsStatus() *runningEventsStatus { 262 return &runningEventsStatus{ 263 mu: &sync.Mutex{}, 264 status: make(map[string]bool), 265 reAdd: make(map[string]bool), 266 } 267 } 268 269 // clear removes all entries from runningEventsStatus object maps. 270 func (r *runningEventsStatus) clear() { 271 r.mu.Lock() 272 defer r.mu.Unlock() 273 r.status = make(map[string]bool) 274 r.reAdd = make(map[string]bool) 275 } 276 277 // update updates the runningEventsStatus object maps with given key and values. 278 func (r *runningEventsStatus) update(key string, status, reAdd bool) { 279 r.mu.Lock() 280 defer r.mu.Unlock() 281 r.status[key] = status 282 r.reAdd[key] = reAdd 283 } 284 285 // remove removes an entry from runningEventsStatus object maps with given key. 286 func (r *runningEventsStatus) remove(key string) { 287 r.mu.Lock() 288 defer r.mu.Unlock() 289 delete(r.status, key) 290 delete(r.reAdd, key) 291 } 292 293 // getStatus returns the status of the event at given key. 294 func (r *runningEventsStatus) getStatus(key string) (bool, bool) { 295 r.mu.Lock() 296 defer r.mu.Unlock() 297 b, ok := r.status[key] 298 return b, ok 299 } 300 301 // getReAdd returns whether to re-add the event at given key. 302 func (r *runningEventsStatus) getReAdd(key string) (bool, bool) { 303 r.mu.Lock() 304 defer r.mu.Unlock() 305 b, ok := r.reAdd[key] 306 return b, ok 307 } 308 309 // cancelEventsForDatabase marks all running events for the specified database 310 // so that they do not get rescheduled after they finish running. 311 func (r *runningEventsStatus) cancelEventsForDatabase(dbName string) { 312 r.mu.Lock() 313 defer r.mu.Unlock() 314 // if there are any running events of given database, then set reAdd to false 315 for evId := range r.status { 316 if strings.HasPrefix(evId, fmt.Sprintf("%s.", dbName)) { 317 r.reAdd[evId] = false 318 } 319 } 320 }