Skip to content

Commit e7bc1be

Browse files
committed
fix: parse MySQL events as SHOW CREATE EVENT prints them
SHOW CREATE EVENT prints DEFINER=user@host before EVENT, and MySQL 8.4 prints DISABLE ON REPLICA for an event that is disabled on replicas. Event bodies are often BEGIN ... END blocks. CREATE and ALTER EVENT with a DEFINER fell back to UnsupportedStatement, and DISABLE ON REPLICA and a BEGIN ... END body failed. CREATE EVENT and ALTER EVENT now read DEFINER with the production that stored routines use and keep it in EventStatement.getDefiner(). The body reuses MySqlTriggerBody, so a block of SQL statements is a Block and other compound statements keep their tokens, as in triggers. EventStatus gains DISABLE_ON_REPLICA; DISABLE ON SLAVE still gives DISABLE_ON_SLAVE. Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent c84ad0e commit e7bc1be

7 files changed

Lines changed: 150 additions & 12 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/create/event/AlterEvent.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
3232

3333
@Override
3434
public String toString() {
35-
StringBuilder builder = new StringBuilder("ALTER EVENT ").append(getEvent());
35+
StringBuilder builder = new StringBuilder("ALTER ");
36+
appendDefinerAndEvent(builder);
37+
builder.append(getEvent());
3638
appendScheduleAndCompletion(builder);
3739
if (renameTo != null) {
3840
builder.append(" RENAME TO ").append(renameTo);

‎src/main/java/net/sf/jsqlparser/statement/create/event/CreateEvent.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
3131

3232
@Override
3333
public String toString() {
34-
StringBuilder builder = new StringBuilder("CREATE EVENT ");
34+
StringBuilder builder = new StringBuilder("CREATE ");
35+
appendDefinerAndEvent(builder);
3536
if (ifNotExists) {
3637
builder.append("IF NOT EXISTS ");
3738
}

‎src/main/java/net/sf/jsqlparser/statement/create/event/EventStatement.java‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,28 @@
1111

1212
import net.sf.jsqlparser.expression.StringValue;
1313
import net.sf.jsqlparser.schema.Table;
14+
import net.sf.jsqlparser.statement.MySqlDefiner;
1415
import net.sf.jsqlparser.statement.Statement;
1516

1617
/** Common structured properties of MySQL {@code CREATE EVENT} and {@code ALTER EVENT}. */
1718
public abstract class EventStatement implements Statement {
1819

20+
private MySqlDefiner definer;
1921
private Table event;
2022
private EventSchedule schedule;
2123
private Boolean onCompletionPreserve;
2224
private EventStatus status;
2325
private StringValue comment;
2426
private Statement body;
2527

28+
public MySqlDefiner getDefiner() {
29+
return definer;
30+
}
31+
32+
public void setDefiner(MySqlDefiner definer) {
33+
this.definer = definer;
34+
}
35+
2636
public Table getEvent() {
2737
return event;
2838
}
@@ -75,6 +85,14 @@ public void setBody(Statement body) {
7585
this.body = body;
7686
}
7787

88+
/** Appends the optional DEFINER clause, followed by EVENT, after CREATE or ALTER. */
89+
protected void appendDefinerAndEvent(StringBuilder builder) {
90+
if (definer != null) {
91+
builder.append("DEFINER = ").append(definer).append(' ');
92+
}
93+
builder.append("EVENT ");
94+
}
95+
7896
protected void appendScheduleAndCompletion(StringBuilder builder) {
7997
if (schedule != null) {
8098
builder.append(" ON SCHEDULE ").append(schedule);

‎src/main/java/net/sf/jsqlparser/statement/create/event/EventStatus.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
package net.sf.jsqlparser.statement.create.event;
1111

1212
public enum EventStatus {
13-
ENABLE("ENABLE"), DISABLE("DISABLE"), DISABLE_ON_SLAVE("DISABLE ON SLAVE");
13+
ENABLE("ENABLE"), DISABLE("DISABLE"), DISABLE_ON_SLAVE("DISABLE ON SLAVE"), DISABLE_ON_REPLICA(
14+
"DISABLE ON REPLICA");
1415

1516
private final String sql;
1617

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17330,7 +17330,13 @@ EventStatus MySqlEventStatus():
1733017330
<K_ENABLE> { status = EventStatus.ENABLE; }
1733117331
|
1733217332
<K_DISABLE> { status = EventStatus.DISABLE; }
17333-
[ LOOKAHEAD(2) <K_ON> <K_SLAVE> { status = EventStatus.DISABLE_ON_SLAVE; } ]
17333+
[ LOOKAHEAD({ getToken(1).kind == K_ON && (getToken(2).kind == K_SLAVE
17334+
|| "REPLICA".equalsIgnoreCase(getToken(2).image)) })
17335+
<K_ON> (
17336+
<K_SLAVE> { status = EventStatus.DISABLE_ON_SLAVE; }
17337+
|
17338+
ContextualKeyword("REPLICA") { status = EventStatus.DISABLE_ON_REPLICA; }
17339+
) ]
1733417340
)
1733517341
{ return status; }
1733617342
}
@@ -17344,8 +17350,11 @@ CreateEvent CreateEvent():
1734417350
EventStatus status = null;
1734517351
Token comment = null;
1734617352
Statement body = null;
17353+
MySqlDefiner definer = null;
1734717354
}
1734817355
{
17356+
[ LOOKAHEAD({ isKeywordAhead("DEFINER") })
17357+
definer=MySqlRoutineDefiner() { createEvent.setDefiner(definer); } ]
1734917358
<K_EVENT>
1735017359
[ LOOKAHEAD(3) <K_IF> <K_NOT> <K_EXISTS> { createEvent.setIfNotExists(true); } ]
1735117360
event=Table() { createEvent.setEvent(event); }
@@ -17358,7 +17367,7 @@ CreateEvent CreateEvent():
1735817367
[ LOOKAHEAD({ getToken(1).kind == K_COMMENT }) <K_COMMENT> comment=<S_CHAR_LITERAL> {
1735917368
createEvent.setComment(stringValue(comment));
1736017369
} ]
17361-
<K_DO> body=SingleStatement() { createEvent.setBody(body); }
17370+
<K_DO> body=MySqlTriggerBody() { createEvent.setBody(body); }
1736217371
{ return createEvent; }
1736317372
}
1736417373

@@ -17372,8 +17381,11 @@ AlterEvent AlterEvent():
1737217381
EventStatus status = null;
1737317382
Token comment = null;
1737417383
Statement body = null;
17384+
MySqlDefiner definer = null;
1737517385
}
1737617386
{
17387+
[ LOOKAHEAD({ isKeywordAhead("DEFINER") })
17388+
definer=MySqlRoutineDefiner() { alterEvent.setDefiner(definer); } ]
1737717389
<K_EVENT> event=Table() { alterEvent.setEvent(event); }
1737817390
[ LOOKAHEAD({ getToken(1).kind == K_ON && getToken(2).kind == K_SCHEDULE })
1737917391
schedule=MySqlEventSchedule() { alterEvent.setSchedule(schedule); } ]
@@ -17387,7 +17399,7 @@ AlterEvent AlterEvent():
1738717399
alterEvent.setComment(stringValue(comment));
1738817400
} ]
1738917401
[ LOOKAHEAD({ getToken(1).kind == K_DO })
17390-
<K_DO> body=SingleStatement() { alterEvent.setBody(body); } ]
17402+
<K_DO> body=MySqlTriggerBody() { alterEvent.setBody(body); } ]
1739117403
{ return alterEvent; }
1739217404
}
1739317405

