Skip to content

Commit 1d09790

Browse files
Feat(mysql-event-listener): Add user and password config to MySQL event listener
Allow configuring MySQL credentials for the event listener using separate mysql-event-listener.db.user and mysql-event-listener.db.password properties. This makes it easier and safer to provide credentials, especially when passwords contain special characters, and aligns with the configuration style of other Trino connectors. The connection logic now uses these properties if provided, falling back to credentials in the JDBC URL if not set.
1 parent 6abf6a9 commit 1d09790

File tree

5 files changed

+90
-15
lines changed

5 files changed

+90
-15
lines changed

docs/src/main/sphinx/admin/event-listeners-mysql.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,12 @@ string, user, catalog, and others with information about the query processing.
6666
* - Property name
6767
- Description
6868
* - `mysql-event-listener.db.url`
69-
- JDBC connection URL to the database including credentials
69+
- Fully qualified JDBC connection URL used to connect to the MySQL database.
70+
Credentials can be embedded in the URL if `user` and `password` are not specified separately
71+
* - `mysql-event-listener.db.user`
72+
- Username used for authenticating the JDBC connection to the MySQL database.
73+
An exception will be raised if credentials are included in the `URL`.
74+
* - `mysql-event-listener.db.password`
75+
- Password used for authenticating the JDBC connection to the MySQL database.
76+
An exception will be raised if credentials are included in the `URL`.
7077
:::

plugin/trino-mysql-event-listener/src/main/java/io/trino/plugin/eventlistener/mysql/MysqlEventListenerConfig.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@
1515

1616
import com.mysql.cj.jdbc.Driver;
1717
import io.airlift.configuration.Config;
18+
import io.airlift.configuration.ConfigDescription;
1819
import io.airlift.configuration.ConfigSecuritySensitive;
1920
import jakarta.validation.constraints.AssertTrue;
2021
import jakarta.validation.constraints.NotNull;
2122

2223
import java.sql.SQLException;
24+
import java.util.Optional;
2325

2426
public class MysqlEventListenerConfig
2527
{
2628
private String url;
29+
private Optional<String> user = Optional.empty();
30+
private Optional<String> password = Optional.empty();
2731

2832
@NotNull
2933
public String getUrl()
@@ -39,11 +43,47 @@ public MysqlEventListenerConfig setUrl(String url)
3943
return this;
4044
}
4145

46+
public Optional<String> getUser()
47+
{
48+
return user;
49+
}
50+
51+
@ConfigDescription("MySQL connection user")
52+
@Config("mysql-event-listener.db.user")
53+
public MysqlEventListenerConfig setUser(String user)
54+
{
55+
this.user = Optional.ofNullable(user);
56+
return this;
57+
}
58+
59+
public Optional<String> getPassword()
60+
{
61+
return password;
62+
}
63+
64+
@ConfigSecuritySensitive
65+
@ConfigDescription("MySQL connection password")
66+
@Config("mysql-event-listener.db.password")
67+
public MysqlEventListenerConfig setPassword(String password)
68+
{
69+
this.password = Optional.ofNullable(password);
70+
return this;
71+
}
72+
4273
@AssertTrue(message = "Invalid JDBC URL for MySQL event listener")
4374
public boolean isValidUrl()
4475
{
4576
try {
46-
return new Driver().acceptsURL(getUrl());
77+
String url = getUrl();
78+
// Check for user property
79+
if (getUser().isPresent() && url != null && url.matches(".*[?&]user=.+")) {
80+
throw new RuntimeException("'user' property is set, but JDBC URL also contains 'user' in the query string. Please specify the user only once.");
81+
}
82+
// Check for password property
83+
if (getPassword().isPresent() && url != null && url.matches(".*[?&]password=.+")) {
84+
throw new RuntimeException("'password' property is set, but JDBC URL also contains 'password' in the query string. Please specify the password only once.");
85+
}
86+
return new Driver().acceptsURL(url);
4787
}
4888
catch (SQLException e) {
4989
throw new RuntimeException(e);

plugin/trino-mysql-event-listener/src/main/java/io/trino/plugin/eventlistener/mysql/MysqlEventListenerFactory.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,16 @@ public void configure(Binder binder)
9090
@Provides
9191
public ConnectionFactory createConnectionFactory(MysqlEventListenerConfig config)
9292
{
93-
return () -> new Driver().connect(config.getUrl(), new Properties());
93+
return () -> {
94+
Properties properties = new Properties();
95+
if (config.getUser() != null) {
96+
properties.setProperty("user", config.getUser());
97+
}
98+
if (config.getPassword() != null) {
99+
properties.setProperty("password", config.getPassword());
100+
}
101+
return new Driver().connect(config.getUrl(), properties);
102+
};
94103
}
95104

96105
@Singleton

plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListener.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ final class TestMysqlEventListener
354354

355355
private MySQLContainer<?> mysqlContainer;
356356
private String mysqlContainerUrl;
357+
private String mysqlContainerUser;
358+
private String mysqlContainerPassword;
357359
private EventListener eventListener;
358360
private JsonCodecFactory jsonCodecFactory;
359361

@@ -363,8 +365,12 @@ void setup()
363365
mysqlContainer = new MySQLContainer<>("mysql:8.0.36");
364366
mysqlContainer.start();
365367
mysqlContainerUrl = getJdbcUrl(mysqlContainer);
368+
mysqlContainerUser = mysqlContainer.getUsername();
369+
mysqlContainerPassword = mysqlContainer.getPassword();
366370
eventListener = new MysqlEventListenerFactory()
367-
.create(Map.of("mysql-event-listener.db.url", mysqlContainerUrl), new TestingEventListenerContext());
371+
.create(Map.of("mysql-event-listener.db.url", mysqlContainerUrl,
372+
"mysql-event-listener.db.user", mysqlContainerUser,
373+
"mysql-event-listener.db.password", mysqlContainerPassword), new TestingEventListenerContext());
368374
jsonCodecFactory = new JsonCodecFactory();
369375
}
370376

@@ -375,17 +381,17 @@ void teardown()
375381
mysqlContainer.close();
376382
mysqlContainer = null;
377383
mysqlContainerUrl = null;
384+
mysqlContainerUser = null;
385+
mysqlContainerPassword = null;
378386
}
379387
eventListener = null;
380388
jsonCodecFactory = null;
381389
}
382390

