Skip to content

Commit

Permalink
add SQL implementation for the DidResourceStore
Browse files Browse the repository at this point in the history
  • Loading branch information
paullatzelsperger committed Dec 8, 2023
1 parent e462929 commit 0309010
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 13 deletions.
2 changes: 2 additions & 0 deletions core/identity-hub-did-store-sql/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ plugins {

dependencies {
api(project(":spi:identity-hub-did-spi"))
implementation(libs.edc.core.sql) // for the SqlStatements
implementation(libs.edc.spi.transaction.datasource)

testImplementation(testFixtures(project(":spi:identity-hub-did-spi")))
testImplementation(testFixtures(libs.edc.core.sql))
Expand Down
23 changes: 23 additions & 0 deletions core/identity-hub-did-store-sql/docs/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--
-- Copyright (c) 2023 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
--

-- only intended for and tested with Postgres!
CREATE TABLE IF NOT EXISTS did_resources
(
did VARCHAR NOT NULL,
create_timestamp BIGINT NOT NULL,
state_timestamp BIGINT NOT NULL,
state INT NOT NULL,
did_document JSON NOT NULL,
PRIMARY KEY (did)
);
Original file line number Diff line number Diff line change
@@ -1,2 +1,57 @@
package org.eclipse.edc.identityhub.did.store.sql;public class BaseDidResourceStatements {
/*
* Copyright (c) 2023 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.identityhub.did.store.sql;

import static java.lang.String.format;

public class BaseSqlDialectStatements implements DidResourceStatements {
@Override
public String getInsertTemplate() {
return executeStatement()
.column(getIdColumn())
.column(getStateColumn())
.column(getCreateTimestampColumn())
.column(getStateTimestampColumn())
.jsonColumn(getDidDocumentColumn())
.insertInto(getDidResourceTableName());
}

@Override
public String getUpdateTemplate() {
return executeStatement()
.column(getIdColumn())
.column(getStateColumn())
.column(getCreateTimestampColumn())
.column(getStateTimestampColumn())
.jsonColumn(getDidDocumentColumn())
.update(getDidResourceTableName(), getIdColumn());
}

@Override
public String getDeleteByIdTemplate() {
return executeStatement().delete(getDidResourceTableName(), getIdColumn());
}

@Override
public String getFindByIdTemplate() {
return format("SELECT * FROM %s WHERE %s = ?", getDidResourceTableName(), getIdColumn());

}

@Override
public String selectAllTemplate() {
return format("SELECT * FROM %s".formatted(getDidResourceTableName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2023 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.identityhub.did.store.sql;

import org.eclipse.edc.sql.statement.SqlStatements;

/**
* The DidResourceStatements interface defines the SQL statements required to interact with the `did_resources` table.
* It extends the SqlStatements interface.
*/
public interface DidResourceStatements extends SqlStatements {
default String getDidResourceTableName() {
return "did_resources";
}

default String getIdColumn() {
return "did";
}

default String getStateColumn() {
return "state";
}

default String getStateTimestampColumn() {
return "state_timestamp";
}

default String getCreateTimestampColumn() {
return "create_timestamp";
}

default String getDidDocumentColumn() {
return "did_document";
}

String getInsertTemplate();

String getUpdateTemplate();

String getDeleteByIdTemplate();

String getFindByIdTemplate();

String selectAllTemplate();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 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.identityhub.did.store.sql;

import org.eclipse.edc.sql.dialect.PostgresDialect;

public class PostgresStatementsImpl extends BaseSqlDialectStatements {
@Override
public String getFormatAsJsonOperator() {
return PostgresDialect.getJsonCastOperator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,130 @@

package org.eclipse.edc.identityhub.did.store.sql;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.edc.iam.did.spi.document.DidDocument;
import org.eclipse.edc.identithub.did.spi.model.DidResource;
import org.eclipse.edc.identithub.did.spi.store.DidResourceStore;
import org.eclipse.edc.spi.persistence.EdcPersistenceException;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.sql.QueryExecutor;
import org.eclipse.edc.sql.store.AbstractSqlStore;
import org.eclipse.edc.transaction.datasource.spi.DataSourceRegistry;
import org.eclipse.edc.transaction.spi.TransactionContext;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Objects;


public class SqlDidResourceStore extends AbstractSqlStore implements DidResourceStore {

private final DidResourceStatements statements;

public SqlDidResourceStore(DataSourceRegistry dataSourceRegistry, String dataSourceName, TransactionContext transactionContext,
ObjectMapper objectMapper, QueryExecutor queryExecutor, DidResourceStatements statements) {
super(dataSourceRegistry, dataSourceName, transactionContext, objectMapper, queryExecutor);
this.statements = statements;
}


public class SqlDidResourceStore implements DidResourceStore {
@Override
public StoreResult<Void> save(DidResource resource) {
return null;
var did = resource.getDid();
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
if (findById(did) != null) {
return StoreResult.alreadyExists(alreadyExistsErrorMessage(did));
}

var stmt = statements.getInsertTemplate();
queryExecutor.execute(connection, stmt,
did,
resource.getState().code(),
resource.getCreateTimestamp(),
resource.getStateTimestamp(),
toJson(resource.getDocument()));
return StoreResult.success();
} catch (SQLException e) {
throw new EdcPersistenceException(e);
}
});
}

@Override
public StoreResult<Void> update(DidResource resource) {
return null;
var did = resource.getDid();
Objects.requireNonNull(resource);
Objects.requireNonNull(did);
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
if (findById(did) != null) {
queryExecutor.execute(connection, statements.getUpdateTemplate(),
did,
resource.getState().code(),
resource.getCreateTimestamp(),
resource.getStateTimestamp(),
toJson(resource.getDocument()),
did);
return StoreResult.success();
}
return StoreResult.notFound(notFoundErrorMessage(did));
} catch (SQLException e) {
throw new EdcPersistenceException(e);
}
});
}

@Override
public DidResource getById(String did) {
return null;
public DidResource findById(String did) {
Objects.requireNonNull(did);
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
var sql = statements.getFindByIdTemplate();
return queryExecutor.single(connection, false, this::mapResultSet, sql, did);
} catch (Exception exception) {
throw new EdcPersistenceException(exception);
}
});
}

