Asymmetric table projection in Slick - scala

Is it possible to have slick have asymmetric projects for select * vs insert? For example, this is what I'd like to do:
class Users(tag: Tag)
extends Table[(String, String)](tag, "users") {
def username = column[String]("username", O.PrimaryKey)
def email = column[String]("email", O.NotNull)
def password = column[String]("password", O.NotNull)
def * = (username, email)
}
val users = TableQuery[Users]
users += ("username", "email", "password")
println(users.take(1).firstOption) // prints ("username, "email)
Is there any practical way of making this work? The main issue is the insert, complaining about too many parameters because it doesn't match the projection.

Untested, but how about:
def withPassword = (username, email, password)
and then use as:
users.withPassword += ("username", "email", "password")
I believe that's the general approach to overloading the * projection.

Related

How to use select statement using slick?

I wanted to query data from example table like below using slick
| id | username | password |
+-----+----------+------------+
| 1 | admin | admin#123 |
| 2 | user | user#123 |
The query is
SELECT password FROM users WHERE username = 'user';
I have read many slick examples on stack overflow but they are more complex queries
I want a simple query as above using slick .
You can just use plain SQL query for such cases:
sql"""SELECT password FROM users WHERE username = 'user'""".as[String]
This is less portable than building queries, but let you write any arbitrary query and run it without the risk of SQL injection. Though if you are using only such queries then you don't have a reason to use Slick in the first place - you could rather use Quill or Doobie (or both) or ScalikeJDBC or jOOQ.
Using slick you can write your implementation like below:
trait UsertDAO {
this: DbComponent =>
import driver.api._
case class User(id: Int, username: String, password: String)
val userTableQuery = TableQuery[UserTable]
class UserTable(tag: Tag) extends Table[User](tag, "user") {
val id = column[String]("id", O.SqlType("NUMBER"))
val username = column[String]("username", O.SqlType("VARCHAR(200)"))
val password = column[String]("password", O.SqlType("VARCHAR(200)"))
def pk: PrimaryKey = primaryKey("pk", id)
def * : ProvenShape[CjTable] =
(id, username, password) <> (User.tupled, User.unapply)
}
}
The above class defines your table structure. Later you can define your method like to run the sql query. :
def getPassword(userName: String): Future[Option[String]] = {
db.run{
userTableQuery.filter(_.username === userName).map(_.password).to[List].result.headOption
}
}
If you still face any issues, Try looking at the CRUD application i made: https://github.com/SangeetaGulia/Student-CRUD-Slick/blob/master/Student-Slick-Project.zip
First define a class that represents your SQL table
class UsersTable(tag: Tag) extends Table[User](tag, "users") {
def email: Rep[String] = column[String]("email", O.PrimaryKey)
def password: Rep[String] = column[String]("password")
def registered: Rep[Boolean] = column[Boolean]("registered")
def firstName: Rep[Option[String]] = column[Option[String]]("first_name")
def lastName: Rep[Option[String]] = column[Option[String]]("last_name")
def username: Rep[Option[String]] = column[Option[String]]("username", O.Unique)
def contact: Rep[Option[String]] = column[Option[String]]("contact")
override def * = (email, password, registered, firstName, lastName, username, contact) <> (User.tupled, User.unapply)
}
Then create a DAO for your table which has the method to select 'one' row. Like this:
class UsersDao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider)
(implicit executionContext: ExecutionContext) extends HasDatabaseConfigProvider[JdbcProfile] {
import profile.api._
private val Users = TableQuery[UsersTable]
def selectOne(email: String): Future[Option[User]] = db.run(Users.filter(_.email === email).result.headOption)
}
Now in your service class read the record like this
class AuthService #Inject()(usersDao: UsersDao) {
def someFunction(.. some_params ..) {
usersDao.selectOne(someParama.email).flatMap {
case Some(user: User) => //Do something if user exists
case None => //Do something else if user doesn't exist
}
}
}
In case you're interested in exploring a play project written in Scala, have a look at this project https://github.com/ashishtomer/fastscraping-web

Play Framework and Slick: testing database-related services

