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
8 changes: 6 additions & 2 deletions src/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ Configure database connection using environment variables to enable PostgreSQL m

```bash
# Required to enable PostgreSQL mode
export DATABASE_URL=jdbc:postgresql://localhost:5432/lampcontrol
# Accepted formats: jdbc:postgresql://..., postgresql://..., postgres://...
export DATABASE_URL=postgresql://localhost:5432/lampcontrol

# Optional override (takes precedence and is used as-is without normalization)
# export SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/lampcontrol

# Database credentials
export DB_USER=lampuser
Expand Down Expand Up @@ -200,7 +204,7 @@ spring.datasource.hikari.minimum-idle=5
mvn spring-boot:run

# Run with PostgreSQL (requires DATABASE_URL)
DATABASE_URL=jdbc:postgresql://localhost:5432/lampcontrol \
DATABASE_URL=postgresql://localhost:5432/lampcontrol \
FLYWAY_ENABLED=true \
DB_USER=lampuser \
DB_PASSWORD=lamppass \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@
@Conditional(OnDatabaseUrlCondition.class)
public class DataSourceConfig {

@Value("${SPRING_DATASOURCE_URL:}")
private String springDatasourceUrl;

@Value("${DATABASE_URL:}")
private String databaseUrl;

@Value("${spring.datasource.url}")
private String jdbcUrl;
private String fallbackJdbcUrl;

@Value("${spring.datasource.username:lampuser}")
private String username;
Expand All @@ -45,14 +51,40 @@ public HikariConfig hikariConfig() {
HikariConfig config = new HikariConfig();

// Set core JDBC properties
config.setJdbcUrl(jdbcUrl);
config.setJdbcUrl(resolveJdbcUrl());
config.setUsername(username);
config.setPassword(password);
config.setDriverClassName(driverClassName);

return config;
}

private String resolveJdbcUrl() {
if (isNotBlank(springDatasourceUrl)) {
return springDatasourceUrl;
}

if (isNotBlank(databaseUrl)) {
return normalizeDatabaseUrl(databaseUrl);
}

return fallbackJdbcUrl;
}

private String normalizeDatabaseUrl(String url) {
if (url.startsWith("postgresql://")) {
return "jdbc:" + url;
}
if (url.startsWith("postgres://")) {
return "jdbc:postgresql://" + url.substring("postgres://".length());
Comment on lines +75 to +79
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeDatabaseUrl() currently only rewrites the URI scheme by prefixing/replacing strings. If DATABASE_URL is a full Postgres URI with userinfo (e.g. postgresql://user:pass@host:5432/db?sslmode=require), the resulting JDBC URL (jdbc:postgresql://user:pass@host:5432/db...) is not a valid PostgreSQL JDBC URL and will fail to connect. Consider parsing the URI (e.g., via java.net.URI) and constructing a proper jdbc:postgresql://host:port/db URL, mapping user/password into either Hikari username/password or query parameters, and preserving query params safely (including URL-decoding/encoding as needed).

Copilot uses AI. Check for mistakes.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. This PR intentionally scopes compatibility to scheme normalization only (postgresql:// and postgres:// -> jdbc:postgresql://) for DATABASE_URL, while leaving SPRING_DATASOURCE_URL behavior unchanged. Handling full URI parsing (userinfo/query remapping) is a valid enhancement, but it is out of scope for this change and we can track it as a follow-up.

}
return url;
}

private boolean isNotBlank(String value) {
return value != null && !value.isBlank();
}

/**
* Creates a HikariCP DataSource bean from the configured HikariConfig.
*
Expand Down
4 changes: 3 additions & 1 deletion src/java/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
# Database Configuration (PostgreSQL)
# By default, the application uses an in-memory repository
# To enable PostgreSQL, set DATABASE_URL environment variable
# Example: DATABASE_URL=jdbc:postgresql://localhost:5432/lampcontrol
# DATABASE_URL supports: jdbc:postgresql://..., postgresql://..., postgres://...
# SPRING_DATASOURCE_URL is used as-is when set (no normalization)
# Example: DATABASE_URL=postgresql://localhost:5432/lampcontrol
spring.datasource.url=${SPRING_DATASOURCE_URL:${DATABASE_URL:}}
spring.datasource.username=${DB_USER:lampuser}
spring.datasource.password=${DB_PASSWORD:lamppass}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,86 @@
class DataSourceConfigTest {

@Test
void hikariConfig_ShouldReturnConfiguredHikariConfig() throws Exception {
void hikariConfig_ShouldUseSpringDatasourceUrlAsIs() throws Exception {
DataSourceConfig config = new DataSourceConfig();

setField(config, "jdbcUrl", "jdbc:postgresql://localhost:5432/lamp");
setField(config, "springDatasourceUrl", "postgresql://localhost:5432/lamp");
setField(config, "databaseUrl", "postgres://ignored:5432/ignored");
setField(config, "fallbackJdbcUrl", "jdbc:postgresql://localhost:5432/fallback");
setField(config, "username", "user");
setField(config, "password", "pass");
setField(config, "driverClassName", "org.postgresql.Driver");

HikariConfig result = config.hikariConfig();

assertThat(result.getJdbcUrl()).isEqualTo("postgresql://localhost:5432/lamp");
assertThat(result.getUsername()).isEqualTo("user");
assertThat(result.getPassword()).isEqualTo("pass");
assertThat(result.getDriverClassName()).isEqualTo("org.postgresql.Driver");
}

@Test
void hikariConfig_ShouldNormalizeDatabaseUrlPostgresqlScheme() throws Exception {
DataSourceConfig config = new DataSourceConfig();

setField(config, "springDatasourceUrl", "");
setField(config, "databaseUrl", "postgresql://localhost:5432/lamp");
setField(config, "fallbackJdbcUrl", "jdbc:postgresql://localhost:5432/fallback");
setField(config, "username", "user");
setField(config, "password", "pass");
setField(config, "driverClassName", "org.postgresql.Driver");

HikariConfig result = config.hikariConfig();

assertThat(result.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/lamp");
Comment on lines +32 to 44
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new normalization tests cover scheme-only rewrites but don’t cover the most common DATABASE_URL form that includes credentials and/or query params (e.g. postgres://user:pass@host:5432/db?sslmode=disable). Adding unit tests for these cases will prevent regressions once normalizeDatabaseUrl() is updated to produce valid JDBC URLs for full Postgres URIs.

Copilot generated this review using guidance from repository custom instructions.
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this would be useful once we broaden parsing behavior. For this PR we intentionally kept scope to scheme compatibility only, so tests cover that explicit contract (including precedence and passthrough behavior). I’m leaving full credential/query-param URI parsing and corresponding tests for a follow-up change.

}

@Test
void hikariConfig_ShouldNormalizeDatabaseUrlPostgresScheme() throws Exception {
DataSourceConfig config = new DataSourceConfig();

setField(config, "springDatasourceUrl", "");
setField(config, "databaseUrl", "postgres://localhost:5432/lamp");
setField(config, "fallbackJdbcUrl", "jdbc:postgresql://localhost:5432/fallback");
setField(config, "username", "user");
setField(config, "password", "pass");
setField(config, "driverClassName", "org.postgresql.Driver");

HikariConfig result = config.hikariConfig();

assertThat(result.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/lamp");
}

@Test
void hikariConfig_ShouldKeepJdbcDatabaseUrlUnchanged() throws Exception {
DataSourceConfig config = new DataSourceConfig();

setField(config, "springDatasourceUrl", "");
setField(config, "databaseUrl", "jdbc:postgresql://localhost:5432/lamp");
setField(config, "fallbackJdbcUrl", "jdbc:postgresql://localhost:5432/fallback");
setField(config, "username", "user");
setField(config, "password", "pass");
setField(config, "driverClassName", "org.postgresql.Driver");

HikariConfig result = config.hikariConfig();

assertThat(result.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/lamp");
}

@Test
void hikariConfig_ShouldFallbackToSpringDatasourceProperty() throws Exception {
DataSourceConfig config = new DataSourceConfig();

setField(config, "springDatasourceUrl", "");
setField(config, "databaseUrl", "");
setField(config, "fallbackJdbcUrl", "jdbc:postgresql://localhost:5432/fallback");
setField(config, "username", "user");
setField(config, "password", "pass");
setField(config, "driverClassName", "org.postgresql.Driver");

HikariConfig result = config.hikariConfig();

assertThat(result.getJdbcUrl()).isEqualTo("jdbc:postgresql://localhost:5432/fallback");
assertThat(result.getUsername()).isEqualTo("user");
assertThat(result.getPassword()).isEqualTo("pass");
assertThat(result.getDriverClassName()).isEqualTo("org.postgresql.Driver");
Expand Down
Loading