Convenient way of inserting values to Cassandra with Phantom - scala

Does anyone know of a convenient way of inserting values to Cassandra via phatom-dsl? Currently I'm doing this:
case class Entry(id: UUID, firstName: String, lastName: String)
sealed class EntryTable extends CassandraTable[EntryTable, Entry] {
override val tableName = "entries"
object id extends UUIDColumn(this) with Index[UUID]
object firstName extends StringColumn(this)
object lastName extends StringColumn(this)
override def fromRow(r: dsl.Row): Entry = {
Entry(id(r), firstName(r), firstName(r))
}
}
object EntryTable extends EntryTable {
private val connector = CassandraConnector.apply(Set(InetAddress.getByName("localhost")))
implicit val keySpace = KeySpace("keyspace")
def insert(e: Entry) = {
connector.withSessionDo(implicit session => {
insert().value(_.id, e.id)).value(_.firstName, e.firstName).value(_.lastName, e.lastName).future()
}
}
}
But I would like to do:
def insert(e: Entry) = {
connector.withSessionDo(implicit session => {
insert().value(e).future()
}
}
Which would be way more convenient when the case class has many fields. Any ideas?

You are using the API slightly wrong and we are in the process of publishing multiple tutorials to make the "new" way public. In the mean time, a basic version of it is available here and this branch in the activator template is also describing everything you need to know.
Specifically, the way to insert records is described here.

Related

phantom cassandra multiple tables throw exceptions

I'm using phantom to connect cassandra in play framework. Created the first class following the tutorial. Everything works fine.
case class User(id: String, page: Map[String,String])
sealed class Users extends CassandraTable[Users, User] {
object id extends StringColumn(this) with PartitionKey[String]
object page extends MapColumn[String,String](this)
def fromRow(row: Row): User = {
User(
id(row),
page(row)
)
}
}
abstract class ConcreteUsers extends Users with RootConnector {
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
def create(id:String, kv:(String,String)): Future[ResultSet] = {
insert.value(_.id, id).value(_.page, Map(kv)).consistencyLevel_=(ConsistencyLevel.QUORUM).future()
}
}
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
}
object UserDB extends ResourceAuthDB(conn) {
def createTable() {
Await.ready(users.create.ifNotExists().future(), 3.seconds)
}
}
However, when I try to create another table following the exact same way, play throws the exception when compile:
overriding method session in trait RootConnector of type => com.datastax.driver.core.Session;
How could I build create another table? Also can someone explain what causes the exception? Thanks.
EDIT
I moved the connection part together in one class:
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
object auth extends ConcreteAuthInfo with keyspace.Connector
}
This time the error message is:
overriding object session in class AuthInfo; lazy value session in trait Connector of
type com.datastax.driver.core.Session cannot override final member
Hope the message helps identify the problem.
The only problem I see here is not to do with connectors, it's here:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
This should be:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs page).one()
}
Try this, I was able to compile. Is RootConnector the default one or do you define another yourself?
It took me 6 hours to figure out the problem. It is because there is a column named "session" in the other table. It turns out that you need to be careful when selecting column names. "session" obviously gives the above exception. Cassandra also has a long list of reserved keywords. If you accidentally use one of them as your column name, phantom will not throw any exceptions (maybe it should?). I don't know if any other keywords are reserved in phantom. A list of them will be really helpful.

How to model nested class with Phantom Cassandra driver

I have a case class that has a number of nested classes.
How do I model with is Phantom DSL
Putting it all into one case class is not an option.
For example:
case class Car(age: Int,size: Int,door: Door)
case class Door(color:String, size:Int)
Thanks
Well, when modeling things on Cassandra, you should have in mind that it does not work like relational databases and phantom is not a kind of hibernate.
One important thing when modeling is to consider the queries you want to do, but let's get to the point.
Phantom does allow you to model nested classes, using the json table.
Consider the following:
case class JsonTest(prop1: String, prop2: String)
case class JsonClass(
id: UUID,
name: String,
json: JsonTest,
jsonList: List[JsonTest],
jsonSet: Set[JsonTest]
)
You have inside the JsonClass 3 columns with JsonTest case class type.
When declaring your fields, you should do something like this:
object json extends JsonColumn[ConcreteJsonTable, JsonClass, JsonTest](this) {
override def fromJson(obj: String): JsonTest = {
JsonParser.parse(obj).extract[JsonTest]
}
override def toJson(obj: JsonTest): String = {
compactRender(Extraction.decompose(obj))
}
}
object jsonList extends JsonListColumn[ConcreteJsonTable, JsonClass, JsonTest](this) {
override def fromJson(obj: String): JsonTest = {
JsonParser.parse(obj).extract[JsonTest]
}
override def toJson(obj: JsonTest): String = {
compactRender(Extraction.decompose(obj))
}
}
object jsonSet extends JsonSetColumn[ConcreteJsonTable, JsonClass, JsonTest](this) {
override def fromJson(obj: String): JsonTest = {
JsonParser.parse(obj).extract[JsonTest]
}
override def toJson(obj: JsonTest): String = {
compactRender(Extraction.decompose(obj))
}
}
Basically what phantom is doing is to save a string json representation inside a string column.
source: https://github.com/outworkers/phantom/blob/develop/phantom-dsl/src/test/scala/com/websudos/phantom/tables/JsonTable.scala
You can't really do that because it's not Hibernate or something like that. You need to use the nested class' ID, like this:
case class Car(age: Int,size: Int, doorId: UUID)
case class Door(id: UUID, color:String, size:Int)
Then just add a function to the case class that gives back the Door object calling a getById on that.
Try simpledba https://github.com/doolse/simpledba
It seems to define a relational view over columnar databases.

