Skip to content

Commit c3db4ff

Browse files
authored
support EVENT storage handling (#1701)
1 parent 3610cac commit c3db4ff

23 files changed

+1943
-84
lines changed

enginetest/enginetests.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,6 +1819,12 @@ func TestStoredProcedures(t *testing.T, harness Harness) {
18191819
})
18201820
}
18211821

1822+
func TestEvents(t *testing.T, h Harness) {
1823+
for _, script := range queries.EventTests {
1824+
TestScript(t, h, script)
1825+
}
1826+
}
1827+
18221828
func TestTriggerErrors(t *testing.T, harness Harness) {
18231829
for _, script := range queries.TriggerErrorTests {
18241830
TestScript(t, harness, script)

enginetest/memory_engine_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,10 @@ func TestStoredProcedures(t *testing.T) {
645645
enginetest.TestStoredProcedures(t, enginetest.NewDefaultMemoryHarness())
646646
}
647647

648+
func TestEvents(t *testing.T) {
649+
enginetest.TestEvents(t, enginetest.NewDefaultMemoryHarness())
650+
}
651+
648652
func TestTriggersErrors(t *testing.T) {
649653
enginetest.TestTriggerErrors(t, enginetest.NewDefaultMemoryHarness())
650654
}

enginetest/mysqlshim/database.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var _ sql.TableRenamer = Database{}
3636
var _ sql.TriggerDatabase = Database{}
3737
var _ sql.StoredProcedureDatabase = Database{}
3838
var _ sql.ViewDatabase = Database{}
39+
var _ sql.EventDatabase = Database{}
3940

4041
// Name implements the interface sql.Database.
4142
func (d Database) Name() string {
@@ -199,6 +200,63 @@ func (d Database) DropStoredProcedure(ctx *sql.Context, name string) error {
199200
return d.shim.Exec(d.name, fmt.Sprintf("DROP PROCEDURE `%s`;", name))
200201
}
201202

203+
// GetEvent implements sql.EventDatabase
204+
func (d Database) GetEvent(ctx *sql.Context, name string) (sql.EventDefinition, bool, error) {
205+
name = strings.ToLower(name)
206+
events, err := d.GetEvents(ctx)
207+
if err != nil {
208+
return sql.EventDefinition{}, false, err
209+
}
210+
for _, event := range events {
211+
if name == strings.ToLower(event.Name) {
212+
return event, true, nil
213+
}
214+
}
215+
return sql.EventDefinition{}, false, nil
216+
}
217+
218+
// GetEvents implements sql.EventDatabase
219+
func (d Database) GetEvents(ctx *sql.Context) ([]sql.EventDefinition, error) {
220+
events, err := d.shim.QueryRows("", fmt.Sprintf("SHOW EVENTS WHERE Db = '%s';", d.name))
221+
if err != nil {
222+
return nil, err
223+
}
224+
eventDefinition := make([]sql.EventDefinition, len(events))
225+
for i, event := range events {
226+
// Db, Name, Definer, Time Zone, Type, ...
227+
eventStmt, err := d.shim.QueryRows("", fmt.Sprintf("SHOW CREATE EVENT `%s`.`%s`;", d.name, event[1]))
228+
if err != nil {
229+
return nil, err
230+
}
231+
// Event, sql_mode, time_zone, Create Event, ...
232+
eventDefinition[i] = sql.EventDefinition{
233+
Name: eventStmt[0][0].(string),
234+
CreateStatement: eventStmt[0][3].(string),
235+
// TODO: other fields should be added such as Created, LastAltered
236+
}
237+
}
238+
return eventDefinition, nil
239+
}
240+
241+
// SaveEvent implements sql.EventDatabase
242+
func (d Database) SaveEvent(ctx *sql.Context, ed sql.EventDefinition) error {
243+
return d.shim.Exec(d.name, ed.CreateStatement)
244+
}
245+
246+
// DropEvent implements sql.EventDatabase
247+
func (d Database) DropEvent(ctx *sql.Context, name string) error {
248+
return d.shim.Exec(d.name, fmt.Sprintf("DROP EVENT `%s`;", name))
249+
}
250+
251+
// UpdateEvent implements sql.EventDatabase
252+
func (d Database) UpdateEvent(ctx *sql.Context, ed sql.EventDefinition) error {
253+
err := d.shim.Exec(d.name, fmt.Sprintf("DROP EVENT `%s`;", ed.Name))
254+
if err != nil {
255+
return err
256+
}
257+
return d.shim.Exec(d.name, ed.CreateStatement)
258+
}
259+
202260
// CreateView implements the interface sql.ViewDatabase.
203261
func (d Database) CreateView(ctx *sql.Context, name string, selectStatement, createViewStmt string) error {
204262
return d.shim.Exec(d.name, createViewStmt)
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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 queries
16+
17+
import (
18+
"github.com/dolthub/go-mysql-server/sql"
19+
"github.com/dolthub/go-mysql-server/sql/types"
20+
)
21+
22+
// EventTests tests any EVENT related behavior. Events have at least one timestamp value (AT/STARTS/ENDS), so to test
23+
// SHOW EVENTS and SHOW CREATE EVENTS statements, some tests have those timestamps defined in 2037.
24+
var EventTests = []ScriptTest{
25+
{
26+
Name: "EVENTs with ON SCHEDULE EVERY",
27+
SetUpScript: []string{
28+
"USE mydb;",
29+
"CREATE TABLE totals (num int);",
30+
},
31+
Assertions: []ScriptTestAssertion{
32+
{
33+
Query: "CREATE EVENT event_with_starts_and_ends ON SCHEDULE EVERY '1:2' MINUTE_SECOND STARTS CURRENT_TIMESTAMP + INTERVAL 1 HOUR ENDS CURRENT_TIMESTAMP + INTERVAL 1 DAY DISABLE DO INSERT INTO totals VALUES (1);",
34+
Expected: []sql.Row{{types.OkResult{}}},
35+
},
36+
{
37+
Query: "CREATE EVENT event_with_starts_only ON SCHEDULE EVERY '1:2' MINUTE_SECOND STARTS CURRENT_TIMESTAMP + INTERVAL 1 HOUR DISABLE DO INSERT INTO totals VALUES (1);",
38+
Expected: []sql.Row{{types.OkResult{}}},
39+
},
40+
{
41+
Query: "CREATE EVENT event_with_ends_only ON SCHEDULE EVERY '1:2' MINUTE_SECOND ENDS CURRENT_TIMESTAMP + INTERVAL 1 DAY DISABLE DO INSERT INTO totals VALUES (1);",
42+
Expected: []sql.Row{{types.OkResult{}}},
43+
},
44+
{
45+
Query: "CREATE EVENT event_without_starts_and_ends ON SCHEDULE EVERY '1:2' MINUTE_SECOND DISABLE DO INSERT INTO totals VALUES (1);",
46+
Expected: []sql.Row{{types.OkResult{}}},
47+
},
48+
{
49+
Query: "CREATE EVENT event2 ON SCHEDULE EVERY 3 DAY STARTS '2037-10-16 23:59:00' + INTERVAL 2 DAY ENDS '2037-11-16 23:59:00' + INTERVAL 1 MONTH DO INSERT INTO totals VALUES (1000);",
50+
Expected: []sql.Row{{types.OkResult{}}},
51+
},
52+
{
53+
Query: "SHOW EVENTS LIKE 'event2';",
54+
Expected: []sql.Row{{"mydb", "event2", "`root`@`localhost`", "SYSTEM", "RECURRING", nil, "3", "DAY", "2037-10-18 23:59:00", "2037-12-16 23:59:00", "ENABLED", 0, "utf8mb4", "utf8mb4_0900_bin", "utf8mb4_0900_bin"}},
55+
},
56+
{
57+
Query: "SHOW CREATE EVENT event2;",
58+
Expected: []sql.Row{
59+
{"event2", "", "SYSTEM", "CREATE DEFINER = `root`@`localhost` EVENT `event2` ON SCHEDULE EVERY 3 DAY STARTS '2037-10-18 23:59:00' ENDS '2037-12-16 23:59:00' ON COMPLETION NOT PRESERVE ENABLE DO INSERT INTO totals VALUES (1000)", "utf8mb4", "utf8mb4_0900_bin", "utf8mb4_0900_bin"},
60+
},
61+
},
62+
},
63+
},
64+
{
65+
Name: "EVENTs with ON SCHEDULE AT",
66+
SetUpScript: []string{
67+
"USE mydb;",
68+
"CREATE TABLE totals (num int);",
69+
},
70+
Assertions: []ScriptTestAssertion{
71+
{
72+
Query: "CREATE EVENT event2 ON SCHEDULE AT '2038-01-16 23:59:00 +0000 UTC' + INTERVAL 1 DAY ON COMPLETION PRESERVE DISABLE DO INSERT INTO totals VALUES (100);",
73+
Expected: []sql.Row{{types.OkResult{}}},
74+
},
75+
{
76+
Query: "SHOW EVENTS;",
77+
Expected: []sql.Row{{"mydb", "event2", "`root`@`localhost`", "SYSTEM", "ONE TIME", "2038-01-17 23:59:00", nil, nil, nil, nil, "DISABLED", 0, "utf8mb4", "utf8mb4_0900_bin", "utf8mb4_0900_bin"}},
78+
},
79+
},
80+
},
81+
{
82+
Name: "DROP EVENTs",
83+
SetUpScript: []string{
84+
"USE mydb;",
85+
"CREATE TABLE totals (num int);",
86+
"CREATE EVENT event1 ON SCHEDULE AT '2038-01-15 23:59:00 +0000 UTC' + INTERVAL 2 DAY DISABLE DO INSERT INTO totals VALUES (100);",
87+
},
88+
Assertions: []ScriptTestAssertion{
89+
{
90+
Query: "SHOW EVENTS;",
91+
Expected: []sql.Row{{"mydb", "event1", "`root`@`localhost`", "SYSTEM", "ONE TIME", "2038-01-17 23:59:00", nil, nil, nil, nil, "DISABLED", 0, "utf8mb4", "utf8mb4_0900_bin", "utf8mb4_0900_bin"}},
92+
},
93+
{
94+
Query: "DROP EVENT event1",
95+
Expected: []sql.Row{{types.OkResult{}}},
96+
},
97+
{
98+
Query: "SHOW EVENTS;",
99+
Expected: []sql.Row{},
100+
},
101+
},
102+
},
103+
{
104+
Name: "invalid events actions",
105+
SetUpScript: []string{
106+
"USE mydb;",
107+
"CREATE TABLE totals (num int);",
108+
"CREATE EVENT my_event1 ON SCHEDULE EVERY '1:2' MINUTE_SECOND DISABLE DO INSERT INTO totals VALUES (1);",
109+
},
110+
Assertions: []ScriptTestAssertion{
111+
{
112+
Query: "CREATE EVENT my_event1 ON SCHEDULE EVERY '1:2' MINUTE_SECOND DISABLE DO INSERT INTO totals VALUES (1);",
113+
ExpectedErr: sql.ErrEventAlreadyExists,
114+
},
115+
{
116+
Query: "CREATE EVENT IF NOT EXISTS my_event1 ON SCHEDULE EVERY '1:2' MINUTE_SECOND DISABLE DO INSERT INTO totals VALUES (1);",
117+
Expected: []sql.Row{{types.OkResult{}}},
118+
ExpectedWarningsCount: 1,
119+
},
120+
{
121+
Query: "SHOW WARNINGS;",
122+
Expected: []sql.Row{{"Note", 1537, "Event 'my_event1' already exists"}},
123+
},
124+
{
125+
Query: "CREATE EVENT ends_before_starts ON SCHEDULE EVERY 1 MINUTE ENDS '2006-02-10 23:59:00' DO INSERT INTO totals VALUES (1);",
126+
ExpectedErrStr: "ENDS is either invalid or before STARTS",
127+
},
128+
{
129+
Query: "SHOW CREATE EVENT non_existent_event;",
130+
ExpectedErr: sql.ErrUnknownEvent,
131+
},
132+
{
133+
Query: "DROP EVENT non_existent_event",
134+
ExpectedErr: sql.ErrEventDoesNotExist,
135+
},
136+
{
137+
Query: "DROP EVENT IF EXISTS non_existent_event",
138+
Expected: []sql.Row{{types.OkResult{}}},
139+
ExpectedWarningsCount: 1,
140+
},
141+
{
142+
Query: "SHOW WARNINGS;",
143+
Expected: []sql.Row{{"Note", 1305, "Event non_existent_event does not exist"}},
144+
},
145+
{
146+
Query: "CREATE EVENT past_event1 ON SCHEDULE AT '2006-02-10 23:59:00' DISABLE DO INSERT INTO totals VALUES (100);",
147+
Expected: []sql.Row{{types.OkResult{}}},
148+
ExpectedWarningsCount: 1,
149+
},
150+
{
151+
Query: "SHOW WARNINGS;",
152+
Expected: []sql.Row{{"Note", 1588, "Event execution time is in the past and ON COMPLETION NOT PRESERVE is set. The event was dropped immediately after creation."}},
153+
},
154+
{
155+
Query: "SHOW EVENTS LIKE 'past_event1';",
156+
Expected: []sql.Row{},
157+
},
158+
{
159+
Query: "CREATE EVENT past_event2 ON SCHEDULE AT '2006-02-10 23:59:00' ON COMPLETION PRESERVE DO INSERT INTO totals VALUES (100);",
160+
Expected: []sql.Row{{types.OkResult{}}},
161+
ExpectedWarningsCount: 1,
162+
},
163+
{
164+
Query: "SHOW WARNINGS;",
165+
Expected: []sql.Row{{"Note", 1544, "Event execution time is in the past. Event has been disabled"}},
166+
},
167+
{
168+
Query: "SHOW EVENTS LIKE 'past_event2';",
169+
Expected: []sql.Row{{"mydb", "past_event2", "`root`@`localhost`", "SYSTEM", "ONE TIME", "2006-02-10 23:59:00", nil, nil, nil, nil, "DISABLED", 0, "utf8mb4", "utf8mb4_0900_bin", "utf8mb4_0900_bin"}},
170+
},
171+
},
172+
},
173+
}

go.sum

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,6 @@ github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474 h1:xTrR+l5l+1Lfq0
5555
github.com/dolthub/jsonpath v0.0.0-20210609232853-d49537a30474/go.mod h1:kMz7uXOXq4qRriCEyZ/LUeTqraLJCjf0WVZcUi6TxUY=
5656
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE=
5757
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY=
58-
github.com/dolthub/vitess v0.0.0-20230403205610-0c8d4c5aa5a2 h1:l6EfOQNPQcOQ0yaf0NT/NmARfdJB7vQAsylmX56Xnz8=
59-
github.com/dolthub/vitess v0.0.0-20230403205610-0c8d4c5aa5a2/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
60-
github.com/dolthub/vitess v0.0.0-20230403222318-aa590a202153 h1:pYtxt69Tc6Q8VMmDuKPhbKoS/fdtcwEh0nK6ZLUE25g=
61-
github.com/dolthub/vitess v0.0.0-20230403222318-aa590a202153/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
62-
github.com/dolthub/vitess v0.0.0-20230404224528-8d863f05acdd h1:HbSqiyaeg5MzXvUwvcRE7ldOLzGZ0Kt55fqBkidOSoE=
63-
github.com/dolthub/vitess v0.0.0-20230404224528-8d863f05acdd/go.mod h1:oVFIBdqMFEkt4Xz2fzFJBNtzKhDEjwdCF0dzde39iKs=
64-
github.com/dolthub/vitess v0.0.0-20230406223939-011b5234783f h1:wSzMlRGaveVkOwRNvw7AJs8rgz/074AfrcbxSZp+kzE=
65-
github.com/dolthub/vitess v0.0.0-20230406223939-011b5234783f/go.mod h1:IyoysiiOzrIs7QsEHC+yVF0yRQ6W70GXyCXqtI2vVTs=
6658
github.com/dolthub/vitess v0.0.0-20230407173322-ae1622f38e94 h1:As3/5XDEthX6rvA+Dbiutxo965AdVhxa937j2OGSaog=
6759
github.com/dolthub/vitess v0.0.0-20230407173322-ae1622f38e94/go.mod h1:IyoysiiOzrIs7QsEHC+yVF0yRQ6W70GXyCXqtI2vVTs=
6860
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -408,8 +400,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
408400
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
409401
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
410402
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
411-
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
412-
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
413403
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
414404
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
415405
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

memory/database.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ var _ sql.TableDropper = (*Database)(nil)
4141
var _ sql.TableRenamer = (*Database)(nil)
4242
var _ sql.TriggerDatabase = (*Database)(nil)
4343
var _ sql.StoredProcedureDatabase = (*Database)(nil)
44+
var _ sql.EventDatabase = (*Database)(nil)
4445
var _ sql.ViewDatabase = (*Database)(nil)
4546
var _ sql.CollatedDatabase = (*Database)(nil)
4647

@@ -51,6 +52,7 @@ type BaseDatabase struct {
5152
fkColl *ForeignKeyCollection
5253
triggers []sql.TriggerDefinition
5354
storedProcedures []sql.StoredProcedureDetails
55+
events []sql.EventDefinition
5456
primaryKeyIndexes bool
5557
collation sql.CollationID
5658
}
@@ -320,6 +322,71 @@ func (d *BaseDatabase) DropStoredProcedure(ctx *sql.Context, name string) error
320322
return nil
321323
}
322324

325+
// GetEvent implements sql.EventDatabase
326+
func (d *BaseDatabase) GetEvent(ctx *sql.Context, name string) (sql.EventDefinition, bool, error) {
327+
name = strings.ToLower(name)
328+
for _, ed := range d.events {
329+
if name == strings.ToLower(ed.Name) {
330+
return ed, true, nil
331+
}
332+
}
333+
return sql.EventDefinition{}, false, nil
334+
}
335+
336+
// GetEvents implements sql.EventDatabase
337+
func (d *BaseDatabase) GetEvents(ctx *sql.Context) ([]sql.EventDefinition, error) {
338+
var eds []sql.EventDefinition
339+
for _, ed := range d.events {
340+
eds = append(eds, ed)
341+
}
342+
return eds, nil
343+
}
344+
345+
// SaveEvent implements sql.EventDatabase
346+
func (d *BaseDatabase) SaveEvent(ctx *sql.Context, ed sql.EventDefinition) error {
347+
loweredName := strings.ToLower(ed.Name)
348+
for _, existingEd := range d.events {
349+
if strings.ToLower(existingEd.Name) == loweredName {
350+
return sql.ErrEventAlreadyExists.New(ed.Name)
351+
}
352+
}
353+
d.events = append(d.events, ed)
354+
return nil
355+
}
356+
357+
// DropEvent implements sql.EventDatabase
358+
func (d *BaseDatabase) DropEvent(ctx *sql.Context, name string) error {
359+
loweredName := strings.ToLower(name)
360+
found := false
361+
for i, ed := range d.events {
362+
if strings.ToLower(ed.Name) == loweredName {
363+
d.events = append(d.events[:i], d.events[i+1:]...)
364+
found = true
365+
break
366+
}
367+
}
368+
if !found {
369+
return sql.ErrEventDoesNotExist.New(name)
370+
}
371+
return nil
372+
}
373+
374+
// UpdateEvent implements sql.EventDatabase
375+
func (d *BaseDatabase) UpdateEvent(ctx *sql.Context, ed sql.EventDefinition) error {
376+
loweredName := strings.ToLower(ed.Name)
377+
found := false
378+
for i, existingEd := range d.events {
379+
if strings.ToLower(existingEd.Name) == loweredName {
380+
d.events[i] = ed
381+
found = true
382+
}
383+
}
384+
if !found {
385+
return sql.ErrEventDoesNotExist.New(ed.Name)
386+
}
387+
return nil
388+
}
389+
323390
// GetCollation implements sql.CollatedDatabase.
324391
func (d *BaseDatabase) GetCollation(ctx *sql.Context) sql.CollationID {
325392
return d.collation

sql/analyzer/autocommit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func GetTransactionDatabase(ctx *sql.Context, parsed sql.Node) string {
5858
return GetTransactionDatabase(ctx, n.(sql.UnaryNode).Child())
5959
case *plan.Use, *plan.CreateProcedure, *plan.DropProcedure, *plan.CreateTrigger, *plan.DropTrigger,
6060
*plan.CreateTable, *plan.InsertInto, *plan.AlterIndex, *plan.AlterAutoIncrement, *plan.AlterPK,
61-
*plan.DropColumn, *plan.RenameColumn, *plan.ModifyColumn:
61+
*plan.DropColumn, *plan.RenameColumn, *plan.ModifyColumn, *plan.CreateEvent, *plan.DropEvent:
6262
database := n.(sql.Databaser).Database()
6363
if database != nil {
6464
dbName = database.Name()

0 commit comments

Comments
 (0)