@Override
public Collection<DidResource> getAll() {
return null;
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
var sql = statements.selectAllTemplate();
return queryExecutor.query(connection, true, this::mapResultSet, sql).toList();
} catch (Exception exception) {
throw new EdcPersistenceException(exception);
}
});
}

@Override
public StoreResult<Void> deleteById(String did) {
return null;
Objects.requireNonNull(did);
return transactionContext.execute(() -> {
try (var connection = getConnection()) {
if (findById(did) != null) {
var stmt = statements.getDeleteByIdTemplate();
queryExecutor.execute(connection, stmt, did);
return StoreResult.success();
}
return StoreResult.notFound(notFoundErrorMessage(did));
} catch (SQLException e) {
throw new EdcPersistenceException(e);
}
});
}

private DidResource mapResultSet(ResultSet resultSet) throws Exception {
return DidResource.Builder.newInstance()
.did(resultSet.getString(statements.getIdColumn()))
.createTimestamp(resultSet.getLong(statements.getCreateTimestampColumn()))
.stateTimeStamp(resultSet.getLong(statements.getStateTimestampColumn()))
.document(fromJson(resultSet.getString(statements.getDidDocumentColumn()), DidDocument.class))
.state(resultSet.getInt(statements.getStateColumn()))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
@Extension(value = NAME)
public class SqlDidResourceStoreExtension implements ServiceExtension {
public static final String NAME = "DID Resource SQL Store Extension";

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,38 @@
import org.eclipse.edc.identithub.did.spi.store.DidResourceStore;
import org.eclipse.edc.identityhub.did.store.test.DidResourceStoreTestBase;
import org.eclipse.edc.junit.annotations.ComponentTest;
import org.eclipse.edc.spi.types.TypeManager;
import org.eclipse.edc.sql.QueryExecutor;
import org.eclipse.edc.sql.testfixtures.PostgresqlStoreSetupExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

@ComponentTest
@ExtendWith(PostgresqlStoreSetupExtension.class)
class SqlDidResourceStoreTest extends DidResourceStoreTestBase {

private final SqlDidResourceStore store = new SqlDidResourceStore();
private final DidResourceStatements statements = new PostgresStatementsImpl();
private SqlDidResourceStore store;

@BeforeEach
void setup(PostgresqlStoreSetupExtension extension, QueryExecutor queryExecutor) throws IOException {
var typeManager = new TypeManager();
store = new SqlDidResourceStore(extension.getDataSourceRegistry(), extension.getDatasourceName(),
extension.getTransactionContext(), typeManager.getMapper(), queryExecutor, statements);

var schema = Files.readString(Paths.get("./docs/schema.sql"));
extension.runQuery(schema);
}

@AfterEach
void tearDown(PostgresqlStoreSetupExtension extension) {
extension.runQuery("DROP TABLE " + statements.getDidResourceTableName() + " CASCADE");
}

@Override
protected DidResourceStore getStore() {
Expand Down
Loading

0 comments on commit 0309010

Please sign in to comment.