Alexis Hernandez
6 years ago
9 changed files with 331 additions and 304 deletions
@ -0,0 +1,83 @@ |
|||||
|
package com.xsn.explorer.data.anorm.dao |
||||
|
|
||||
|
import java.sql.Connection |
||||
|
|
||||
|
import anorm._ |
||||
|
import com.xsn.explorer.data.anorm.parsers.TransactionParsers._ |
||||
|
import com.xsn.explorer.models.{AddressTransactionDetails, Transaction, TransactionId} |
||||
|
|
||||
|
class AddressTransactionDetailsPostgresDAO { |
||||
|
|
||||
|
def batchInsertDetails(transaction: Transaction)(implicit conn: Connection): Option[Unit] = { |
||||
|
val received = transaction |
||||
|
.outputs |
||||
|
.groupBy(_.address) |
||||
|
.mapValues { outputs => outputs.map(_.value).sum } |
||||
|
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, received = value) } |
||||
|
|
||||
|
val sent = transaction |
||||
|
.inputs |
||||
|
.groupBy(_.address) |
||||
|
.mapValues { inputs => inputs.map(_.value).sum } |
||||
|
.map { case (address, value) => AddressTransactionDetails(address, transaction.id, time = transaction.time, sent = value) } |
||||
|
|
||||
|
val details = (received ++ sent) |
||||
|
.groupBy(_.address) |
||||
|
.mapValues { |
||||
|
case head :: list => list.foldLeft(head) { (acc, current) => |
||||
|
current.copy(received = current.received + acc.received, sent = current.sent + acc.sent) |
||||
|
} |
||||
|
} |
||||
|
.values |
||||
|
|
||||
|
batchInsertDetails(details.toList) |
||||
|
} |
||||
|
|
||||
|
def batchInsertDetails(details: List[AddressTransactionDetails])(implicit conn: Connection): Option[Unit] = { |
||||
|
details match { |
||||
|
case Nil => Some(()) |
||||
|
case _ => |
||||
|
val params = details.map { d => |
||||
|
List( |
||||
|
'address -> d.address.string: NamedParameter, |
||||
|
'txid -> d.txid.string: NamedParameter, |
||||
|
'received -> d.received: NamedParameter, |
||||
|
'sent -> d.sent: NamedParameter, |
||||
|
'time -> d.time: NamedParameter) |
||||
|
} |
||||
|
|
||||
|
val batch = BatchSql( |
||||
|
""" |
||||
|
|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 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def deleteDetails(txid: TransactionId)(implicit conn: Connection): List[AddressTransactionDetails] = { |
||||
|
val result = SQL( |
||||
|
""" |
||||
|
|DELETE FROM address_transaction_details |
||||
|
|WHERE txid = {txid} |
||||
|
|RETURNING address, txid, received, sent, time |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'txid -> txid.string |
||||
|
).as(parseAddressTransactionDetails.*) |
||||
|
|
||||
|
result |
||||
|
} |
||||
|
} |
@ -0,0 +1,75 @@ |
|||||
|
package com.xsn.explorer.data.anorm.dao |
||||
|
|
||||
|
import java.sql.Connection |
||||
|
|
||||
|
import anorm._ |
||||
|
import com.xsn.explorer.data.anorm.parsers.TransactionParsers._ |
||||
|
import com.xsn.explorer.models.{Address, Transaction, TransactionId} |
||||
|
|
||||
|
class TransactionInputPostgresDAO { |
||||
|
|
||||
|
def batchInsertInputs( |
||||
|
inputs: List[(TransactionId, Transaction.Input)])( |
||||
|
implicit conn: Connection): Option[List[(TransactionId, Transaction.Input)]] = { |
||||
|
|
||||
|
inputs match { |
||||
|
case Nil => Some(inputs) |
||||
|
|
||||
|
case _ => |
||||
|
val params = inputs.map { case (txid, input) => |
||||
|
List( |
||||
|
'txid -> txid.string: NamedParameter, |
||||
|
'index -> input.index: NamedParameter, |
||||
|
'from_txid -> input.fromTxid.string: NamedParameter, |
||||
|
'from_output_index -> input.fromOutputIndex: NamedParameter, |
||||
|
'value -> input.value: NamedParameter, |
||||
|
'address -> input.address.string: NamedParameter) |
||||
|
} |
||||
|
|
||||
|
val batch = BatchSql( |
||||
|
""" |
||||
|
|INSERT INTO transaction_inputs |
||||
|
| (txid, index, from_txid, from_output_index, value, address) |
||||
|
|VALUES |
||||
|
| ({txid}, {index}, {from_txid}, {from_output_index}, {value}, {address}) |
||||
|
""".stripMargin, |
||||
|
params.head, |
||||
|
params.tail: _* |
||||
|
) |
||||
|
|
||||
|
val success = batch.execute().forall(_ == 1) |
||||
|
|
||||
|
if (success) { |
||||
|
Some(inputs) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def deleteInputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Input] = { |
||||
|
SQL( |
||||
|
""" |
||||
|
|DELETE FROM transaction_inputs |
||||
|
|WHERE txid = {txid} |
||||
|
|RETURNING txid, index, from_txid, from_output_index, value, address |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'txid -> txid.string |
||||
|
).as(parseTransactionInput.*).flatten |
||||
|
} |
||||
|
|
||||
|
def getInputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Input] = { |
||||
|
SQL( |
||||
|
""" |
||||
|
|SELECT txid, index, from_txid, from_output_index, value, address |
||||
|
|FROM transaction_inputs |
||||
|
|WHERE txid = {txid} AND |
||||
|
| address = {address} |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'txid -> txid.string, |
||||
|
'address -> address.string |
||||
|
).as(parseTransactionInput.*).flatten |
||||
|
} |
||||
|
} |
@ -0,0 +1,128 @@ |
|||||
|
package com.xsn.explorer.data.anorm.dao |
||||
|
|
||||
|
import java.sql.Connection |
||||
|
|
||||
|
import anorm._ |
||||
|
import com.xsn.explorer.data.anorm.parsers.TransactionParsers._ |
||||
|
import com.xsn.explorer.models.{Address, Transaction, TransactionId} |
||||
|
|
||||
|
class TransactionOutputPostgresDAO { |
||||
|
|
||||
|
def getUnspentOutputs(address: Address)(implicit conn: Connection): List[Transaction.Output] = { |
||||
|
SQL( |
||||
|
""" |
||||
|
|SELECT txid, index, value, address, hex_script, tpos_owner_address, tpos_merchant_address |
||||
|
|FROM transaction_outputs |
||||
|
|WHERE address = {address} AND |
||||
|
| spent_on IS NULL AND |
||||
|
| value > 0 |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'address -> address.string |
||||
|
).as(parseTransactionOutput.*).flatten |
||||
|
} |
||||
|
|
||||
|
def batchInsertOutputs( |
||||
|
outputs: List[Transaction.Output])( |
||||
|
implicit conn: Connection): Option[List[Transaction.Output]] = { |
||||
|
|
||||
|
outputs match { |
||||
|
case Nil => Some(outputs) |
||||
|
case _ => |
||||
|
val params = outputs.map { output => |
||||
|
List( |
||||
|
'txid -> output.txid.string: NamedParameter, |
||||
|
'index -> output.index: NamedParameter, |
||||
|
'value -> output.value: NamedParameter, |
||||
|
'address -> output.address.string: NamedParameter, |
||||
|
'hex_script -> output.script.string: NamedParameter, |
||||
|
'tpos_owner_address -> output.tposOwnerAddress.map(_.string): NamedParameter, |
||||
|
'tpos_merchant_address -> output.tposMerchantAddress.map(_.string): NamedParameter) |
||||
|
} |
||||
|
|
||||
|
val batch = BatchSql( |
||||
|
""" |
||||
|
|INSERT INTO transaction_outputs |
||||
|
| (txid, index, value, address, hex_script, tpos_owner_address, tpos_merchant_address) |
||||
|
|VALUES |
||||
|
| ({txid}, {index}, {value}, {address}, {hex_script}, {tpos_owner_address}, {tpos_merchant_address}) |
||||
|
""".stripMargin, |
||||
|
params.head, |
||||
|
params.tail: _* |
||||
|
) |
||||
|
|
||||
|
val success = batch.execute().forall(_ == 1) |
||||
|
|
||||
|
if (success) { |
||||
|
Some(outputs) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
def deleteOutputs(txid: TransactionId)(implicit conn: Connection): List[Transaction.Output] = { |
||||
|
val result = SQL( |
||||
|
""" |
||||
|
|DELETE FROM transaction_outputs |
||||
|
|WHERE txid = {txid} |
||||
|
|RETURNING txid, index, hex_script, value, address, tpos_owner_address, tpos_merchant_address |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'txid -> txid.string |
||||
|
).as(parseTransactionOutput.*) |
||||
|
|
||||
|
result.flatten |
||||
|
} |
||||
|
|
||||
|
def getOutputs(txid: TransactionId, address: Address)(implicit conn: Connection): List[Transaction.Output] = { |
||||
|
SQL( |
||||
|
""" |
||||
|
|SELECT txid, index, hex_script, value, address, tpos_owner_address, tpos_merchant_address |
||||
|
|FROM transaction_outputs |
||||
|
|WHERE txid = {txid} AND |
||||
|
| address = {address} |
||||
|
""".stripMargin |
||||
|
).on( |
||||
|
'txid -> txid.string, |
||||
|
'address -> address.string |
||||
|
).as(parseTransactionOutput.*).flatten |
||||
|
} |
||||
|
|
||||
|
def batchSpend(txid: TransactionId, inputs: List[Transaction.Input])(implicit conn: Connection): Option[Unit] = { |
||||
|
inputs match { |
||||
|
case Nil => Option(()) |
||||
|
case _ => |
||||
|
val txidArray = inputs |
||||
|
.map { input => s"'${input.fromTxid.string}'" } |
||||
|
.mkString("[", ",", "]") |
||||
|
|
||||
|
val indexArray = inputs.map(_.fromOutputIndex).mkString("[", ",", "]") |
||||
|
|
||||
|
// Note: the TransactionId must meet a safe format, this approach speeds up the inserts |
||||
|
val result = SQL( |
||||
|
s""" |
||||
|
|UPDATE transaction_outputs t |
||||
|
|SET spent_on = tmp.spent_on |
||||
|
|FROM ( |
||||
|
| WITH CTE AS ( |
||||
|
| SELECT '${txid.string}' AS spent_on |
||||
|
| ) |
||||
|
| SELECT spent_on, txid, index |
||||
|
| FROM CTE CROSS JOIN (SELECT |
||||
|
| UNNEST(array$indexArray) AS index, |
||||
|
| UNNEST(array$txidArray) AS txid) x |
||||
|
|) AS tmp |
||||
|
|WHERE t.txid = tmp.txid AND |
||||
|
| t.index = tmp.index |
||||
|
""".stripMargin |
||||
|
).executeUpdate() |
||||
|
|
||||
|
if (result == inputs.size) { |
||||
|
Option(()) |
||||
|
} else { |
||||
|
None |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue