-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: TxLog write, improve resource creation
Signed-off-by: Ryan Roberts <ryan@blockchaintp.com>
- Loading branch information
1 parent
4651ea8
commit 9c02a87
Showing
9 changed files
with
446 additions
and
31 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
63 changes: 63 additions & 0 deletions
63
src/main/java/com/blockchaintp/daml/stores/qldb/QldbTransactionException.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,63 @@ | ||
/* | ||
* Copyright 2021 Blockchain Technology Partners | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package com.blockchaintp.daml.stores.qldb; | ||
|
||
import com.amazon.ion.IonValue; | ||
import com.blockchaintp.daml.stores.exception.StoreException; | ||
|
||
/** | ||
* | ||
*/ | ||
public class QldbTransactionException extends StoreException { | ||
/** | ||
* A transaction log exception with an originating cause. | ||
* | ||
* @param cause | ||
* the cause | ||
*/ | ||
protected QldbTransactionException(final Throwable cause) { | ||
super(cause); | ||
} | ||
|
||
/** | ||
* An exception with a message. | ||
* | ||
* @param message | ||
* Pertinent message text. | ||
*/ | ||
protected QldbTransactionException(final String message) { | ||
super(message); | ||
} | ||
|
||
/** | ||
* We have retrieved a qldb record with an unexpected schema. | ||
* | ||
* @param value | ||
* @return The constructed exception | ||
*/ | ||
public static QldbTransactionException invalidSchema(final IonValue value) { | ||
return new QldbTransactionException(String.format("Invalid result from qldb %s", value.toPrettyString())); | ||
} | ||
|
||
/** | ||
* We have not managed to fetch metadata for a document. | ||
* | ||
* @param query | ||
* The query that failed to fetch metadata | ||
* @return The constructed exception | ||
*/ | ||
public static QldbTransactionException noMetadata(final String query) { | ||
return new QldbTransactionException(String.format("Metadata query '%s' returned no results", query)); | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
src/main/java/com/blockchaintp/daml/stores/qldb/QldbTransactionLog.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,176 @@ | ||
/* | ||
* Copyright 2021 Blockchain Technology Partners | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package com.blockchaintp.daml.stores.qldb; | ||
|
||
import java.nio.ByteBuffer; | ||
import java.util.Arrays; | ||
import java.util.UUID; | ||
|
||
import com.amazon.ion.IonInt; | ||
import com.amazon.ion.IonString; | ||
import com.amazon.ion.IonStruct; | ||
import com.amazon.ion.IonSystem; | ||
import com.blockchaintp.daml.stores.exception.StoreWriteException; | ||
import com.blockchaintp.daml.stores.service.TransactionLog; | ||
import com.google.protobuf.ByteString; | ||
|
||
import io.vavr.Tuple; | ||
import software.amazon.awssdk.services.qldb.model.QldbException; | ||
import software.amazon.awssdk.services.qldbsession.model.QldbSessionException; | ||
import software.amazon.qldb.ExecutorNoReturn; | ||
import software.amazon.qldb.QldbDriver; | ||
|
||
/** | ||
* Implements a transaction log using 2 QLDB tables. | ||
* | ||
* daml_tx_log: i: UUID as a blob, indexed v: transaction value | ||
* | ||
* daml_tx_seq: s: Long sequence field, indexed d: docid of a daml_tx_log entry | ||
*/ | ||
public final class QldbTransactionLog implements TransactionLog<UUID, ByteString, Long> { | ||
private static final String ID_FIELD = "i"; | ||
private static final String SEQ_FIELD = "s"; | ||
private static final String DOCID_FIELD = "d"; | ||
private static final String DATA_FIELD = "v"; | ||
private static final int UUID_LENGTH_IN_BYTES = 16; | ||
private final RequiresTables tables; | ||
private final String txLogTable; | ||
private final String seqTable; | ||
private final QldbDriver driver; | ||
private final IonSystem ion; | ||
private QldbTxSeq seqSource; | ||
|
||
/** | ||
* Construct a new QLDB transaction log for an id, sequence and opaque blob value. | ||
* | ||
* @param tableName | ||
* A common prefix for the transaction log tables names | ||
* @param theDriver | ||
* @param ionSystem | ||
*/ | ||
public QldbTransactionLog(final String tableName, final QldbDriver theDriver, final IonSystem ionSystem) { | ||
this.driver = theDriver; | ||
this.ion = ionSystem; | ||
this.txLogTable = String.format("%s_tx_log", tableName); | ||
this.seqTable = String.format("%s_seq", tableName); | ||
this.tables = new RequiresTables(Arrays.asList(Tuple.of(txLogTable, ID_FIELD), Tuple.of(seqTable, SEQ_FIELD)), | ||
theDriver); | ||
} | ||
|
||
private static UUID asUuid(final byte[] bytes) { | ||
var bb = ByteBuffer.wrap(bytes); | ||
var firstLong = bb.getLong(); | ||
var secondLong = bb.getLong(); | ||
return new UUID(firstLong, secondLong); | ||
} | ||
|
||
private static byte[] asBytes(final UUID uuid) { | ||
var bb = ByteBuffer.wrap(new byte[UUID_LENGTH_IN_BYTES]); | ||
bb.putLong(uuid.getMostSignificantBits()); | ||
bb.putLong(uuid.getLeastSignificantBits()); | ||
return bb.array(); | ||
} | ||
|
||
@Override | ||
public UUID begin() throws StoreWriteException { | ||
var uuid = UUID.randomUUID(); | ||
try { | ||
var uuidBytes = asBytes(uuid); | ||
|
||
driver.execute(tx -> { | ||
var struct = ion.newEmptyStruct(); | ||
struct.add(ID_FIELD, ion.newBlob(uuidBytes)); | ||
tx.execute(String.format("insert into %s value ?", txLogTable), struct); | ||
}); | ||
} catch (QldbException e) { | ||
throw new StoreWriteException(e); | ||
} | ||
return uuid; | ||
} | ||
|
||
@Override | ||
public void sendEvent(final UUID id, final ByteString data) throws StoreWriteException { | ||
try { | ||
var uuidBytes = asBytes(id); | ||
|
||
driver.execute(tx -> { | ||
var struct = ion.newEmptyStruct(); | ||
struct.add(ID_FIELD, ion.newBlob(uuidBytes)); | ||
tx.execute(String.format("update %s as o set %s = ? where o.%s = ?", txLogTable, DATA_FIELD, ID_FIELD), | ||
ion.newBlob(data.toByteArray()), ion.newBlob(uuidBytes)); | ||
}); | ||
} catch (QldbException e) { | ||
throw new StoreWriteException(e); | ||
} | ||
} | ||
|
||
private void ensureSequence() throws QldbSessionException { | ||
if (seqSource != null) { | ||
return; | ||
} | ||
|
||
driver.execute(tx -> { | ||
var res = tx.execute(String.format("select max(%s) from %s", SEQ_FIELD, seqTable)); | ||
if (res.isEmpty()) { | ||
this.seqSource = new QldbTxSeq(0L); | ||
} else { | ||
var s = (IonStruct) res.iterator().next(); | ||
var i = (IonInt) s.get("_1"); | ||
if (i == null) { | ||
throw QldbSessionException.create("", QldbTransactionException.invalidSchema(s)); | ||
} | ||
|
||
this.seqSource = new QldbTxSeq(i.longValue()); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
public Long commit(final UUID txId) throws StoreWriteException { | ||
try { | ||
ensureSequence(); | ||
|
||
var uuidBytes = asBytes(txId); | ||
|
||
driver.execute((ExecutorNoReturn) tx -> { | ||
var query = String.format("select metadata.id from _ql_committed_%s as o where o.%s = ?", txLogTable, ID_FIELD); | ||
var r = tx.execute(query, ion.newBlob(uuidBytes)); | ||
|
||
if (r.isEmpty()) { | ||
throw QldbException.create("", QldbTransactionException.noMetadata(query)); | ||
} | ||
|
||
var metaData = (IonStruct) r.iterator().next(); | ||
var docid = (IonString) metaData.get("id"); | ||
|
||
if (docid == null) { | ||
throw QldbException.create("", QldbTransactionException.invalidSchema(metaData)); | ||
} | ||
|
||
var struct = ion.newEmptyStruct(); | ||
struct.add(SEQ_FIELD, ion.newInt(seqSource.peekNext())); | ||
struct.add(DOCID_FIELD, docid); | ||
|
||
tx.execute(String.format("insert into %s value ?", txLogTable, DATA_FIELD, ID_FIELD), struct); | ||
}); | ||
} catch (QldbException e) { | ||
throw new StoreWriteException(e); | ||
} | ||
return seqSource.takeNext(); | ||
} | ||
|
||
@Override | ||
public void abort(final UUID txId) { | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
src/main/java/com/blockchaintp/daml/stores/qldb/QldbTxSeq.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,60 @@ | ||
/* | ||
* Copyright 2021 Blockchain Technology Partners | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | ||
* in compliance with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software distributed under the License | ||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | ||
* or implied. See the License for the specific language governing permissions and limitations under | ||
* the License. | ||
*/ | ||
package com.blockchaintp.daml.stores.qldb; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.LongStream; | ||
|
||
import com.blockchaintp.daml.stores.service.SeqSource; | ||
|
||
/** | ||
* A long sequence, initialisable from a QLDB tx log sequence table or an explicit point. | ||
*/ | ||
public final class QldbTxSeq implements SeqSource<Long> { | ||
private long current; | ||
|
||
/** | ||
* Initialise the sequence at a particular point. | ||
* | ||
* @param start | ||
*/ | ||
public QldbTxSeq(final Long start) { | ||
this.current = start; | ||
} | ||
|
||
@Override | ||
public Long peekNext() { | ||
return current; | ||
} | ||
|
||
@Override | ||
public Long takeNext() { | ||
var then = current; | ||
current = current + 1; | ||
return then; | ||
} | ||
|
||
@Override | ||
public List<Long> peekRange(final long size) { | ||
return LongStream.range(current, current + size).boxed().collect(Collectors.toList()); | ||
} | ||
|
||
@Override | ||
public List<Long> takeRange(final long size) { | ||
var seq = peekRange(size); | ||
current += size; | ||
return seq; | ||
} | ||
} |
Oops, something went wrong.