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