Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {

@Override
public String toString() {
StringBuilder builder = new StringBuilder("ALTER EVENT ").append(getEvent());
StringBuilder builder = new StringBuilder("ALTER ");
appendDefinerAndEvent(builder);
builder.append(getEvent());
appendScheduleAndCompletion(builder);
if (renameTo != null) {
builder.append(" RENAME TO ").append(renameTo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {

@Override
public String toString() {
StringBuilder builder = new StringBuilder("CREATE EVENT ");
StringBuilder builder = new StringBuilder("CREATE ");
appendDefinerAndEvent(builder);
if (ifNotExists) {
builder.append("IF NOT EXISTS ");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,28 @@

import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.MySqlDefiner;
import net.sf.jsqlparser.statement.Statement;

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

private MySqlDefiner definer;
private Table event;
private EventSchedule schedule;
private Boolean onCompletionPreserve;
private EventStatus status;
private StringValue comment;
private Statement body;

public MySqlDefiner getDefiner() {
return definer;
}

public void setDefiner(MySqlDefiner definer) {
this.definer = definer;
}

public Table getEvent() {
return event;
}
Expand Down Expand Up @@ -75,6 +85,14 @@ public void setBody(Statement body) {
this.body = body;
}

/** Appends the optional DEFINER clause, followed by EVENT, after CREATE or ALTER. */
protected void appendDefinerAndEvent(StringBuilder builder) {
if (definer != null) {
builder.append("DEFINER = ").append(definer).append(' ');
}
builder.append("EVENT ");
}

protected void appendScheduleAndCompletion(StringBuilder builder) {
if (schedule != null) {
builder.append(" ON SCHEDULE ").append(schedule);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
package net.sf.jsqlparser.statement.create.event;

public enum EventStatus {
ENABLE("ENABLE"), DISABLE("DISABLE"), DISABLE_ON_SLAVE("DISABLE ON SLAVE");
ENABLE("ENABLE"), DISABLE("DISABLE"), DISABLE_ON_SLAVE("DISABLE ON SLAVE"), DISABLE_ON_REPLICA(
"DISABLE ON REPLICA");

private final String sql;

Expand Down
28 changes: 22 additions & 6 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -17330,7 +17330,13 @@ EventStatus MySqlEventStatus():
<K_ENABLE> { status = EventStatus.ENABLE; }
|
<K_DISABLE> { status = EventStatus.DISABLE; }
[ LOOKAHEAD(2) <K_ON> <K_SLAVE> { status = EventStatus.DISABLE_ON_SLAVE; } ]
[ LOOKAHEAD({ getToken(1).kind == K_ON && (getToken(2).kind == K_SLAVE
|| "REPLICA".equalsIgnoreCase(getToken(2).image)) })
<K_ON> (
<K_SLAVE> { status = EventStatus.DISABLE_ON_SLAVE; }
|
ContextualKeyword("REPLICA") { status = EventStatus.DISABLE_ON_REPLICA; }
) ]
)
{ return status; }
}
Expand All @@ -17344,8 +17350,11 @@ CreateEvent CreateEvent():
EventStatus status = null;
Token comment = null;
Statement body = null;
MySqlDefiner definer = null;
}
{
[ LOOKAHEAD({ isKeywordAhead("DEFINER") })
definer=MySqlRoutineDefiner() { createEvent.setDefiner(definer); } ]
<K_EVENT>
[ LOOKAHEAD(3) <K_IF> <K_NOT> <K_EXISTS> { createEvent.setIfNotExists(true); } ]
event=Table() { createEvent.setEvent(event); }
Expand All @@ -17358,7 +17367,7 @@ CreateEvent CreateEvent():
[ LOOKAHEAD({ getToken(1).kind == K_COMMENT }) <K_COMMENT> comment=<S_CHAR_LITERAL> {
createEvent.setComment(stringValue(comment));
} ]
<K_DO> body=SingleStatement() { createEvent.setBody(body); }
<K_DO> body=MySqlTriggerBody() { createEvent.setBody(body); }
{ return createEvent; }
}

Expand All @@ -17372,8 +17381,11 @@ AlterEvent AlterEvent():
EventStatus status = null;
Token comment = null;
Statement body = null;
MySqlDefiner definer = null;
}
{
[ LOOKAHEAD({ isKeywordAhead("DEFINER") })
definer=MySqlRoutineDefiner() { alterEvent.setDefiner(definer); } ]
<K_EVENT> event=Table() { alterEvent.setEvent(event); }
[ LOOKAHEAD({ getToken(1).kind == K_ON && getToken(2).kind == K_SCHEDULE })
schedule=MySqlEventSchedule() { alterEvent.setSchedule(schedule); } ]
Expand All @@ -17387,7 +17399,7 @@ AlterEvent AlterEvent():
alterEvent.setComment(stringValue(comment));
} ]
[ LOOKAHEAD({ getToken(1).kind == K_DO })
<K_DO> body=SingleStatement() { alterEvent.setBody(body); } ]
<K_DO> body=MySqlTriggerBody() { alterEvent.setBody(body); } ]
{ return alterEvent; }
}

Expand Down Expand Up @@ -17472,9 +17484,9 @@ CreateTrigger.Order MySqlTriggerOrder():
}

/**
* MySQL trigger statement. BEGIN ... END with SQL statements only remains a Block. Other compound
* statements, such as those with IF or DECLARE, keep their tokens like routine bodies and use the
* same boundary tracking. The semicolon after END is left to separate statements.
* MySQL trigger or event statement. BEGIN ... END with SQL statements only remains a Block. Other
* compound statements, such as those with IF or DECLARE, keep their tokens like routine bodies and
* use the same boundary tracking. The semicolon after END is left to separate statements.
*/
Statement MySqlTriggerBody():
{
Expand Down Expand Up @@ -21824,6 +21836,8 @@ Statement Alter():
|
LOOKAHEAD({ isPostgreSqlEventTriggerAhead(true) }) statement = AlterEventTrigger()
|
LOOKAHEAD([ MySqlRoutineDefiner() ] <K_EVENT>,
{ getToken(1).kind == K_EVENT || isKeywordAhead("DEFINER") })
statement = AlterEvent()
|
LOOKAHEAD(( <K_TABLE> | <K_INDEX> | <K_MATERIALIZED> <K_VIEW> ) <K_ALL> <K_IN> <K_TABLESPACE>)
Expand Down Expand Up @@ -23625,6 +23639,8 @@ Statement Create():
{ requireDdlSyntax(!isUsingOrReplace, "CREATE EVENT TRIGGER does not support OR REPLACE"); }
statement = CreateEventTrigger()
|
LOOKAHEAD([ MySqlRoutineDefiner() ] <K_EVENT>,
{ getToken(1).kind == K_EVENT || isKeywordAhead("DEFINER") })
statement = CreateEvent()
|
LOOKAHEAD(MySqlViewPrefix() <K_VIEW>) statement = CreateView(isUsingOrReplace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.UnsupportedStatement;
import net.sf.jsqlparser.statement.MySqlDefiner;
import net.sf.jsqlparser.statement.CreateFunctionalStatement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.create.event.CreateEvent;
import net.sf.jsqlparser.statement.create.view.CreateView;
import net.sf.jsqlparser.statement.create.trigger.CreateTrigger;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
Expand Down Expand Up @@ -154,9 +154,8 @@ public void definerDispatchKeepsTriggersAndViews() throws JSQLParserException {
assertThat(CCJSqlParserUtil.parse("CREATE FUNCTION f() RETURNS int AS 'SELECT 1'"))
.isInstanceOf(CreateFunction.class)
.extracting(statement -> ((CreateFunction) statement).getDefiner()).isNull();
// DEFINER is not modelled for EVENT, so the statement falls back like other unknown forms
assertThat(CCJSqlParserUtil.parse("CREATE DEFINER = x EVENT e "
+ "ON SCHEDULE EVERY 1 DAY DO SELECT 1")).isInstanceOf(UnsupportedStatement.class);
+ "ON SCHEDULE EVERY 1 DAY DO SELECT 1")).isInstanceOf(CreateEvent.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,33 @@
package net.sf.jsqlparser.statement.create.event;

import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
import static net.sf.jsqlparser.test.TestUtils.buildSqlString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParser;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Block;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.UnsupportedStatement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.truncate.Truncate;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.util.TablesNamesFinder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class EventStatementTest {

Expand Down Expand Up @@ -92,4 +109,88 @@ void parsesCompletionPolicy() throws JSQLParserException {
assertFalse(event.getOnCompletionPreserve());
assertEquals(EventStatus.ENABLE, event.getStatus());
}

@ParameterizedTest
@ValueSource(strings = {
"CREATE DEFINER = `root`@`localhost` EVENT `e_daily` ON SCHEDULE EVERY 1 DAY "
+ "STARTS CURRENT_TIMESTAMP + INTERVAL 6 HOUR ON COMPLETION NOT PRESERVE "
+ "ENABLE COMMENT 'Saves total number of sessions then clears the table' "
+ "DO BEGIN INSERT INTO site_activity.totals (time, total) "
+ "SELECT CURRENT_TIMESTAMP, COUNT(*) FROM site_activity.sessions; "
+ "DELETE FROM site_activity.sessions; END",
"CREATE DEFINER = 'admin'@'%' EVENT IF NOT EXISTS ev ON SCHEDULE EVERY 1 HOUR "
+ "DO SELECT 1",
"CREATE DEFINER = CURRENT_USER() EVENT ev ON SCHEDULE AT CURRENT_TIMESTAMP "
+ "DISABLE ON REPLICA DO SELECT 1",
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY DO BEGIN END",
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY "
+ "DO BEGIN IF @a > 0 THEN DELETE FROM t1; END IF; END",
"ALTER DEFINER = CURRENT_USER EVENT ev ON COMPLETION PRESERVE RENAME TO db2.ev2 "
+ "ENABLE COMMENT 'moved' DO SELECT 2",
"ALTER EVENT ev DISABLE ON REPLICA",
"ALTER EVENT ev DISABLE ON SLAVE",
"ALTER EVENT ev DO BEGIN DELETE FROM t1; DELETE FROM t2; END"})
void parsesMySqlEventForms(String sql) throws JSQLParserException {
for (Consumer<CCJSqlParser> consumer : List.<Consumer<CCJSqlParser>>of(
parser -> parser.withDialect(Dialect.MYSQL), parser -> {
})) {
// a statement block prints one statement per line
Statement event = assertSqlCanBeParsedAndDeparsed(sql, true, consumer);
assertInstanceOf(EventStatement.class, event);
Statements script = CCJSqlParserUtil.parseStatements(sql + ";\nSELECT 1", consumer);
assertEquals(2, script.size());
assertEquals(buildSqlString(sql, true),
buildSqlString(script.get(0).toString(), true));
}
}

@Test
void parsesDefinerAsShowCreateEventPrintsIt() throws JSQLParserException {
CreateEvent event = assertInstanceOf(CreateEvent.class, CCJSqlParserUtil.parse(
"CREATE DEFINER=`root`@`localhost` EVENT `ev` ON SCHEDULE EVERY 1 DAY "
+ "STARTS '2026-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE "
+ "DO DELETE FROM t1"));

assertEquals("root", event.getDefiner().getUser().getValue());
assertEquals("localhost", event.getDefiner().getHost().getValue());
assertInstanceOf(Delete.class, event.getBody());
assertEquals("CREATE DEFINER = `root`@`localhost` EVENT `ev` ON SCHEDULE EVERY 1 DAY "
+ "STARTS '2026-01-01 00:00:00' ON COMPLETION NOT PRESERVE ENABLE "
+ "DO DELETE FROM t1", event.toString());

AlterEvent altered = assertInstanceOf(AlterEvent.class, CCJSqlParserUtil.parse(
"ALTER DEFINER = 'admin'@'%' EVENT ev ENABLE"));
assertEquals("admin", altered.getDefiner().getUser().getValue());
assertEquals("%", altered.getDefiner().getHost().getValue());
assertEquals(EventStatus.ENABLE, altered.getStatus());
}

@Test
void keepsStatementBlockBodiesStructured() throws JSQLParserException {
CreateEvent event = assertInstanceOf(CreateEvent.class, CCJSqlParserUtil.parse(
"CREATE EVENT ev ON SCHEDULE EVERY 1 DAY "
+ "DO BEGIN DELETE FROM a; DELETE FROM b; END",
parser -> parser.withDialect(Dialect.MYSQL)));

Block body = assertInstanceOf(Block.class, event.getBody());
assertEquals(2, body.getStatements().size());
assertInstanceOf(Delete.class, body.getStatements().get(0));
assertEquals(Set.of("a", "b"),
new HashSet<>(new TablesNamesFinder<>().getTables((Statement) event)));

AlterEvent altered = assertInstanceOf(AlterEvent.class, CCJSqlParserUtil.parse(
"ALTER EVENT ev DO BEGIN IF @a > 0 THEN DELETE FROM t1; END IF; END"));
assertTrue(altered.getBody() instanceof UnsupportedStatement);
}

@Test
void parsesDisableOnReplica() throws JSQLParserException {
AlterEvent event = assertInstanceOf(AlterEvent.class,
CCJSqlParserUtil.parse("ALTER EVENT ev DISABLE ON REPLICA"));
assertEquals(EventStatus.DISABLE_ON_REPLICA, event.getStatus());

event = assertInstanceOf(AlterEvent.class,
CCJSqlParserUtil.parse("ALTER EVENT ev DISABLE ON SLAVE"));
assertEquals(EventStatus.DISABLE_ON_SLAVE, event.getStatus());
}
}
Loading