|
@ -19,11 +19,67 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
def upsert(index: Int, transaction: Transaction)(implicit conn: Connection): Option[Transaction] = { |
|
|
def upsert(index: Int, transaction: Transaction)(implicit conn: Connection): Option[Transaction] = { |
|
|
for { |
|
|
for { |
|
|
partialTx <- upsertTransaction(index, transaction) |
|
|
partialTx <- upsertTransaction(index, transaction) |
|
|
inputs <- insertInputs(transaction.id, transaction.inputs) |
|
|
_ <- batchInsertOutputs(transaction.outputs) |
|
|
outputs <- insertOutputs(transaction.id, transaction.outputs) |
|
|
_ <- batchInsertInputs(transaction.inputs.map(transaction.id -> _)) |
|
|
_ <- spend(transaction.id, inputs) |
|
|
_ <- batchSpend(transaction.id, transaction.inputs) |
|
|
_ = insertDetails(transaction) |
|
|
_ <- batchInsertDetails(transaction) |
|
|
} yield partialTx.copy(inputs = inputs, outputs = outputs) |
|
|
} yield partialTx.copy(inputs = transaction.inputs, outputs = transaction.outputs) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
def insert(transactions: List[Transaction])(implicit conn: Connection): Option[List[Transaction]] = { |
|
|
|
|
|
for { |
|
|
|
|
|
r <- batchInsert(transactions) |
|
|
|
|
|
|
|
|
|
|
|
outputs = transactions.flatMap(_.outputs) |
|
|
|
|
|
_ <- batchInsertOutputs(outputs) |
|
|
|
|
|
|
|
|
|
|
|
inputs = transactions.flatMap { tx => tx.inputs.map(tx.id -> _) } |
|
|
|
|
|
_ <- batchInsertInputs(inputs) |
|
|
|
|
|
} yield { |
|
|
|
|
|
val extra = for { |
|
|
|
|
|
tx <- transactions |
|
|
|
|
|
_ <- batchInsertDetails(tx) |
|
|
|
|
|
_ <- batchSpend(tx.id, tx.inputs) |
|
|
|
|
|
} yield tx |
|
|
|
|
|
|
|
|
|
|
|
assert(extra.size == transactions.size, "Not all transactions were inserted properly") |
|
|
|
|
|
|
|
|
|
|
|
r |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private def batchInsert(transactions: List[Transaction])(implicit conn: Connection): Option[List[Transaction]] = { |
|
|
|
|
|
transactions match { |
|
|
|
|
|
case Nil => Some(transactions) |
|
|
|
|
|
case _ => |
|
|
|
|
|
val params = transactions.zipWithIndex.map { case (transaction, index) => |
|
|
|
|
|
List( |
|
|
|
|
|
'txid -> transaction.id.string: NamedParameter, |
|
|
|
|
|
'blockhash -> transaction.blockhash.string: NamedParameter, |
|
|
|
|
|
'time -> transaction.time: NamedParameter, |
|
|
|
|
|
'size -> transaction.size.int: NamedParameter, |
|
|
|
|
|
'index -> index: NamedParameter) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val batch = BatchSql( |
|
|
|
|
|
""" |
|
|
|
|
|
|INSERT INTO transactions |
|
|
|
|
|
| (txid, blockhash, time, size, index) |
|
|
|
|
|
|VALUES |
|
|
|
|
|
| ({txid}, {blockhash}, {time}, {size}, {index}) |
|
|
|
|
|
""".stripMargin, |
|
|
|
|
|
params.head, |
|
|
|
|
|
params.tail: _* |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val success = batch.execute().forall(_ == 1) |
|
|
|
|
|
|
|
|
|
|
|
if (success) { |
|
|
|
|
|
Some(transactions) |
|
|
|
|
|
} else { |
|
|
|
|
|
None |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
@ -299,18 +355,17 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
).as(parseTransaction.singleOpt).flatten |
|
|
).as(parseTransaction.singleOpt).flatten |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def insertInputs( |
|
|
private def batchInsertInputs( |
|
|
transactionId: TransactionId, |
|
|
inputs: List[(TransactionId, Transaction.Input)])( |
|
|
inputs: List[Transaction.Input])( |
|
|
implicit conn: Connection): Option[List[(TransactionId, Transaction.Input)]] = { |
|
|
implicit conn: Connection): Option[List[Transaction.Input]] = { |
|
|
|
|
|
|
|
|
|
|
|
inputs match { |
|
|
inputs match { |
|
|
case Nil => Some(inputs) |
|
|
case Nil => Some(inputs) |
|
|
|
|
|
|
|
|
case _ => |
|
|
case _ => |
|
|
val params = inputs.map { input => |
|
|
val params = inputs.map { case (txid, input) => |
|
|
List( |
|
|
List( |
|
|
'txid -> transactionId.string: NamedParameter, |
|
|
'txid -> txid.string: NamedParameter, |
|
|
'index -> input.index: NamedParameter, |
|
|
'index -> input.index: NamedParameter, |
|
|
'from_txid -> input.fromTxid.string: NamedParameter, |
|
|
'from_txid -> input.fromTxid.string: NamedParameter, |
|
|
'from_output_index -> input.fromOutputIndex: NamedParameter, |
|
|
'from_output_index -> input.fromOutputIndex: NamedParameter, |
|
@ -339,8 +394,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def insertOutputs( |
|
|
private def batchInsertOutputs( |
|
|
transactionId: TransactionId, |
|
|
|
|
|
outputs: List[Transaction.Output])( |
|
|
outputs: List[Transaction.Output])( |
|
|
implicit conn: Connection): Option[List[Transaction.Output]] = { |
|
|
implicit conn: Connection): Option[List[Transaction.Output]] = { |
|
|
|
|
|
|
|
@ -349,7 +403,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
case _ => |
|
|
case _ => |
|
|
val params = outputs.map { output => |
|
|
val params = outputs.map { output => |
|
|
List( |
|
|
List( |
|
|
'txid -> transactionId.string: NamedParameter, |
|
|
'txid -> output.txid.string: NamedParameter, |
|
|
'index -> output.index: NamedParameter, |
|
|
'index -> output.index: NamedParameter, |
|
|
'value -> output.value: NamedParameter, |
|
|
'value -> output.value: NamedParameter, |
|
|
'address -> output.address.string: NamedParameter, |
|
|
'address -> output.address.string: NamedParameter, |
|
@ -405,7 +459,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
result.flatten |
|
|
result.flatten |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def insertDetails(transaction: Transaction)(implicit conn: Connection): List[AddressTransactionDetails] = { |
|
|
private def batchInsertDetails(transaction: Transaction)(implicit conn: Connection): Option[Unit] = { |
|
|
val received = transaction |
|
|
val received = transaction |
|
|
.outputs |
|
|
.outputs |
|
|
.groupBy(_.address) |
|
|
.groupBy(_.address) |
|
@ -418,7 +472,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
.mapValues { inputs => inputs.map(_.value).sum } |
|
|
.mapValues { inputs => inputs.map(_.value).sum } |
|
|
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value) } |
|
|
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value) } |
|
|
|
|
|
|
|
|
val result = (received ++ sent) |
|
|
val details = (received ++ sent) |
|
|
.groupBy(_.address) |
|
|
.groupBy(_.address) |
|
|
.mapValues { |
|
|
.mapValues { |
|
|
case head :: list => list.foldLeft(head) { (acc, current) => |
|
|
case head :: list => list.foldLeft(head) { (acc, current) => |
|
@ -426,27 +480,42 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
.values |
|
|
.values |
|
|
.map(d => insertDetails(d)) |
|
|
|
|
|
|
|
|
|
|
|
result.toList |
|
|
batchInsertDetails(details.toList) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def insertDetails(details: AddressTransactionDetails)(implicit conn: Connection): AddressTransactionDetails = { |
|
|
private def batchInsertDetails(details: List[AddressTransactionDetails])(implicit conn: Connection): Option[Unit] = { |
|
|
SQL( |
|
|
details match { |
|
|
""" |
|
|
case Nil => Some(()) |
|
|
|INSERT INTO address_transaction_details |
|
|
case _ => |
|
|
| (address, txid, received, sent, time) |
|
|
val params = details.map { d => |
|
|
|VALUES |
|
|
List( |
|
|
| ({address}, {txid}, {received}, {sent}, {time}) |
|
|
'address -> d.address.string: NamedParameter, |
|
|
|RETURNING address, txid, received, sent, time |
|
|
'txid -> d.txid.string: NamedParameter, |
|
|
""".stripMargin |
|
|
'received -> d.received: NamedParameter, |
|
|
).on( |
|
|
'sent -> d.sent: NamedParameter, |
|
|
'address -> details.address.string, |
|
|
'time -> d.time: NamedParameter) |
|
|
'txid -> details.txid.string, |
|
|
} |
|
|
'received -> details.received, |
|
|
|
|
|
'sent -> details.sent, |
|
|
val batch = BatchSql( |
|
|
'time -> details.time |
|
|
""" |
|
|
).as(parseAddressTransactionDetails.single) |
|
|
|INSERT INTO address_transaction_details |
|
|
|
|
|
| (address, txid, received, sent, time) |
|
|
|
|
|
|VALUES |
|
|
|
|
|
| ({address}, {txid}, {received}, {sent}, {time}) |
|
|
|
|
|
""".stripMargin, |
|
|
|
|
|
params.head, |
|
|
|
|
|
params.tail: _* |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
val success = batch.execute().forall(_ == 1) |
|
|
|
|
|
|
|
|
|
|
|
if (success) { |
|
|
|
|
|
Some(()) |
|
|
|
|
|
} else { |
|
|
|
|
|
None |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def deleteDetails(txid: TransactionId)(implicit conn: Connection): List[AddressTransactionDetails] = { |
|
|
private def deleteDetails(txid: TransactionId)(implicit conn: Connection): List[AddressTransactionDetails] = { |
|
@ -491,7 +560,7 @@ class TransactionPostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderi |
|
|
).as(parseTransactionOutput.*).flatten |
|
|
).as(parseTransactionOutput.*).flatten |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private def spend(txid: TransactionId, inputs: List[Transaction.Input])(implicit conn: Connection): Option[Unit] = { |
|
|
private def batchSpend(txid: TransactionId, inputs: List[Transaction.Input])(implicit conn: Connection): Option[Unit] = { |
|
|
inputs match { |
|
|
inputs match { |
|
|
case Nil => Option(()) |
|
|
case Nil => Option(()) |
|
|
case _ => |
|
|
case _ => |
|
|