Slick sorting joda date - scala

I am using slick-joda-mapper 2.2, slick 3.1 and play 2.5
It maps Joda date without any problems.
import com.github.tototoshi.slick.PostgresJodaSupport._
trait QuoteMapping {
self: HasDatabaseConfigProvider[JdbcProfile] =>
import driver.api._
val quotes = TableQuery[Quotes]
class Quotes(tag: Tag) extends Table[Quote](tag, "QUOTE") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def startDate = column[LocalDate]("START_DATE") // All OK
def endDate = column[LocalDate]("END_DATE") // All OK
def pricePerDay = column[Double]("PRICE_PER_DAY")
def totalPrice = column[Double]("TOTAL_PRICE")
def * = (id.?, startDate, endDate, pricePerDay, totalPrice) <>(Quote.tupled, Quote.unapply)
}
}
Is it possible to sort joda date in slick?
/** Return a page of Quote */
def list(page: Int = 0, pageSize: Int = 10, orderBy: Int = 1, filter: String = "%"): Future[Page[(Quote)]] = {
val offset = pageSize * page
var query =
quotes.filter(_.id.asColumnOf[String] like filter.toLowerCase)
.drop(offset)
.take(pageSize)
orderBy match {
case 1 => query = query.sortBy(_.id.asc)
case 2 => query = query.sortBy(_.startDate.asc) // no .asc method
case 3 => query = query.sortBy(_.endDate.asc) // no .asc method
case 4 => query = query.sortBy(_.pricePerDay.asc)
case 5 => query = query.sortBy(_.totalPrice.asc)
case -1 => query = query.sortBy(_.id.desc)
case -2 => query = query.sortBy(_.startDate.desc) // no .desc method
case -3 => query = query.sortBy(_.endDate.desc) // no .desc method
case -4 => query = query.sortBy(_.pricePerDay.desc)
case -5 => query = query.sortBy(_.totalPrice.desc)
}
for {
totalRows <- count(filter)
list = query.result
result <- db.run(list)
} yield Page(result, page, offset, totalRows, pageSize)
}

Since the corresponding issue is still open, I think you can't.

I just had bad import instead:
org.joda.time.LocalDate
it was:
java.time.LocalDate
All works as expected, I hope it helps someone

Related