@@ -17472,9 +17484,9 @@ CreateTrigger.Order MySqlTriggerOrder():
1747217484
}
1747317485

1747417486
/**
17475-
* MySQL trigger statement. BEGIN ... END with SQL statements only remains a Block. Other compound
17476-
* statements, such as those with IF or DECLARE, keep their tokens like routine bodies and use the
17477-
* same boundary tracking. The semicolon after END is left to separate statements.
17487+
* MySQL trigger or event statement. BEGIN ... END with SQL statements only remains a Block. Other
17488+
* compound statements, such as those with IF or DECLARE, keep their tokens like routine bodies and
17489+
* use the same boundary tracking. The semicolon after END is left to separate statements.
1747817490
*/
1747917491
Statement MySqlTriggerBody():
1748017492
{
@@ -21824,6 +21836,8 @@ Statement Alter():
2182421836
|
2182521837
LOOKAHEAD({ isPostgreSqlEventTriggerAhead(true) }) statement = AlterEventTrigger()
2182621838
|
21839+
LOOKAHEAD([ MySqlRoutineDefiner() ] <K_EVENT>,
21840+
{ getToken(1).kind == K_EVENT || isKeywordAhead("DEFINER") })
2182721841
statement = AlterEvent()
2182821842
|
2182921843
LOOKAHEAD(( <K_TABLE> | <K_INDEX> | <K_MATERIALIZED> <K_VIEW> ) <K_ALL> <K_IN> <K_TABLESPACE>)
@@ -23625,6 +23639,8 @@ Statement Create():
2362523639
{ requireDdlSyntax(!isUsingOrReplace, "CREATE EVENT TRIGGER does not support OR REPLACE"); }
2362623640
statement = CreateEventTrigger()
2362723641
|
23642+
LOOKAHEAD([ MySqlRoutineDefiner() ] <K_EVENT>,
23643+
{ getToken(1).kind == K_EVENT || isKeywordAhead("DEFINER") })
2362823644
statement = CreateEvent()
2362923645
|
2363023646
LOOKAHEAD(MySqlViewPrefix() <K_VIEW>) statement = CreateView(isUsingOrReplace)

‎src/test/java/net/sf/jsqlparser/statement/create/CreateFunctionalStatementTest.java‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
import net.sf.jsqlparser.expression.StringValue;
1919
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
2020
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
21-
import net.sf.jsqlparser.statement.UnsupportedStatement;
2221
import net.sf.jsqlparser.statement.MySqlDefiner;
2322
import net.sf.jsqlparser.statement.CreateFunctionalStatement;
2423
import net.sf.jsqlparser.statement.Statements;
24+
import net.sf.jsqlparser.statement.create.event.CreateEvent;
2525
import net.sf.jsqlparser.statement.create.view.CreateView;
2626
import net.sf.jsqlparser.statement.create.trigger.CreateTrigger;
2727
import net.sf.jsqlparser.util.deparser.StatementDeParser;
@@ -154,9 +154,8 @@ public void definerDispatchKeepsTriggersAndViews() throws JSQLParserException {
154154
assertThat(CCJSqlParserUtil.parse("CREATE FUNCTION f() RETURNS int AS 'SELECT 1'"))
155155
.isInstanceOf(CreateFunction.class)
156156
.extracting(statement -> ((CreateFunction) statement).getDefiner()).isNull();
157-
// DEFINER is not modelled for EVENT, so the statement falls back like other unknown forms
158157
assertThat(CCJSqlParserUtil.parse("CREATE DEFINER = x EVENT e "
159-
+ "ON SCHEDULE EVERY 1 DAY DO SELECT 1")).isInstanceOf(UnsupportedStatement.class);
158+
+ "ON SCHEDULE EVERY 1 DAY DO SELECT 1")).isInstanceOf(CreateEvent.class);
160159
}
161160