I'm trying to follow the most idiomatic way to having a few fully tested DAO services.
I've got a few case classes such as the following:
case class Person (
id :Int,
firstName :String,
lastName :String
)
case class Car (
id :Int,
brand :String,
model :String
)
Then I've got a simple DAO class like this:
class ADao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val persons = TableQuery[PersonTable]
private val cars = TableQuery[CarTable]
private val personCar = TableQuery[PersonCar]
class PersonTable(tag: Tag) extends Table[Person](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("name")
def lastName = column[String]("description")
def * = (id, firstName, lastName) <> (Person.tupled, Person.unapply)
}
class CarTable(tag: Tag) extends Table[Car](tag, "car") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def brand = column[String]("brand")
def model = column[String]("model")
def * = (id, brand, model) <> (Car.tupled, Car.unapply)
}
// relationship
class PersonCar(tag: Tag) extends Table[(Int, Int)](tag, "person_car") {
def carId = column[Int]("c_id")
def personId = column[Int]("p_id")
def * = (carId, personId)
}
// simple function that I want to test
def getAll(): Future[Seq[((Person, (Int, Int)), Car)]] = db.run(
persons
.join(personCar).on(_.id === _.personId)
.join(cars).on(_._2.carId === _.id)
.result
)
}
And my application.conf looks like:
slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://super-secrete-prod-host/my-awesome-db"
slick.dbs.default.db.user="myself"
slick.dbs.default.db.password="yolo"
Now by going through Testing with databases and trying to mimic play-slick sample project
I'm getting into so much trouble and I cannot seem to understand how to make my test use a different database (I suppose I need to add a different db on my conf file, say slick.dbs.test) but then I couldn't find out how to inject that inside the test.
Also, on the sample repo, there's some "magic" like Application.instanceCache[CatDAO] or app2dao(app).
Can anybody point me at some full fledged example of or repo that deals correctly with testing play and slick?
Thanks.
I agree it's confusing. I don't know if this is the best solution, but I ended up having a separate configuration file test.conf that specifies an in-memory database:
slick.dbs {
default {
driver = "slick.driver.H2Driver$"
db.driver = "org.h2.Driver"
db.url = "jdbc:h2:mem:play-test"
}
}
and then told sbt to use this when running the tests:
[..] javaOptions in Test ++= Seq("-Dconfig.file=conf/test.conf")

slick 2.0 error row._1 error in Scala Intellij IDEA 15.0.2