Change value inside Future(success(Some(List(BN)))

I have a class as
case class BN[A] (val BNId: Long, score: Double, child: A)
and a database as BnDAO
class BnDAO(...) {
def readAsync(info : scala.Long)(implicit ec : scala.concurrent.ExecutionContext) : scala.concurrent.Future[scala.Option[scala.List[BN]]] = { /* compiled code */ }
}
During my function of calling BnDAO to retrieve BN for set of ids, I want to change the result BN's score according to a Map[InfoId: Long, score: Double]
Here is my current code
val infoIds = scoreMap.keys.toSet
val futureBN = infoIds.map {
id =>
val curBN = interestsDAO.readAsync(id)
//curBN is now "Future(Success(Some(List(BN))))"
val curScore = scoreMap.get(id)
//how to change curBN's score with curSocre?
}
Thank you!!
//Thank you for Allen's answer. Based on his answer. Is it possible to create a Set of Future[Option[List[BN]]] and a Map of Long to List[Long] (infoId -> List[BNId]) within one traverse.
I was not sure what you meant, so I created two answers. Pick the one that you want.
import scala.concurrent.duration._
val ids = scoreMap.keys.toVector
val futureBN = ids.map { id =>
val curBNFut = interestsDAO.readAsync(id)
val v = curBNFut.map(opt => opt.map(l =>
id -> l.map(e =>
scoreMap.get(e.id).map(escore => e.copy(score = escore)).getOrElse(e)
)
)
}
val mapResultFut = Future.sequence(futureBN).map(m =>
m.flatMap(identity).toMap
)
val mapResult = Await.result(mapResultFut, 5.seconds)
val setFut = futureBN.map(fut => fut.map{opt => opt.map{case (id, l) => l }}).toSet
or
import scala.concurrent.duration._
val ids = scoreMap.keys.toVector
val futureBN = ids.map { id =>
val curBNFut = interestsDAO.readAsync(id)
val v = curBNFut.map(opt => opt.map(l =>
id -> l.map(e =>
scoreMap.get(id).map(escore => e.copy(score = escore)).getOrElse(e)
)
)
}
val mapResultFut = Future.sequence(futureBN).map(m =>
m.flatMap(identity).toMap
)
val mapResult = Await.result(mapResultFut, 5.seconds)
val setFut = futureBN.map(fut => fut.map{opt => opt.map{case (id, l) => l }}).toSet
The difference is in the input to scoreMap.get
I am not sure which one you want.
Post a comment if there is anything wrong. Make sure to test my answer, so that it is what you want.

Access exclusive members of an object when it's passed into a method as an instance/type of it's superclass

I am new to scala. The following 4 classes relevant to my question are present in a test automation framework designed in Scala. Is it possible to access members(day, month, year) of an object (LoginPage) if it is being passed into a method as an instance of it's superclass (PageObject) and the superclass does not contain those members
trait PageObject {
val rootUrl = "http://localhost:9999/basic-page"
val back = "backLink"
val submit = "submit"
val url: String
val heading: String
}
object LoginPage extends PageObject {
val url =s"$rootUrl/login-page"
val heading = "Heading heading"
val day = "login_page_day"
val month = "login_page__month"
val year = "login_page__year"
}
import LoginPage
object Main {
def main(args: Array[String]) {
val date = "15 August 2017"
enterDateOnPage(date, LoginPage)
}
}
<current way>
class DateMethods {
def enterDateOnPage(date: String, page: PageObject) {
val arr = date.split(" ")
val cal = Calendar.getInstance()
cal.setTime(new SimpleDateFormat("MMMM").parse(arr(1)))
numberField(LoginPage.day).value = arr(0)
numberField(LoginPage.month).value = (cal.get(Calendar.MONTH) + 1).toString
numberField(LoginPage.year).value = arr(2)
click on name(LoginPage.submit)
}
}
<preferred way>
class DateMethods {
def enterDateOnPage(date: String, page: PageObject) {
val arr = date.split(" ")
val cal = Calendar.getInstance()
cal.setTime(new SimpleDateFormat("MMMM").parse(arr(1)))
numberField(page.day).value = arr(0)
numberField(page.month).value = (cal.get(Calendar.MONTH) + 1).toString
numberField(page.year).value = arr(2)
click on name(page.submit)
}
}
Can the 'LoginPage.day' and the likes be replaced by 'page.day' after confirming that the pageMatch is actually returning a LoginPage?
I did try something like this based on Samuel Heaney's answer, but it won't compile
def enterDateOnPage(date: String, page: PageObject) {
val npage = getPage(page)
val arr = date.split(" ")
val cal = Calendar.getInstance()
cal.setTime(new SimpleDateFormat("MMMM").parse(arr(1)))
numberField(npage.day).value = arr(0)
numberField(npage.month).value = (cal.get(Calendar.MONTH) + 1).toString
numberField(npage.year).value = arr(2)
click on name(npage.submit)
}
def getPage(page:PageObject)={
page match{
case _:LoginPage.type => LoginPage
case _:EntryFormPage.type => EntryFormPage
}
Only works if i add day, month and year as members to PageObject trait, but that is not a feasible option for me
You could use pattern matching to test if its a LoginPage.
class DateMethods {
def enterDateOnPage(date: String, page: PageObject): Unit = {
page match {
case LoginPage =>
setUIFields(
LoginPage.day,
LoginPage.month,
LoginPage.year,
date
)
case LogoutPage =>
setUIFields(...)
case EntryFormPage =>
setUIFields(...)
}
click on name(page.submit)
}
def setUIFields(
day: String,
month: String,
year: String,
date: String
): Unit = {
val arr = date.split(" ")
val cal = Calendar.getInstance()
cal.setTime(new SimpleDateFormat("MMMM").parse(arr(1)))
numberField(day).value = arr(0)
numberField(month).value = (cal.get(Calendar.MONTH) + 1).toString
numberField(year).value = arr(2)
}
}
Pull your date logic into its own trait and match on that.
trait Dates {
val day: String
val month: String
val year: String
}
case object LoginPage extends PageObject with Dates {
val day = ""
val month = ""
val year = ""
// additional code
}
And now your function can match on the trait specifically
def enterDateOnPage(date: String, page: PageObject) {
page match {
case datePage: Dates => {
// add log to validate date fields
}
case _ => // no-op
}
// additional code
}

scala slick or and query

my question may sound very banal but I still didn't resolve it.
I have the Products Table implemented like
class ProductsTable(tag: Tag) extends Table[Product](tag, "PRODUCTS") {
def id = column[Int]("PRODUCT_ID", O.PrimaryKey, O.AutoInc)
def title = column[String]("NAME")
def description = column[String]("DESCRIPTION")
def style = column[String]("STYLE")
def price = column[Int]("PRICE")
def category_id = column[Int]("CATEGORY_ID")
def size_id = column[Int]("SIZE_ID")
def brand_id = column[Int]("BRAND_ID")
def * = (id.?, title, description, style, price, category_id, size_id, brand_id) <>(Product.tupled, Product.unapply _)
}
and its representation in
val Products = TableQuery[ProductsTable]
How can I implement query equivalent to SQl query:
select * from products where( category_id = 1 or category_id = 2 or category_id = 3 ) and (price between min and max)
Try something like this:
val query = Products filter { p => (p.category_id inSet List(1,2,3)) && p.price > min && p.price < max }
val result = db.run(query.result)
You can use println(query.result.statements) to see what query looks like.
EDIT:
Answer for additional question. You can make a function for your query that accepts optional min and max values:
def getProductsQuery(maybeMin: Option[Int] = None, maybeMax: Option[Int] = None) = {
val initialQuery = val query = Products filter { p => (p.category_id inSet List(1,2,3)) }
val queryWithMin = maybeMin match {
case Some(min) => initialQuery filter { _.price > min }
case None => initialQuery
}
val queryWithMax = maybeMax match {
case Some(max) => queryWithMin filter { _.price < max }
case None => queryWithMin
}
queryWithMax
}
And then you could do any of these:
val q1 = getProductsQuery() // without min or max
val q2 = getProductsQuery(maybeMin = Option(3)) // only min
val q3 = getProductsQuery(maybeMax = Option(10)) // only max
val q4 = getProductsQuery(maybeMin = Option(3), maybeMax = Option(10)) // both
and run any of these as needed...

Play framework - Using anorm with Option[LocalDate] \ Option[LocalDateTime]

I am trying to define a nullable date field in postgres, while using anorm as connection to the database.
I am trying to update an entry:
def update(id: Long, startTime: Option[LocalDate]){
SQL("""UPDATE my_table
|SET start_date = {start_date}
|WHERE id = {id}
""".stripMargin)
.on(
'id ->id,
'start_date -> startDate,
).executeUpdate()
}
But I get a compilation error, looks like anorm can't handle Option[DateTime], although when I configured a parser it works form me:
val parser: RowParser[Info] = {
get[Long]("id") ~
get[Option[DateTime]]("start_date") map {
case id ~ startTime => Info(id, startDate)
}
}
What am I missing here?
Thanks!
I added my own implicit definitions:
implicit def rowToLocalDate: Column[LocalDate] = Column.nonNull {(value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case ts: java.sql.Timestamp => Right(new LocalDate(ts.getTime))
case d: java.sql.Date => Right(new LocalDate(d.getTime))
case str: java.lang.String => Right(fmt.parseLocalDate(str))
case _ => Left(TypeDoesNotMatch("Cannot convert " + value + ":" + value.asInstanceOf[AnyRef].getClass) )
}
}
implicit val localDateToStatement = new ToStatement[LocalDate] {
def set(s: java.sql.PreparedStatement, index: Int, aValue: LocalDate): Unit = {
s.setTimestamp(index, new java.sql.Timestamp(aValue.toDateTimeAtStartOfDay().getMillis()))
}
}
And the relevant ParameterMetaData
implicit object LocalDateClassMetaData extends ParameterMetaData[LocalDate] {
val sqlType = ParameterMetaData.DateParameterMetaData.sqlType
val jdbcType = ParameterMetaData.DateParameterMetaData.jdbcType
}
That made the trick
Related question, Anorm compare/search by java.time LocalDateTime what it worked for me is just update to new version (not-yet-release-one)

Insert if not exists in Slick 3.0.0

I'm trying to insert if not exists, I found this post for 1.0.1, 2.0.
I found snippet using transactionally in the docs of 3.0.0
val a = (for {
ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
_ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
val f: Future[Unit] = db.run(a)
I'm struggling to write the logic from insert if not exists with this structure. I'm new to Slick and have little experience with Scala. This is my attempt to do insert if not exists outside the transaction...
val result: Future[Boolean] = db.run(products.filter(_.name==="foo").exists.result)
result.map { exists =>
if (!exists) {
products += Product(
None,
productName,
productPrice
)
}
}
But how do I put this in the transactionally block? This is the furthest I can go:
val a = (for {
exists <- products.filter(_.name==="foo").exists.result
//???
// _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally
Thanks in advance
It is possible to use a single insert ... if not exists query. This avoids multiple database round-trips and race conditions (transactions may not be enough depending on isolation level).
def insertIfNotExists(name: String) = users.forceInsertQuery {
val exists = (for (u <- users if u.name === name.bind) yield u).exists
val insert = (name.bind, None) <> (User.apply _ tupled, User.unapply)
for (u <- Query(insert) if !exists) yield u
}
Await.result(db.run(DBIO.seq(
// create the schema
users.schema.create,
users += User("Bob"),
users += User("Bob"),
insertIfNotExists("Bob"),
insertIfNotExists("Fred"),
insertIfNotExists("Fred"),
// print the users (select * from USERS)
users.result.map(println)
)), Duration.Inf)
Output:
Vector(User(Bob,Some(1)), User(Bob,Some(2)), User(Fred,Some(3)))
Generated SQL:
insert into "USERS" ("NAME","ID") select ?, null where not exists(select x2."NAME", x2."ID" from "USERS" x2 where x2."NAME" = ?)
Here's the full example on github
This is the version I came up with:
val a = (
products.filter(_.name==="foo").exists.result.flatMap { exists =>
if (!exists) {
products += Product(
None,
productName,
productPrice
)
} else {
DBIO.successful(None) // no-op
}
}
).transactionally
It's is a bit lacking though, for example it would be useful to return the inserted or existing object.
For completeness, here the table definition:
case class DBProduct(id: Int, uuid: String, name: String, price: BigDecimal)
class Products(tag: Tag) extends Table[DBProduct](tag, "product") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column
def uuid = column[String]("uuid")
def name = column[String]("name")
def price = column[BigDecimal]("price", O.SqlType("decimal(10, 4)"))
def * = (id, uuid, name, price) <> (DBProduct.tupled, DBProduct.unapply)
}
val products = TableQuery[Products]
I'm using a mapped table, the solution works also for tuples, with minor changes.
Note also that it's not necessary to define the id as optional, according to the documentation it's ignored in insert operations:
When you include an AutoInc column in an insert operation, it is silently ignored, so that the database can generate the proper value
And here the method:
def insertIfNotExists(productInput: ProductInput): Future[DBProduct] = {
val productAction = (
products.filter(_.uuid===productInput.uuid).result.headOption.flatMap {
case Some(product) =>
mylog("product was there: " + product)
DBIO.successful(product)
case None =>
mylog("inserting product")
val productId =
(products returning products.map(_.id)) += DBProduct(
0,
productInput.uuid,
productInput.name,
productInput.price
)
val product = productId.map { id => DBProduct(
id,
productInput.uuid,
productInput.name,
productInput.price
)
}
product
}
).transactionally
db.run(productAction)
}
(Thanks Matthew Pocock from Google group thread, for orienting me to this solution).
I've run into the solution that looks more complete. Section 3.1.7 More Control over Inserts of the Essential Slick book has the example.
At the end you get smth like:
val entity = UserEntity(UUID.random, "jay", "jay#localhost")
val exists =
users
.filter(
u =>
u.name === entity.name.bind
&& u.email === entity.email.bind
)
.exists
val selectExpression = Query(
(
entity.id.bind,
entity.name.bind,
entity.email.bind
)
).filterNot(_ => exists)
val action = usersDecisions
.map(u => (u.id, u.name, u.email))
.forceInsertQuery(selectExpression)
exec(action)
// res17: Int = 1
exec(action)
// res18: Int = 0
according to the slick 3.0 manual insert query section (http://slick.typesafe.com/doc/3.0.0/queries.html), the inserted values can be returned with id as below:
def insertIfNotExists(productInput: ProductInput): Future[DBProduct] = {
val productAction = (
products.filter(_.uuid===productInput.uuid).result.headOption.flatMap {
case Some(product) =>
mylog("product was there: " + product)
DBIO.successful(product)
case None =>
mylog("inserting product")
(products returning products.map(_.id)
into ((prod,id) => prod.copy(id=id))) += DBProduct(
0,
productInput.uuid,
productInput.name,
productInput.price
)
}
).transactionally
db.run(productAction)
}