162161
@Test

‎src/test/java/net/sf/jsqlparser/statement/create/event/EventStatementTest.java‎

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,33 @@
1010
package net.sf.jsqlparser.statement.create.event;
1111

1212
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
13+
import static net.sf.jsqlparser.test.TestUtils.buildSqlString;
1314
import static org.junit.jupiter.api.Assertions.assertEquals;
1415
import static org.junit.jupiter.api.Assertions.assertFalse;
1516
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
1617
import static org.junit.jupiter.api.Assertions.assertNull;
18+
import static org.junit.jupiter.api.Assertions.assertTrue;
1719

20+
import java.util.HashSet;
21+
import java.util.List;
22+
import java.util.Set;
23+
import java.util.function.Consumer;
1824
import net.sf.jsqlparser.JSQLParserException;
1925
import net.sf.jsqlparser.expression.LongValue;
26+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
27+
import net.sf.jsqlparser.parser.CCJSqlParser;
28+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
29+
import net.sf.jsqlparser.statement.Block;
30+
import net.sf.jsqlparser.statement.Statement;
31+
import net.sf.jsqlparser.statement.Statements;
32+
import net.sf.jsqlparser.statement.UnsupportedStatement;
33+
import net.sf.jsqlparser.statement.delete.Delete;
2034
import net.sf.jsqlparser.statement.truncate.Truncate;
2135
import net.sf.jsqlparser.statement.update.Update;
36+
import net.sf.jsqlparser.util.TablesNamesFinder;
2237
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.params.ParameterizedTest;
39+
import org.junit.jupiter.params.provider.ValueSource;
2340