383391
private static String getJdbcUrl(MySQLContainer<?> container)
384392
{
385-
return format("%s?user=%s&password=%s&useSSL=false&allowPublicKeyRetrieval=true",
386-
container.getJdbcUrl(),
387-
container.getUsername(),
388-
container.getPassword());
393+
return format("%s?useSSL=false&allowPublicKeyRetrieval=true",
394+
container.getJdbcUrl());
389395
}
390396

391397
@Test

plugin/trino-mysql-event-listener/src/test/java/io/trino/plugin/eventlistener/mysql/TestMysqlEventListenerConfig.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.junit.jupiter.api.Test;
1717

1818
import java.util.Map;
19+
import java.util.Optional;
1920

2021
import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
2122
import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults;
@@ -28,33 +29,45 @@ final class TestMysqlEventListenerConfig
2829
void testDefaults()
2930
{
3031
assertRecordedDefaults(recordDefaults(MysqlEventListenerConfig.class)
31-
.setUrl(null));
32+
.setUrl(null)
33+
.setUser(null)
34+
.setPassword(null));
3235
}
3336

3437
@Test
3538
void testExplicitPropertyMappings()
3639
{
3740
Map<String, String> properties = Map.of(
38-
"mysql-event-listener.db.url", "jdbc:mysql://example.net:3306");
41+
"mysql-event-listener.db.url", "jdbc:mysql://example.net:3306",
42+
"mysql-event-listener.db.user", "user",
43+
"mysql-event-listener.db.password", "password");
3944

4045
MysqlEventListenerConfig expected = new MysqlEventListenerConfig()
41-
.setUrl("jdbc:mysql://example.net:3306");
46+
.setUrl("jdbc:mysql://example.net:3306")
47+
.setUser("user")
48+
.setPassword("password");
4249

4350
assertFullMapping(properties, expected);
4451
}
4552

4653
@Test
4754
void testIsValidUrl()
4855
{
49-
assertThat(isValidUrl("jdbc:mysql://example.net:3306")).isTrue();
50-
assertThat(isValidUrl("jdbc:mysql://example.net:3306/")).isTrue();
51-
assertThat(isValidUrl("jdbc:postgresql://example.net:3306/somedatabase")).isFalse();
56+
assertThat(isValidUrl("jdbc:mysql://example.net:3306", Optional.empty(), Optional.empty())).isTrue();
57+
assertThat(isValidUrl("jdbc:mysql://example.net:3306/", Optional.empty(), Optional.empty())).isTrue();
58+
assertThat(isValidUrl("jdbc:postgresql://example.net:3306/somedatabase", Optional.empty(), Optional.empty())).isFalse();
59+
assertThat(isValidUrl("jdbc:mysql://example.net:3306?user=user", Optional.empty(), Optional.empty())).isTrue();
60+
assertThat(isValidUrl("jdbc:mysql://example.net:3306?user=user", Optional.of("user"), Optional.empty())).isFalse();
61+
assertThat(isValidUrl("jdbc:mysql://example.net:3306?password=password", Optional.empty(), Optional.empty())).isTrue();
62+
assertThat(isValidUrl("jdbc:mysql://example.net:3306?password=password", Optional.empty(), Optional.of("password"))).isFalse();
5263
}
5364

54-
private static boolean isValidUrl(String url)
65+
private static boolean isValidUrl(String url, Optional<String> user, Optional<String> password)
5566
{
5667
return new MysqlEventListenerConfig()
5768
.setUrl(url)
69+
.setUser(user.orElse(null))
70+
.setPassword(password.orElse(null))
5871
.isValidUrl();
5972
}
6073
}

0 commit comments

Comments
 (0)