-
Notifications
You must be signed in to change notification settings - Fork 245
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sql): add SQL bootstrapper (#4376)
- Loading branch information
1 parent
fd137be
commit ae5664e
Showing
65 changed files
with
733 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
plugins { | ||
`java-library` | ||
`java-test-fixtures` | ||
`maven-publish` | ||
} | ||
|
||
dependencies { | ||
api(project(":spi:common:core-spi")) | ||
api(project(":spi:common:transaction-spi")) | ||
implementation(project(":spi:common:transaction-datasource-spi")) | ||
implementation(project(":extensions:common:sql:sql-core")) // SqlQueryExecutor | ||
// | ||
testImplementation(project(":core:common:junit")) | ||
testImplementation(libs.assertj) | ||
} | ||
|
||
|
18 changes: 18 additions & 0 deletions
18
...ql-bootstrapper/src/main/java/org/eclipse/edc/sql/bootstrapper/QueuedStatementRecord.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.sql.bootstrapper; | ||
|
||
record QueuedStatementRecord(String name, String datasourceName, String sql) { | ||
} |
83 changes: 83 additions & 0 deletions
83
...ql-bootstrapper/src/main/java/org/eclipse/edc/sql/bootstrapper/SqlDmlStatementRunner.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.sql.bootstrapper; | ||
|
||
import org.eclipse.edc.spi.monitor.Monitor; | ||
import org.eclipse.edc.spi.persistence.EdcPersistenceException; | ||
import org.eclipse.edc.spi.result.Result; | ||
import org.eclipse.edc.sql.QueryExecutor; | ||
import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; | ||
import org.eclipse.edc.transaction.spi.TransactionContext; | ||
|
||
import java.sql.Connection; | ||
import java.sql.SQLException; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
import static org.eclipse.edc.spi.result.Result.failure; | ||
import static org.eclipse.edc.spi.result.Result.success; | ||
|
||
/** | ||
* Internal class to the SQL Bootstrapper Extension module with the intended purpose to execute a series of DML statements against | ||
* the database. | ||
*/ | ||
class SqlDmlStatementRunner { | ||
|
||
private final TransactionContext transactionContext; | ||
private final QueryExecutor queryExecutor; | ||
private final Monitor monitor; | ||
private final DataSourceRegistry dataSourceRegistry; | ||
|
||
SqlDmlStatementRunner(TransactionContext transactionContext, QueryExecutor queryExecutor, Monitor monitor, DataSourceRegistry dataSourceRegistry) { | ||
this.transactionContext = transactionContext; | ||
this.queryExecutor = queryExecutor; | ||
this.monitor = monitor; | ||
this.dataSourceRegistry = dataSourceRegistry; | ||
} | ||
|
||
/** | ||
* Executes the queued DML statements one after the other. This method is intended to be called only from the {@link SqlSchemaBootstrapperExtension}. | ||
* | ||
* @param statements A map containing the datasource name as key and the SQL statements as value | ||
* @return A summary result of all the statements. | ||
*/ | ||
public Result<Void> executeSql(Map<String, List<String>> statements) { | ||
monitor.debug("Running DML statements: [%s]".formatted(String.join(", ", statements.keySet()))); | ||
return transactionContext.execute(() -> statements.entrySet().stream() | ||
.map(statement -> { | ||
var connectionResult = getConnection(statement.getKey()); | ||
return connectionResult.compose(connection -> { | ||
try { | ||
queryExecutor.execute(connection, String.join("", statement.getValue())); | ||
} catch (EdcPersistenceException sqlException) { | ||
return failure(sqlException.getMessage()); | ||
} | ||
return success(); | ||
}); | ||
}) | ||
.reduce(Result::merge) | ||
.orElse(Result.success())); | ||
} | ||
|
||
public Result<Connection> getConnection(String datasourceName) { | ||
try { | ||
var resolve = dataSourceRegistry.resolve(datasourceName); | ||
return resolve != null ? success(resolve.getConnection()) : | ||
failure("No datasource found with name '%s'".formatted(datasourceName)); | ||
} catch (SQLException e) { | ||
return failure(e.getMessage()); | ||
} | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
...ql-bootstrapper/src/main/java/org/eclipse/edc/sql/bootstrapper/SqlSchemaBootstrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.sql.bootstrapper; | ||
|
||
import org.eclipse.edc.spi.system.ServiceExtension; | ||
import org.eclipse.edc.spi.system.ServiceExtensionContext; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* Provides a convenient way to create database structures in an SQL database. DML statements can be added in the {@code initialize()} | ||
* phase of extensions and the bootstrapper takes care of executing them against the database. | ||
*/ | ||
public interface SqlSchemaBootstrapper { | ||
/** | ||
* Extensions that operate a store based on an SQL database and thus require a certain database structure to be present, | ||
* can use this class to have their schema auto-generated. The entire DDL has to be in a file that is available from the resources. | ||
* <p> | ||
* Note that all DDL statements <strong>must</strong> be queued during the {@link ServiceExtension#initialize(ServiceExtensionContext)} phase and | ||
* individual statements <strong>must not</strong> rely on ordering, since that depends on extension ordering. | ||
* | ||
* @param datasourceName The name of the datasource against which the statements are to be run | ||
* @param resourceName An SQL DDL statement. Cannot contain prepared statements. Do not add DML statements here! | ||
*/ | ||
default void addStatementFromResource(String datasourceName, String resourceName) { | ||
addStatementFromResource(datasourceName, resourceName, getClass().getClassLoader()); | ||
} | ||
|
||
/** | ||
* Extensions that operate a store based on an SQL database and thus require a certain database structure to be present, | ||
* can use this class to have their schema auto-generated. The entire DDL has to be in a file that is available from the resources. | ||
* <p> | ||
* Note that all DDL statements <strong>must</strong> be queued during the {@link ServiceExtension#initialize(ServiceExtensionContext)} phase and | ||
* individual statements <strong>must not</strong> rely on ordering, since that depends on extension ordering. | ||
* | ||
* @param datasourceName The name of the datasource against which the statements are to be run | ||
* @param resourceName An SQL DDL statement. Cannot contain prepared statements. Do not add DML statements here! | ||
* @param classLoader A classloader which is used to resolve the resource | ||
*/ | ||
void addStatementFromResource(String datasourceName, String resourceName, ClassLoader classLoader); | ||
|
||
/** | ||
* Gets all registered DML statements as a map where the datasource name is the key, and the SQL statement(s) is the value. | ||
*/ | ||
Map<String, List<String>> getStatements(); | ||
} |
72 changes: 72 additions & 0 deletions
72
...rapper/src/main/java/org/eclipse/edc/sql/bootstrapper/SqlSchemaBootstrapperExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.sql.bootstrapper; | ||
|
||
import org.eclipse.edc.runtime.metamodel.annotation.Extension; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Inject; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Provider; | ||
import org.eclipse.edc.spi.monitor.Monitor; | ||
import org.eclipse.edc.spi.persistence.EdcPersistenceException; | ||
import org.eclipse.edc.spi.system.ServiceExtension; | ||
import org.eclipse.edc.spi.system.ServiceExtensionContext; | ||
import org.eclipse.edc.sql.QueryExecutor; | ||
import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry; | ||
import org.eclipse.edc.transaction.spi.TransactionContext; | ||
|
||
import static org.eclipse.edc.sql.bootstrapper.SqlSchemaBootstrapperExtension.NAME; | ||
|
||
@Extension(value = NAME, categories = { "sql", "persistence", "storage" }) | ||
public class SqlSchemaBootstrapperExtension implements ServiceExtension { | ||
public static final String NAME = "SQL Schema Bootstrapper Extension"; | ||
public static final String SCHEMA_AUTOCREATE_PROPERTY = "edc.sql.schema.autocreate"; | ||
public static final boolean SCHEMA_AUTOCREATE_DEFAULT = false; | ||
|
||
@Inject | ||
private TransactionContext transactionContext; | ||
@Inject | ||
private QueryExecutor queryExecutor; | ||
@Inject | ||
private DataSourceRegistry datasourceRegistry; | ||
@Inject | ||
private Monitor monitor; | ||
|
||
private SqlSchemaBootstrapperImpl bootstrapper; | ||
private Boolean shouldAutoCreate; | ||
|
||
@Override | ||
public void initialize(ServiceExtensionContext context) { | ||
shouldAutoCreate = context.getConfig().getBoolean(SCHEMA_AUTOCREATE_PROPERTY, SCHEMA_AUTOCREATE_DEFAULT); | ||
} | ||
|
||
@Override | ||
public void prepare() { | ||
if (shouldAutoCreate) { | ||
var statements = getBootstrapper().getStatements(); | ||
new SqlDmlStatementRunner(transactionContext, queryExecutor, monitor, datasourceRegistry).executeSql(statements) | ||
.orElseThrow(f -> new EdcPersistenceException("Failed to bootstrap SQL schema, error '%s'".formatted(f.getFailureDetail()))); | ||
|
||
} else { | ||
monitor.debug("Automatic SQL schema creation is disabled. To enable it, set '%s' = true".formatted(SCHEMA_AUTOCREATE_PROPERTY)); | ||
} | ||
} | ||
|
||
@Provider | ||
public SqlSchemaBootstrapper getBootstrapper() { | ||
if (bootstrapper == null) { | ||
bootstrapper = new SqlSchemaBootstrapperImpl(); | ||
} | ||
return bootstrapper; | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
...ootstrapper/src/main/java/org/eclipse/edc/sql/bootstrapper/SqlSchemaBootstrapperImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* This program and the accompanying materials are made available under the | ||
* terms of the Apache License, Version 2.0 which is available at | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
* | ||
* Contributors: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.sql.bootstrapper; | ||
|
||
import org.eclipse.edc.spi.EdcException; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Scanner; | ||
|
||
import static java.util.stream.Collectors.groupingBy; | ||
import static java.util.stream.Collectors.mapping; | ||
import static java.util.stream.Collectors.toList; | ||
|
||
public class SqlSchemaBootstrapperImpl implements SqlSchemaBootstrapper { | ||
|
||
private final List<QueuedStatementRecord> statements = new ArrayList<>(); | ||
|
||
@Override | ||
public void addStatementFromResource(String datasourceName, String resourceName, ClassLoader classLoader) { | ||
try (var sqlStream = classLoader.getResourceAsStream(resourceName); var scanner = new Scanner(Objects.requireNonNull(sqlStream)).useDelimiter("\\A")) { | ||
var sql = scanner.next(); | ||
statements.add(new QueuedStatementRecord(resourceName, datasourceName, sql)); | ||
} catch (IOException e) { | ||
throw new EdcException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public Map<String, List<String>> getStatements() { | ||
return statements.stream().collect(groupingBy(QueuedStatementRecord::datasourceName, mapping(QueuedStatementRecord::sql, toList()))); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
...strapper/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# | ||
# Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
# | ||
# This program and the accompanying materials are made available under the | ||
# terms of the Apache License, Version 2.0 which is available at | ||
# https://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
# | ||
# Contributors: | ||
# Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
# | ||
# | ||
|
||
org.eclipse.edc.sql.bootstrapper.SqlSchemaBootstrapperExtension |
Oops, something went wrong.