2441
class EventStatementTest {
2542

@@ -92,4 +109,88 @@ void parsesCompletionPolicy() throws JSQLParserException {
92109
assertFalse(event.getOnCompletionPreserve());
93110
assertEquals(EventStatus.ENABLE, event.getStatus());
94111
}
112+
113+
@ParameterizedTest
114+
@ValueSource(strings = {
115+
"CREATE DEFINER = `root`@`localhost` EVENT `e_daily` ON SCHEDULE EVERY 1 DAY "
116+
+ "STARTS CURRENT_TIMESTAMP + INTERVAL 6 HOUR ON COMPLETION NOT PRESERVE "
117+
+ "ENABLE COMMENT 'Saves total number of sessions then clears the table' "
118+
+ "DO BEGIN INSERT INTO site_activity.totals (time, total) "
119+
+ "SELECT CURRENT_TIMESTAMP, COUNT(*) FROM site_activity.sessions; "
120+
+ "DELETE FROM site_activity.sessions; END",
121+
"CREATE DEFINER = 'admin'@'%' EVENT IF NOT EXISTS ev ON SCHEDULE EVERY 1 HOUR "
122+
+ "DO SELECT 1",
123+
"CREATE DEFINER = CURRENT_USER() EVENT ev ON SCHEDULE AT CURRENT_TIMESTAMP "
124+
+ "DISABLE ON REPLICA DO SELECT 1",
125+
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY DO BEGIN END",
126+
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY "
127+
+ "DO BEGIN IF @a > 0 THEN DELETE FROM t1; END IF; END",
128+
"ALTER DEFINER = CURRENT_USER EVENT ev ON COMPLETION PRESERVE RENAME TO db2.ev2 "
129+
+ "ENABLE COMMENT 'moved' DO SELECT 2",
130+
"ALTER EVENT ev DISABLE ON REPLICA",
131+
"ALTER EVENT ev DISABLE ON SLAVE",
132+
"ALTER EVENT ev DO BEGIN DELETE FROM t1; DELETE FROM t2; END"})
133+
void parsesMySqlEventForms(String sql) throws JSQLParserException {
134+
for (Consumer<CCJSqlParser> consumer : List.<Consumer<CCJSqlParser>>of(
135+
parser -> parser.withDialect(Dialect.MYSQL), parser -> {
136+
})) {
137+
// a statement block prints one statement per line
138+
Statement event = assertSqlCanBeParsedAndDeparsed(sql, true, consumer);
139+
assertInstanceOf(EventStatement.class, event);
140+
Statements script = CCJSqlParserUtil.parseStatements(sql + ";\nSELECT 1", consumer);
141+
assertEquals(2, script.size());
142+
assertEquals(buildSqlString(sql, true),
143+
buildSqlString(script.get(0).toString(), true));
144+
}
145+
}
146+
147+
@Test
148+
void parsesDefinerAsShowCreateEventPrintsIt() throws JSQLParserException {
149+
CreateEvent event = assertInstanceOf(CreateEvent.class, CCJSqlParserUtil.parse(
150+
"CREATE DEFINER=`root`@`localhost` EVENT `ev` ON SCHEDULE EVERY 1 DAY "
151+
+ "STARTS '2026-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE "
152+
+ "DO DELETE FROM t1"));
153+
154+
assertEquals("root", event.getDefiner().getUser().getValue());
155+
assertEquals("localhost", event.getDefiner().getHost().getValue());
156+
assertInstanceOf(Delete.class, event.getBody());
157+
assertEquals("CREATE DEFINER = `root`@`localhost` EVENT `ev` ON SCHEDULE EVERY 1 DAY "
158+
+ "STARTS '2026-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE "
159+
+ "DO DELETE FROM t1", event.toString());
160+
161+
AlterEvent altered = assertInstanceOf(AlterEvent.class, CCJSqlParserUtil.parse(
162+
"ALTER DEFINER = 'admin'@'%' EVENT ev ENABLE"));
163+
assertEquals("admin", altered.getDefiner().getUser().getValue());
164+
assertEquals("%", altered.getDefiner().getHost().getValue());
165+
assertEquals(EventStatus.ENABLE, altered.getStatus());
166+
}
167+
168+
@Test
169+
void keepsStatementBlockBodiesStructured() throws JSQLParserException {
170+
CreateEvent event = assertInstanceOf(CreateEvent.class, CCJSqlParserUtil.parse(
171+
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY "
172+
+ "DO BEGIN DELETE FROM a; DELETE FROM b; END",
173+
parser -> parser.withDialect(Dialect.MYSQL)));
174+
175+
Block body = assertInstanceOf(Block.class, event.getBody());
176+
assertEquals(2, body.getStatements().size());
177+
assertInstanceOf(Delete.class, body.getStatements().get(0));
178+
assertEquals(Set.of("a", "b"),
179+
new HashSet<>(new TablesNamesFinder<>().getTables((Statement) event)));
180+
181+
AlterEvent altered = assertInstanceOf(AlterEvent.class, CCJSqlParserUtil.parse(
182+
"ALTER EVENT ev DO BEGIN IF @a > 0 THEN DELETE FROM t1; END IF; END"));
183+
assertTrue(altered.getBody() instanceof UnsupportedStatement);
184+
}
185+
186+
@Test
187+
void parsesDisableOnReplica() throws JSQLParserException {
188+
AlterEvent event = assertInstanceOf(AlterEvent.class,
189+
CCJSqlParserUtil.parse("ALTER EVENT ev DISABLE ON REPLICA"));
190+
assertEquals(EventStatus.DISABLE_ON_REPLICA, event.getStatus());
191+
192+
event = assertInstanceOf(AlterEvent.class,
193+
CCJSqlParserUtil.parse("ALTER EVENT ev DISABLE ON SLAVE"));
194+
assertEquals(EventStatus.DISABLE_ON_SLAVE, event.getStatus());
195+
}
95196
}

0 commit comments

Comments
 (0)