Browse Source

server: Add getHighestBalances method to the BalanceDataHandler

prometheus-integration
Alexis Hernandez 6 years ago
parent
commit
f1cc9103ea
  1. 4
      server/app/com/xsn/explorer/data/BalanceDataHandler.scala
  2. 10
      server/app/com/xsn/explorer/data/anorm/BalancePostgresDataHandler.scala
  3. 46
      server/app/com/xsn/explorer/data/anorm/dao/BalancePostgresDAO.scala
  4. 5
      server/app/com/xsn/explorer/data/async/BalanceFutureDataHandler.scala
  5. 73
      server/test/com/xsn/explorer/data/BalancePostgresDataHandlerSpec.scala
  6. 1
      server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala
  7. 3
      server/test/com/xsn/explorer/helpers/BalanceDummyDataHandler.scala
  8. 14
      server/test/controllers/BalancesControllerSpec.scala

4
server/app/com/xsn/explorer/data/BalanceDataHandler.scala

@ -2,7 +2,7 @@ package com.xsn.explorer.data
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination.{PaginatedQuery, PaginatedResult}
import com.alexitc.playsonify.models.pagination.{Limit, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.models.fields.BalanceField
import com.xsn.explorer.models.{Address, Balance}
@ -17,6 +17,8 @@ trait BalanceDataHandler[F[_]] {
def getBy(address: Address): F[Balance]
def getNonZeroBalances(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): F[PaginatedResult[Balance]]
def getHighestBalances(limit: Limit, lastSeenAddress: Option[Address]): F[List[Balance]]
}
trait BalanceBlockingDataHandler extends BalanceDataHandler[ApplicationResult]

10
server/app/com/xsn/explorer/data/anorm/BalancePostgresDataHandler.scala

@ -2,7 +2,7 @@ package com.xsn.explorer.data.anorm
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination.{PaginatedQuery, PaginatedResult}
import com.alexitc.playsonify.models.pagination.{Limit, PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.BalanceBlockingDataHandler
import com.xsn.explorer.data.anorm.dao.BalancePostgresDAO
import com.xsn.explorer.errors.BalanceUnknownError
@ -53,4 +53,12 @@ class BalancePostgresDataHandler @Inject() (
Good(result)
}
override def getHighestBalances(limit: Limit, lastSeenAddress: Option[Address]): ApplicationResult[List[Balance]] = withConnection { implicit conn =>
val result = lastSeenAddress
.map { balancePostgresDAO.getHighestBalances(_, limit) }
.getOrElse { balancePostgresDAO.getHighestBalances(limit) }
Good(result)
}
}

46
server/app/com/xsn/explorer/data/anorm/dao/BalancePostgresDAO.scala

@ -4,7 +4,7 @@ import java.sql.Connection
import anorm._
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination.{Count, PaginatedQuery}
import com.alexitc.playsonify.models.pagination.{Count, Limit, PaginatedQuery}
import com.alexitc.playsonify.sql.FieldOrderingSQLInterpreter
import com.xsn.explorer.data.anorm.parsers.BalanceParsers._
import com.xsn.explorer.models.fields.BalanceField
@ -127,4 +127,48 @@ class BalancePostgresDAO @Inject() (fieldOrderingSQLInterpreter: FieldOrderingSQ
Count(result)
}
/**
* Get the highest balances (excluding hidden_addresses).
*/
def getHighestBalances(limit: Limit)(implicit conn: Connection): List[Balance] = {
SQL(
"""
|SELECT address, received, spent
|FROM balances
|WHERE address NOT IN (SELECT address FROM hidden_addresses)
|ORDER BY (received - spent) DESC
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int
).as(parseBalance.*).flatten
}
/**
* Get the highest balances excluding the balances until the given address (excluding hidden_addresses).
*
* Note, the results across calls might not be stable if the given address changes its balance drastically.
*/
def getHighestBalances(lastSeenAddress: Address, limit: Limit)(implicit conn: Connection): List[Balance] = {
SQL(
"""
|WITH CTE AS (
| SELECT (received - spent) AS lastSeenAvailable
| FROM balances
| WHERE address = {lastSeenAddress}
|)
|SELECT address, received, spent
|FROM CTE CROSS JOIN balances
|WHERE ((received - spent) < lastSeenAvailable OR
| ((received - spent) = lastSeenAvailable AND address > {lastSeenAddress})) AND
| address NOT IN (SELECT address FROM hidden_addresses)
|ORDER BY (received - spent) DESC
|LIMIT {limit}
""".stripMargin
).on(
'limit -> limit.int,
'lastSeenAddress -> lastSeenAddress.string
).as(parseBalance.*).flatten
}
}

5
server/app/com/xsn/explorer/data/async/BalanceFutureDataHandler.scala

@ -2,6 +2,7 @@ package com.xsn.explorer.data.async
import com.alexitc.playsonify.core.{FutureApplicationResult, FuturePaginatedResult}
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination
import com.alexitc.playsonify.models.pagination.PaginatedQuery
import com.xsn.explorer.data.{BalanceBlockingDataHandler, BalanceDataHandler}
import com.xsn.explorer.executors.DatabaseExecutionContext
@ -34,4 +35,8 @@ class BalanceFutureDataHandler @Inject() (
blockingDataHandler.getNonZeroBalances(query, ordering)
}
override def getHighestBalances(limit: pagination.Limit, lastSeenAddress: Option[Address]): FutureApplicationResult[List[Balance]] = Future {
blockingDataHandler.getHighestBalances(limit, lastSeenAddress)
}
}

73
server/test/com/xsn/explorer/data/BalancePostgresDataHandlerSpec.scala

@ -13,6 +13,8 @@ import org.scalactic.Good
class BalancePostgresDataHandlerSpec extends PostgresDataHandlerSpec {
import DataHelper._
lazy val dataHandler = new BalancePostgresDataHandler(database, new BalancePostgresDAO(new FieldOrderingSQLInterpreter))
val defaultOrdering = FieldOrdering(BalanceField.Available, OrderingCondition.DescendingOrder)
@ -124,6 +126,7 @@ class BalancePostgresDataHandlerSpec extends PostgresDataHandlerSpec {
.map(dataHandler.upsert)
.foreach(_.isGood mustEqual true)
}
"ignore addresses with balance = 0" in {
prepare()
val query = PaginatedQuery(Offset(0), Limit(1))
@ -135,6 +138,76 @@ class BalancePostgresDataHandlerSpec extends PostgresDataHandlerSpec {
}
}
"getHighestBalances" should {
val balances = List(
Balance(createAddress("XiHW7SR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 1000),
Balance(createAddress("XjHW7SR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 900),
Balance(createAddress("XkHW7SR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 900, spent = 100),
Balance(createAddress("XlHW7SR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 800),
Balance(createAddress("XmmmmSR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 700),
Balance(createAddress("XnHW7SR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 600),
Balance(createAddress("XxxxxSR56uPHeXKwcpeVsE4nUfkHv5RqE3"), received = 2000),
)
def prepare() = {
clearDatabase()
balances.foreach(dataHandler.upsert(_).isGood mustEqual true)
database.withConnection { implicit conn =>
_root_.anorm
.SQL(
s"""
|INSERT INTO hidden_addresses (address) VALUES
| ('${balances(4).address.string}'),
| ('${balances(6).address.string}')
|""".stripMargin)
.executeUpdate()
}
}
"return the highest balances" in {
prepare()
val expected = balances.head
val result = dataHandler.getHighestBalances(Limit(1), None).get
result mustEqual List(expected)
}
"return the next elements given the last seen address" in {
prepare()
val lastSeenAddress = balances.head.address
val expected = balances(1)
val result = dataHandler.getHighestBalances(Limit(1), Option(lastSeenAddress)).get
result mustEqual List(expected)
}
"return the element with the same time breaking ties by address" in {
prepare()
val lastSeenAddress = balances(2).address
val expected = balances(3)
val result = dataHandler.getHighestBalances(Limit(1), Option(lastSeenAddress)).get
result mustEqual List(expected)
}
"return the no elements on unknown lastSeenTransaction" in {
val lastSeenAddress = createAddress("XmHW7SR56uPHeXKwcpeVsE4nUfkHv5Rq12")
val result = dataHandler.getHighestBalances(Limit(1), Option(lastSeenAddress)).get
result must be(empty)
}
"exclude hidden_addresses" in {
prepare()
val lastSeenAddress = balances(3).address
val expected = balances(5)
val result = dataHandler.getHighestBalances(Limit(1), Option(lastSeenAddress)).get
result mustEqual List(expected)
}
}
private def combine(balances: Balance*): Balance = {
balances.reduce { (a, b) =>
Balance(a.address, a.received + b.received, a.spent + b.spent)

1
server/test/com/xsn/explorer/data/common/PostgresDataHandlerSpec.scala

@ -66,6 +66,7 @@ trait PostgresDataHandlerSpec
_root_.anorm.SQL("""DELETE FROM transactions""").execute()
_root_.anorm.SQL("""DELETE FROM blocks""").execute()
_root_.anorm.SQL("""DELETE FROM balances""").execute()
_root_.anorm.SQL("""DELETE FROM hidden_addresses""").execute()
}
}

3
server/test/com/xsn/explorer/helpers/BalanceDummyDataHandler.scala

@ -2,6 +2,7 @@ package com.xsn.explorer.helpers
import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination
import com.alexitc.playsonify.models.pagination.{PaginatedQuery, PaginatedResult}
import com.xsn.explorer.data.BalanceBlockingDataHandler
import com.xsn.explorer.models.fields.BalanceField
@ -16,4 +17,6 @@ class BalanceDummyDataHandler extends BalanceBlockingDataHandler {
override def getBy(address: Address): ApplicationResult[Balance] = ???
override def getNonZeroBalances(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = ???
override def getHighestBalances(limit: pagination.Limit, lastSeenAddress: Option[Address]): ApplicationResult[List[Balance]] = ???
}

14
server/test/controllers/BalancesControllerSpec.scala

@ -4,9 +4,9 @@ import com.alexitc.playsonify.core.ApplicationResult
import com.alexitc.playsonify.models.ordering.FieldOrdering
import com.alexitc.playsonify.models.pagination._
import com.xsn.explorer.data.BalanceBlockingDataHandler
import com.xsn.explorer.helpers.DataHelper
import com.xsn.explorer.helpers.{BalanceDummyDataHandler, DataHelper}
import com.xsn.explorer.models.Balance
import com.xsn.explorer.models.fields.BalanceField
import com.xsn.explorer.models.{Address, Balance}
import controllers.common.MyAPISpec
import org.scalactic.Good
import play.api.inject.bind
@ -38,13 +38,7 @@ class BalancesControllerSpec extends MyAPISpec {
.sortBy(_.available)
.reverse
val dataHandler = new BalanceBlockingDataHandler {
override def upsert(balance: Balance): ApplicationResult[Balance] = ???
override def get(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = ???
override def getBy(address: Address): ApplicationResult[Balance] = ???
val dataHandler = new BalanceDummyDataHandler {
override def getNonZeroBalances(query: PaginatedQuery, ordering: FieldOrdering[BalanceField]): ApplicationResult[PaginatedResult[Balance]] = {
val filtered = balances.filter(_.available > 0)
@ -57,8 +51,8 @@ class BalancesControllerSpec extends MyAPISpec {
Good(result)
}
}
val application = guiceApplicationBuilder
.overrides(bind[BalanceBlockingDataHandler].to(dataHandler))
.build()

Loading…
Cancel
Save