reactivemongo BSON writer / reader with arbitrary JSON branch (also using spray)

I am building REST API using spray. All is working well except this case class:
case class User(name: String, places: List[String], data: List[JsObject])
The key issue here is the data parameter. It contains a json object with arbitrary number of members, types, and levels - but still valid json.
Using spray, I am able to serialize/deserialize a request/response properly using:
object UserProtocol extends DefaultJsonProtocol {
implicit val userResonseFormat = jsonFormat3(User)
}
// ...
import demo.UserProtocol._
post {
path("users") {
entity(as[User]) { user: User =>
complete(user)
}
}
}
The problem is reading and writing BSON for reactivemongo. I cannot seem to figure out how to complete these:
implicit object UserWriter extends BSONDocumentWriter[User] {
def write(user: User): BSONDocument = BSONDocument(
"name" -> user.name,
"places" -> user.places,
"data" -> ???
}
implicit object UserReader extends BSONDocumentReader[User] {
def read(doc: BSONDocument): User = {
User(
doc.getAs[String]("name").get,
doc.getAs[List[String]]("places").get,
???
}
}
In the places of ???, How can I get this arbitrary JSON branch to serialize/deserialize BSON properly for reactivemongo?
This is a simple example that illustrate how to define readers and writers for a model. Hope it helps.
https://github.com/luongbalinh/play-mongo/blob/master/app/models/User.scala

Create instance of object with only string of class name.

I'm new to scala and trying to figure things out as I go. I'm working with the Play 2.x framework and using Scala to build my app. I have a route defined
GET /:tableName controllers.Application.getTable(tableName)
In the controller I would like to take the name of the table and use it as I would the class. For example in the db if have a table named People. I would like it to map to the Slick model for People that has the function getAll. I looked into typeof[t], but couldn't get it to work. below it is an example of what I would like to do.
def getTable(tableName: String) = Action {
Ok(Json.toJson(typeOf[tableName].getAll))
}
You're going to need a bit more than that, to get this accomplished ;) First of all, Slick requires a DB session, so that needs to be handled somewhere. Meaning a Slick Table getAll won't work by itself.
I would do something like this (sorry, typing this up without an IDE, so it may not compile):
case class Person(...)
object People extends Table[Person](PeopleDAO.table) {
def * = ...
}
trait DAO[T] {
val table: String
def getAll: Seq[T]
}
object PeopleDAO extends DAO[Person] {
override val table = "people"
def getAll = {
DB withSession { implicit session =>
Query(People).list
}
}
}
object Controller {
def getTable(tableName: String) = Action {
val dao: DAO[_] = tableName.toLowerCase match {
case PeopleDAO.table => PeopleDAO
case _ => throw new IllegalArgumentException("Not a valid table.")
}
Ok(Json.toJson(dao.getAll))
}
}

Scala: Copying case classes with trait

I'm fairly new to Scala and I have a question about the best way to copy a case class while preserving data that comes from traits. For example, let's say I have the following:
trait Auditing {
var createTime: Timestamp = new Timestamp(System.currentTimeMillis)
}
case class User(val userName: String, val email: String) extends Auditing
val user = User("Joe", "joe#blah.com")
Then I want to make a new copy with one parameter changed:
val user2 = user.copy(email = "joe#newemail.com")
Now, in the example above, the property createTime does not get copied over because it is not defined in the constructor of the User case class. So my question is: assuming that moving createTime into the constructor is not an option, what is the best way for getting a copy of the User object that includes the value from the trait?
I'm using Scala 2.9.1
Thanks in advance!
Joe
You can override the copy method with that behavior.
case class User(val userName: String, val email: String) extends Auditing
{
def copy(userName = this.userName, email = this.email) {
val copiedUser = User(userName, email)
copiedUser.createTime = createTime
copiedUser
}
}
While I see no other solution than Reuben's, I don't understand the requirement to leave the constructor args untouched. This would be the most natural solution:
case class User(userName: String, email: String,
override val createTime:Timestamp = new Timestamp(System.currentTimeMillis))
extends Auditing
If you don't want the user to be able to overwrite createTime, you can still use:
case class User private (userName: String, email: String,
override val createTime:Timestamp) extends Auditing {
def this(userName: String, email: String) =
this(userName, email, new Timestamp(System.currentTimeMillis))
}
The only drawback is that you need to write new User("Joe", "joe#blah.com"), as the primary constructor is now private.
You might be better of not using a case class. You can easily implement the
functionality you need yourself. The below code implements the copy method you wanted, a constructor without new, hides the original constructor, and creates an extractor so that you can use User in case statements.
class User private(val userName: String,
val email: String,
val timeStamp: Timestamp =
new Timestamp(System.currentTimeMillis)) {
def copy(uName: String = userName,
eMail: String = email) =
new User(uName, eMail, timeStamp)
}
object User {
def apply(userName: String, email: String) =
new User(userName, email)
def unapply(u: User) = Some((u.userName, u.email, u.timeStamp))
}