Below is my slick 2.0 program in Scala IntelliJ IDEA 15.0.2.
This program is giving compile time error in IntelliJ IDEA:
Cannot resolve symbol _1
I am attached the screenshot of the error also.
import java.sql.Timestamp
import scala.slick.driver.PostgresDriver.simple._
case class User(
id: Long,
username: String,
email: Option[String],
password: String,
created: Timestamp)
//a simple table called 'users'
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def username = column[String]("username", O.NotNull)
// an Option[] in the case class maps to a Nullable field here
def email = column[String]("email", O.Nullable)
def password = column[String]("password", O.NotNull)
// this is a hack for postgresql; if you're using another DB, comment this out
// and the corresponding field in the case class
def created = column[Timestamp]("created_at", O.NotNull, O.DBType("timestamp default now()"))
// usernames should be unique
def idx = index("users_unique_username", (username), unique = true)
//define the "shape" of a single data record
//we're saying that an object of class User (our case class) should be returned
def * = (id, username, email.?, password,created) <> (User.tupled, User.unapply)
}
val connectionUrl = "jdbc:postgresql://localhost/slick?user=slick&password=slick"
Database.forURL(connectionUrl, driver = "org.postgresql.Driver") withSession {
implicit session =>
val users = TableQuery[Users]
// SELECT * FROM users
users.list foreach { row =>
println("user with id " + row._1 + " has username " + row._2)
}
// SELECT * FROM users WHERE username='john'
users.filter(_.username === "john").list foreach { row =>
println("user whose username is 'john' has id "+row._1 )
}
}
Slick error in intellij IDEA
When I created the same program in eclipse scala-ide, the program runs fine and fetches the result.
Is this an existing issue or am I missing something on my side?
Take a look at this
class Users(tag: Tag) extends Table[User](tag, "users") {
If yor table returned Tuples it would look like
Table[(Long, String, Option[String] <...>]
And then your
println("user with id " + row._1 + " has username " + row._2)
would be valid. I think your IDE is somehow misconfigured and runs your old codes. This is how your codes might be fixed to compile and work:
// SELECT * FROM users
users.list foreach { user =>
println("user with id " + user.id + " has username " + user.name)
}
// SELECT * FROM users WHERE username='john'
users.filter(_.username === "john").list foreach { user =>
println("user whose username is 'john' has id "+ user.id )
}

Scala Slick Model with Optional Image column

I am trying to add a column to an existing model that has an optional image, I am using Scala Slick with the Play Framework. What would be an example of storing a image with a Scala Slick model?
case class Complication(complicationId:Long,
vitalSignId:Long,
complication:String,definition:String,reason:String,
treatment:String,notes:String,
weblinks:String,upperlower:String)
object ComplicationDAO extends Table[Complication]("complications") with DbConn {
def complicationId = column[Long]("complication_id", O.PrimaryKey, O.AutoInc)
def vitalSignId = column[Long]("vital_sign_id")
def complication = column[String]("complication")
def definition = column[String]("definition",O.DBType("varchar(4000)"))
def reason = column[String]("reason",O.DBType("varchar(4000)"))
def treatment = column[String]("treatment",O.DBType("varchar(4000)"))
def notes = column[String]("notes",O.DBType("varchar(4000)"))
def weblinks = column[String]("weblinks")
def upperlower = column[String]("upperlower")
def treatmentImage = ??? //here is where it needs to be defined!
def * = complicationId ~ vitalSignId ~ complication ~ definition ~ reason ~ treatment ~ notes ~ weblinks ~ upperlower<> (Complication, Complication.unapply _)
def autoInc =
vitalSignId ~ complication ~ definition ~ reason ~
treatment ~ notes ~ weblinks ~ upperlower <>
(NewComplication, NewComplication.unapply _) returning complicationId
}
This is the model that I am trying to integrate an image into.
Thanks for the advice!
If you don't want to actually store it into the DB you can use a url as String field, this example can get you started.
A table for pictures is used:
case class Picture(url: String, id: Option[Int] = None)
trait PictureComponent { this: Profile => //requires a Profile to be mixed in...
import profile.simple._ //...to be able import profile.simple._
class Pictures(tag: Tag) extends Table[Picture](tag, "PICTURES") {
def id = column[Option[Int]]("PIC_ID", O.PrimaryKey, O.AutoInc)
def url = column[String]("PIC_URL", O.NotNull)
def * = (url, id) <> (Picture.tupled, Picture.unapply)
}
val pictures = TableQuery[Pictures]
private val picturesAutoInc = pictures returning pictures.map(_.id) into { case (p, id) => p.copy(id = id) }
def insert(picture: Picture)(implicit session: Session): Picture = picturesAutoInc.insert(picture)
}
And then the user table as an external reference to the picture id:
class Users(tag: Tag) extends Table[(String, Int, Option[Int])](tag, "USERS") {
def id = column[Option[Int]]("USER_ID", O.PrimaryKey, O.AutoInc)
def name = column[String]("USER_NAME", O.NotNull)
def pictureId = column[Int]("PIC_ID", O.NotNull)
def * = (name, pictureId, id)
}
When inserting you can upload the image, store the url, retrieve the id and assign it to the User entity as external key. To make the external key column nullable you just have to use [column[Option[Int]] instead of column[Int].
Otherwise you could use a Blob field as suggested in a comment, there's a test inside the Slick library with JDBC that creates a table with a Blob field:
class T(tag: Tag) extends Table[(Int, Blob)](tag, "test3") {
def id = column[Int]("id")
def data = column[Blob]("data")
def * = (id, data)
}
val ts = TableQuery[T]
And then to insert:
ts insert (1, new SerialBlob(Array[Byte](1,2,3)))
ts insert (2, new SerialBlob(Array[Byte](4,5)))
Then you can look into java.io.serializable on how to transform a file to bites before storing it into a Blob, this link could help (ignore the sql parts).

Scala & Play! & Slick & PostgreSQL auto increment

I have the following code in Scala:
case class Product(id: Option[Long] = None, name: String, price: BigDecimal, description: String)
object Products extends Table[Product]("product") {
def id = column[Long]("id", O.AutoInc, O.PrimaryKey)
def name = column[String]("name", O.NotNull)
def price = column[BigDecimal]("price", O.NotNull)
def description = column[String]("description", O.NotNull)
def * = id.? ~ name ~ price ~ description <>(Product.apply _, Product.unapply _)
def autoInc = * returning id
def add(product: Product)(implicit s:Session): Long = {
Products.autoInc.insert(product)
}
def all(implicit s:Session): List[Product] = {
Query(Products).list
}
}
Listing all products works great, however, I can't make adding method working.
After calling:
val myProduct = models.Product(id = None, name = "test2", price = BigDecimal(2.99), description = "test3")
models.Products.add(myProduct)
I constanty get an error message from PostgreSQL saying that id cannot be null. I totally agree with that, but why the id column is not being set by autoInc? Doesn't it work that way?
I use Play! 2.1.2, Scala 2.10.0, PostgreSQL 9.3 and play-slick 0.3.3.
Thanks in advance.
Here is a suggestion, rewrite your autoInc and add methods like this:
def autoInc = name ~ price ~ description returning id
def add(product: Product)(implicit s:Session): Long = {
Products.autoInc.insert(p.name, p.price, p.description)
}
Some databases own't allow you to insert null in the auto increment column. Maybe it's the Postgres case.