diff --git a/core/src/main/kotlin/net/corda/core/node/services/Services.kt b/core/src/main/kotlin/net/corda/core/node/services/Services.kt index 7390983162..8c24a1b6a6 100644 --- a/core/src/main/kotlin/net/corda/core/node/services/Services.kt +++ b/core/src/main/kotlin/net/corda/core/node/services/Services.kt @@ -38,8 +38,7 @@ val DEFAULT_SESSION_ID = 0L * Active means they haven't been consumed yet (or we don't know about it). * Relevant means they contain at least one of our pubkeys. */ -class Vault(val states: Iterable>, - val transactionNotes: Map> = emptyMap()) { +class Vault(val states: Iterable>) { @Suppress("UNCHECKED_CAST") inline fun statesOfType() = states.filter { it.state.data is T } as List> diff --git a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt index 9db56aed6b..d9e9fa8ee1 100644 --- a/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt +++ b/node/src/main/kotlin/net/corda/node/services/vault/NodeVaultService.kt @@ -18,6 +18,7 @@ import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace import net.corda.node.utilities.* import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.statements.InsertStatement import rx.Observable import rx.subjects.PublishSubject @@ -45,9 +46,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT val stateRef = stateRef("transaction_id", "output_index") } + private data class TxnNote(val txnId: SecureHash, val note: String) { + override fun toString() = "$txnId: $note" + } + private object TransactionNotesTable : JDBCHashedTable("${NODE_DATABASE_PREFIX}vault_txn_notes") { - val txnId = secureHash("txnId") - val notes = text("notes") + val txnId = secureHash("txnId").index() + val note = text("note") } private val mutex = ThreadBox(content = object { @@ -60,21 +65,17 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT } } - val transactionNotes = object : AbstractJDBCHashMap, TransactionNotesTable>(TransactionNotesTable, loadOnInit = false) { - override fun keyFromRow(row: ResultRow): SecureHash { - return row[table.txnId] + val transactionNotes = object : AbstractJDBCHashSet(TransactionNotesTable) { + override fun elementFromRow(row: ResultRow): TxnNote = TxnNote(row[table.txnId], row[table.note]) + + override fun addElementToInsert(insert: InsertStatement, entry: TxnNote, finalizables: MutableList<() -> Unit>) { + insert[table.txnId] = entry.txnId + insert[table.note] = entry.note } - override fun valueFromRow(row: ResultRow): Set { - return row[table.notes].split(delimiters = ";").toSet() - } - - override fun addKeyToInsert(insert: InsertStatement, entry: Map.Entry>, finalizables: MutableList<() -> Unit>) { - insert[table.txnId] = entry.key - } - - override fun addValueToInsert(insert: InsertStatement, entry: Map.Entry>, finalizables: MutableList<() -> Unit>) { - insert[table.notes] = entry.value.joinToString(separator = ";") + // TODO: caching (2nd tier db cache) and db results filtering (max records, date, other) + fun select(txnId: SecureHash) : Iterable { + return table.select { table.txnId.eq(txnId) }.map { row -> row[table.note] }.toSet().asIterable() } } @@ -101,14 +102,14 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT } }) - override val currentVault: Vault get() = mutex.locked { Vault(allUnconsumedStates(), transactionNotes) } + override val currentVault: Vault get() = mutex.locked { Vault(allUnconsumedStates()) } override val updates: Observable get() = mutex.locked { _updatesPublisher } override fun track(): Pair> { return mutex.locked { - Pair(Vault(allUnconsumedStates(), transactionNotes), _updatesPublisher.bufferUntilSubscribed()) + Pair(Vault(allUnconsumedStates()), _updatesPublisher.bufferUntilSubscribed()) } } @@ -134,16 +135,13 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT override fun addNoteToTransaction(txnId: SecureHash, noteText: String) { mutex.locked { - val notes = transactionNotes.getOrPut(key = txnId, defaultValue = { - setOf(noteText) - }) - transactionNotes.put(txnId, notes.plus(noteText)) + transactionNotes.add(TxnNote(txnId, noteText)) } } override fun getTransactionNotes(txnId: SecureHash): Iterable { mutex.locked { - return transactionNotes.get(txnId)!!.asIterable() + return transactionNotes.select(txnId) } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt b/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt index 551aad576b..b5ca373f6e 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/DatabaseSupport.kt @@ -140,6 +140,7 @@ class StrandLocalTransactionManager(initWithDatabase: Database) : TransactionMan // Composite columns for use with below Exposed helpers. data class PartyColumns(val name: Column, val owningKey: Column) data class StateRefColumns(val txId: Column, val index: Column) +data class TxnNoteColumns(val txId: Column, val note: Column) /** * [Table] column helpers for use with Exposed, as per [varchar] etc. @@ -154,6 +155,7 @@ fun Table.localDate(name: String) = this.registerColumn(name, LocalDa fun Table.localDateTime(name: String) = this.registerColumn(name, LocalDateTimeColumnType) fun Table.instant(name: String) = this.registerColumn(name, InstantColumnType) fun Table.stateRef(txIdColumnName: String, indexColumnName: String) = StateRefColumns(this.secureHash(txIdColumnName), this.integer(indexColumnName)) +fun Table.txnNote(txIdColumnName: String, txnNoteColumnName: String) = TxnNoteColumns(this.secureHash(txIdColumnName), this.text(txnNoteColumnName)) /** * [ColumnType] for marshalling to/from database on behalf of [PublicKey]. diff --git a/node/src/test/kotlin/net/corda/node/services/NodeVaultServiceTest.kt b/node/src/test/kotlin/net/corda/node/services/NodeVaultServiceTest.kt index d322b7db87..739f5b37eb 100644 --- a/node/src/test/kotlin/net/corda/node/services/NodeVaultServiceTest.kt +++ b/node/src/test/kotlin/net/corda/node/services/NodeVaultServiceTest.kt @@ -112,7 +112,7 @@ class NodeVaultServiceTest { services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 1") services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 2") services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 3") - assertEquals(1, services.vaultService.currentVault.transactionNotes.toList().size) + val results = services.vaultService.getTransactionNotes(usefulTX.id) assertEquals(3, services.vaultService.getTransactionNotes(usefulTX.id).count()) // Issue more Money (GBP) @@ -124,7 +124,6 @@ class NodeVaultServiceTest { services.recordTransactions(listOf(anotherTX)) services.vaultService.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1") - assertEquals(2, services.vaultService.currentVault.transactionNotes.toList().size) assertEquals(1, services.vaultService.getTransactionNotes(anotherTX.id).count()) } }