I have an update function in my UserDAO class that takes a few optional values:
def update(id: Int, name: Option[String], password: Option[String], age: Option[Int])
I know how to update all of the values:
val query = for {
u <- users if u.id === id
} yield (u.name, u.password, u.age)
db.run(query.update(name.get, password.get, age.get))
But want to do it conditionally update the different columns, depending on if their Option value is defined. Something like this perhaps:
val query = for {
u <- users if u.id === id
} yield (u.name if name.isDefined, u.password if password.isDefined, u.age if age.isDefined) // Pseudo code
db.run(query(update(...)) // Unpack arguments here
For slick 3, you can try like this,
val query = for {
u <- db.run(users.filter(_.id === id).result)
u1 = if(u.nonEmpty && u.head.name.isDefined){
u.head.copy(name = u.head.name) //add more if needed
}
else
{
u
}
res <- db.run(users.update(u1))
} yield res
for slick 2 no need of for-yield
val u = db.run(users.filter(_.id === id).result)
val u1 = if(u.nonEmpty && u.head.name.isDefined){
u.head.copy(name = u.head.name) //add more if needed
}
else
{
u
}
val res = db.run(users.update(u1))
Hope my answer was helpful.
Now sure how to do this correctly, I'm trying to do this:
def byId(id: Column[Int], locationId: Column[Int]) = {
for {
m <- users if m.id === id && m.locationId == locationId
} yield m
}
val byIdCompiled = Compiled(byId _) // ???????????? how to pass second parameter?
def getById(id: Int, locationId: Int): Option[User] {
byIdCompiled(id, locationId).firstOption
}
How do I curry a function with 2 parameters when compililng my slick query?
The example provided in Slick docs uses single underscore to encode muiltiple parameters.
http://slick.typesafe.com/doc/2.0.0/queries.html
def userNameByIDRange(min: Column[Int], max: Column[Int]) =
for {
u <- users if u.id >= min && u.id < max
} yield u.first
val userNameByIDRangeCompiled = Compiled(userNameByIDRange _)
// The query will be compiled only once:
val names1 = userNameByIDRangeCompiled(2, 5).run
val names2 = userNameByIDRangeCompiled(1, 3).run
how can do joins on multiple fields like in example beneath?
val ownerId = 1
val contactType = 1
...
val contact = for {
(t, c) <- ContactTypes leftJoin Contacts on (_.id === _.typeId && _.ownerId === ownerId)
if t.id === contactType
} yield (c.?, t)
How can I achieve this with Slick 2.0.1? Idelly I need slick to generate this kind of query
SELECT
x2."contact_id",
x2."type_id",
x2."owner_id",
x2."value",
x2."created_on",
x2."updated_on",
x3."id",
x3."type",
x3."model"
FROM
(
SELECT
x4."id" AS "id",
x4."type" AS "type",
x4."model" AS "model"
FROM
"contact_types" x4
)x3
LEFT OUTER JOIN(
SELECT
x5."created_on" AS "created_on",
x5."value" AS "value",
x5."contact_id" AS "contact_id",
x5."updated_on" AS "updated_on",
x5."type_id" AS "type_id",
x5."owner_id" AS "owner_id"
FROM
"contacts" x5
)x2 ON x3."id" = x2."type_id" AND x2.owner_id = 1
WHERE
(x3."id" = 3)
Please note ON x3."id" = x2."type_id" AND x2.owner_id = 16
Ok, so after digging through websites and source code I think I finally found the solution
leftJoin on() method accepts following parameter pred: (E1, E2) => T, so we simply can do like this
val contacts = for {
(t, c) <- ContactTypes leftJoin Contacts on ( (type, contact) => {
type.id === contact.typeId && contact.ownerId === ownerId
} )
} yield (c.?, t)
Which generated sql query as needed.
Note: started F# 4 days back so consider before giving a negative vote please. Or at least let me know what i am doing wrong to receive all the negative Votes.
Inteligence.fs
module Inteligence
open TTTAiFSharpAlphaBeta
open Boards
type Node(board: Board, moveBox :int, point:int)=
let mutable _moveBox:int=moveBox
let mutable _point:int=point
let mutable _board=new Board(board)
let mutable _parent= board
new() = Node(new Board(),0,0)
new(board: Board)= new Node(new Board(board),0,0)
member this.MoveBox with get() = _moveBox and set(value) = _moveBox <- value
member this.Point with get() = _point and set(value) = _point <- value
member this.Board with get() = _board and set(value) = _board <- value
member this.Parent with get() = _parent and set(value) = _parent <- value
member this.Copy(node: Node)=
_board<- new Board(node.Board)
_moveBox<- node.MoveBox
_point<- node.Point
_parent<- node.Parent
type Inteligence(board:Board,symbol:int)=
let mutable _nodeCount=0;
let mutable _rootNode= new Node(board)
let mutable _level = 0
let mutable _symbol = symbol
let mutable _reff = new Boards.Reffery()
member this.NodeCount with get() = _nodeCount and set(value) = _nodeCount <- value
member this.RootNode with get() = _rootNode and set(value) = _rootNode <- value
member this.Level with get() = _level and set(value) = _level <- value
member this.Symbol with get() = _symbol and set(value) = _symbol <- value
member this.Move() :int = 0
member this.MinMaxAlphaBeta(node:Node, min:bool, alpha:int, beta:int):int=0
member this.BoardPoint(node:Node):int=0
AI.fs
module TTTAiFSharpAlphaBeta
open AIInterface
open Boards
type AI()=
let mutable cboard =new Board()
let mutable level = 0
let mutable symbol = 0
interface IAI with
member this.SetAi (_board: Board ,_level, _symbol) =
cboard <- _board
level <- _level
symbol <- _symbol
member this.GetAiName()="F#AlphaBetaAi"
member this.GetAiVersion()="0.0.1"
member this.GetLevel()= [| 3 |];
member this.AiMove()=
Inteligence.Inteligence(cboard,symbol).Move()
The problem is i can't access
Move()
from Inteligence.fs
member this.AiMove()=
Inteligence.Inteligence(cboard,symbol).Move()
in AI.fs
Getting Error The namespace or module 'Inteligence' is not defined
What am i doing wrong?
I am guessing that you are using fsi (from the #r).
If you are you need to use
#load "Intelligence.fs"
If in visual studio you need to make sure Intelligence.fs is before the other file in the project list (F# is sensitive to the order files are compiled in)
I'm trying to use Slick to query a many-to-many relationship, but I'm running into a variety of errors, the most prominent being "Don't know how to unpack (User, Skill) to T and pack to G".
The structure of the tables is similar to the following:
case class User(val name: String, val picture: Option[URL], val id: Option[UUID])
object Users extends Table[User]("users") {
def name = column[String]("name")
def picture = column[Option[URL]]("picture")
def id = column[UUID]("id")
def * = name ~ picture ~ id.? <> (User, User.unapply _)
}
case class Skill(val name: String, val id: Option[UUID])
object Skills extends Table[Skill]("skill") {
def name = column[String]("name")
def id = column[UUID]("id")
def * = name ~ id.? <> (Skill, Skill.unapply _)
}
case class UserSkill(val userId: UUID, val skillId: UUID, val id: Option[UUID])
object UserSkills extends Table[UserSkill]("user_skill") {
def userId = column[UUID]("userId")
def skillId = column[UUID]("skillId")
def id = column[UUID]("id")
def * = userId ~ skillId ~ id.? <> (UserSkill, UserSkill.unapply _)
def user = foreignKey("userFK", userId, Users)(_.id)
def skill = foreignKey("skillFK", skillId, Skills)(_.id)
}
Ultimately, what I want to achieve is something of the form
SELECT u.*, group_concat(s.name) FROM user_skill us, users u, skills s WHERE us.skillId = s.id && us.userId = u.id GROUP BY u.id
but before I spend the time trying to get group_concat to work as well, I have been trying to produce the simpler query (which I believe is still valid...)
SELECT u.* FROM user_skill us, users u, skills s WHERE us.skillId = s.id && us.userId = u.id GROUP BY u.id
I've tried a variety of scala code to produce this query, but an example of what causes the shape error above is
(for {
us <- UserSkills
user <- us.user
skill <- us.skill
} yield (user, skill)).groupBy(_._1.id).map { case(_, xs) => xs.first }
Similarly, the following produces a packing error regarding "User" instead of "(User, Skill)"
(for {
us <- UserSkills
user <- us.user
skill <- us.skill
} yield (user, skill)).groupBy(_._1.id).map { case(_, xs) => xs.map(_._1).first }
If anyone has any suggestions, I would be very grateful: I've spent most of today and yesterday scouring google/google groups as well as the slick source, but I haven't a solution yet.
(Also, I'm using postgre so group_concat would actually be string_agg)
EDIT
So it seems like when groupBy is used, the mapped projection gets applied because something like
(for {
us <- UserSkills
u <- us.user
s <- us.skill
} yield (u,s)).map(_._1)
works fine because _._1 gives the type Users, which has a Shape since Users is a table. However, when we call xs.first (as we do when we call groupBy), we actually get back a mapped projection type (User, Skill), or if we apply map(_._1) first, we get the type User, which is not Users! As far as I can tell, there is no shape with User as the mixed type because the only shapes defined are for Shape[Column[T], T, Column[T]] and for a table T <: TableNode, Shape[T, NothingContainer#TableNothing, T] as defined in slick.lifted.Shape. Furthermore, if I do something like
(for {
us <- UserSkills
u <- us.user
s <- us.skill
} yield (u,s))
.groupBy(_._1.id)
.map { case (_, xs) => xs.map(_._1.id).first }
I get a strange error of the form "NoSuchElementException: key not found: #1515100893", where the numeric key value changes each time. This is not the query I want, but it is a strange issue none the less.
I've run up against similar situations as well. While I love working with Scala and Slick, I do believe there are times when it is easier to denormalize an object in the database itself and link the Slick Table to a view.
For example, I have an application that has a Tree object that is normalized into several database tables. Since I'm comfortable with SQL, I think it is a cleaner solution than writing a plain Scala Slick query. The Scala code:
case class DbGFolder(id: String,
eTag: String,
url: String,
iconUrl: String,
title: String,
owner: String,
parents: Option[String],
children: Option[String],
scions: Option[String],
created: LocalDateTime,
modified: LocalDateTime)
object DbGFolders extends Table[DbGFolder]("gfolder_view") {
def id = column[String]("id")
def eTag = column[String]("e_tag")
def url = column[String]("url")
def iconUrl = column[String]("icon_url")
def title = column[String]("title")
def owner = column[String]("file_owner")
def parents = column[String]("parent_str")
def children = column[String]("child_str")
def scions = column[String]("scion_str")
def created = column[LocalDateTime]("created")
def modified = column[LocalDateTime]("modified")
def * = id ~ eTag ~ url ~ iconUrl ~ title ~ owner ~ parents.? ~
children.? ~ scions.? ~ created ~ modified <> (DbGFolder, DbGFolder.unapply _)
def findAll(implicit s: Session): List[GFolder] = {
Query(DbGFolders).list().map {v =>
GFolder(id = v.id,
eTag = v.eTag,
url = v.url,
iconUrl = v.iconUrl,
title = v.title,
owner = v.owner,
parents = v.parents.map { parentStr =>
parentStr.split(",").toSet }.getOrElse(Set()),
children = v.children.map{ childStr =>
childStr.split(",").toSet }.getOrElse(Set()),
scions = v.scions.map { scionStr =>
scionStr.split(",").toSet }.getOrElse(Set()),
created = v.created,
modified = v.modified)
}
}
}
And the underlying (postgres) view:
CREATE VIEW scion_view AS
WITH RECURSIVE scions(id, scion) AS (
SELECT c.id, c.child
FROM children AS c
UNION ALL
SELECT s.id, c.child
FROM children AS c, scions AS s
WHERE c.id = s.scion)
SELECT * FROM scions ORDER BY id, scion;
CREATE VIEW gfolder_view AS
SELECT
f.id, f.e_tag, f.url, f.icon_url, f.title, m.name, f.file_owner,
p.parent_str, c.child_str, s.scion_str, f.created, f.modified
FROM
gfiles AS f
JOIN mimes AS m ON (f.mime_type = m.name)
LEFT JOIN (SELECT DISTINCT id, string_agg(parent, ',' ORDER BY parent) AS parent_str
FROM parents GROUP BY id) AS p ON (f.id = p.id)
LEFT JOIN (SELECT DISTINCT id, string_agg(child, ',' ORDER BY child) AS child_str
FROM children GROUP BY id) AS c ON (f.id = c.id)
LEFT JOIN (SELECT DISTINCT id, string_agg(scion, ',' ORDER BY scion) AS scion_str
FROM scion_view GROUP BY id) AS s ON (f.id = s.id)
WHERE
m.category = 'folder';
Try this. Hope it may yield what you expected. Find the Slick Code below the case classes.
click here for the reference regarding lifted embedding .
case class User(val name: String, val picture: Option[URL], val id: Option[UUID])
class Users(_tableTag: Tag) extends Table[User](_tableTag,"users") {
def name = column[String]("name")
def picture = column[Option[URL]]("picture")
def id = column[UUID]("id")
def * = name ~ picture ~ id.? <> (User, User.unapply _)
}
lazy val userTable = new TableQuery(tag => new Users(tag))
case class Skill(val name: String, val id: Option[UUID])
class Skills(_tableTag: Tag) extends Table[Skill](_tableTag,"skill") {
def name = column[String]("name")
def id = column[UUID]("id")
def * = name ~ id.? <> (Skill, Skill.unapply _)
}
lazy val skillTable = new TableQuery(tag => new Skills(tag))
case class UserSkill(val userId: UUID, val skillId: UUID, val id: Option[UUID])
class UserSkills(_tableTag: Tag) extends Table[UserSkill](_tableTag,"user_skill") {
def userId = column[UUID]("userId")
def skillId = column[UUID]("skillId")
def id = column[UUID]("id")
def * = userId ~ skillId ~ id.? <> (UserSkill, UserSkill.unapply _)
def user = foreignKey("userFK", userId, Users)(_.id)
def skill = foreignKey("skillFK", skillId, Skills)(_.id)
}
lazy val userSkillTable = new TableQuery(tag => new UserSkills(tag))
(for {((userSkill, user), skill) <- userSkillTable join userTable.filter on
(_.userId === _.id) join skillTable.filter on (_._1.skillId === _.id)
} yield (userSkill, user, skill)).groupBy(_.2.id)