What's wrong with CollectionColumn? - scala

I'm trying out phantom from outworkers following the laidout tut on the wiki.
I'm using a test model:
case class User (id: String, name: String, friends: List[String])
with:
import com.websudos.phantom.dsl._
class Users extends CassandraTable[Users, User] {
object id extends StringColumn(this) with PartitionKey[String]
object name extends StringCoumn(this)
object friends extends ListColumn[String](this)
}
The ListColumn[String]() argument this is marked as an error which I presume I shouldnt even bother to build. Expected CassandraTable[String, User] instead of this.
I'm using version 1.29.6
Am I using a different version from the wiki example? Or missing something else?

This is an InteliJ highlightining problem. ListColumn is defined as a type alias inside Cassandra table, and for all type aliases that take constructor arguments, InteliJ is not capable of seeing through them.
That aside, I would really upgrade to phantom 2.0.0+, just because of all the new improvements made in 2.0.0. There is quite a bit of work that's gone into fixing errors and reducing how much code you need to type:
import com.outworkers.phantom.dsl._
class Users extends CassandraTable[Users, User] {
object id extends StringColumn(this) with PartitionKey
object name extends StringCoumn(this)
object friends extends ListColumn[String](this)
}
In more recent versions of phantom, 2.9.x+, the this argument is no longer required using the new compact DSL.
import com.outworkers.phantom.dsl._
abtract class Users extends Table[Users, User] {
object id extends StringColumn with PartitionKey
object name extends StringColumn
object friends extends ListColumn[String]
}

Related

How to create keyspace and insert data using Phantom dsl

I'm using this library for the first time, and I ran into a problem. I did everything according to the documentation, but nothing is working, and i don't know why. Here is my table model:
trait CassandraModel
object CassandraModel {
case class TaskData(notifyid: String,
notifyType: String)
extends CassandraModel
abstract class TaskDataCassandra extends Table[TaskDataCassandra, TaskData] {
object notifyid extends StringColumn with PartitionKey
object notifyType extends StringColumn
def store(record: TaskData): InsertQuery.Default[TaskDataCassandra, TaskData] =
insert
.value(_.notifyId, record.notifyId)
.value(_.notifyType, record.notifyType)
}
}
And DataBase with DatabaseProvider:
class AppDatabase(override val connector: CassandraConnection) extends Database[AppDatabase](connector) {
object taskDataCassandra extends TaskDataCassandra with Connector
}
trait AppDatabaseProvider extends DatabaseProvider[AppDatabase]
So, when i starting my app, i'm trying to create a keyspace, but nothing is happens
object Boot extends App with AmqpConnector with ServiceRestRoute with JsonSerializer with AppDatabaseProvider {
override def database: AppDatabase = new AppDatabase(CassandraConnector.createCassandraConnection)
database.taskDataCassandra.create.ifNotExists()
}
store method also doesn't works
Read through the documentation properly and the differences will be obvious. The thing to read is the Database Docs.
You have 2 options. You can call database.create(), which is a blocking creation operation that will create all the tables inside the database.
Option 2 is to call database.taskDataCassandra.create.ifNotExists().future().
If you do not use future(), all you have is a query generated, you are not actually executing anything. If you check the return type of database.taskDataCassandra.create.ifNotExists() it will be a CreateQuery, wheres if you add future() you get a Future[Result].
Hope this makes sense.

Mongodb scala driver codec for trait and inherited classes

Using the following mongo-driver. I want to insert and get structures (see below) from MongoDB.
trait A {
def id: ObjectId
}
case class B(id: ObjectId) extends A
case class C(id: ObjectId, name: String) extends A
I find a solution with using sealed classes, but I want to use traits.
I want to find a solution with Codecs or something else.
I had the same concern just a few days ago but didn't find anything in the documentation regarding sealed traits for modeling ADT in MongoDB.
In the end, I used sealed class as suggested in the official scala driver github repo.
If you really want to use traits (due to the definition of abstract methods) you can do something like this:
package example.model
import example.model.adt._
import org.mongodb.scala.bson.ObjectId
import org.mongodb.scala.bson.codecs.Macros._
import org.mongodb.scala.bson.codecs.DEFAULT_CODEC_REGISTRY
import org.bson.codecs.configuration.CodecRegistries.{fromProviders, fromRegistries}
trait MongoModel {
def _id: ObjectId
}
object MongoModel {
val codecRegistery = fromRegisteries(fromProviders(classOf[A]), DEFAULT_CODEC_REGISTRY)
}
Now you can have your ADT for A defined with sealed class.
package example.model.adt
import example.model.MongoModel
import org.mongodb.scala.bson.ObjectId
sealed class A
final case class B(_id: ObjectId) extends A with MongoModel
final case class C(_id: ObjectId) extends A with MongoModel
This answer doesn't solve the question directly but provides a feasible workaround. Note that this code is just an example. For a more complete implementation, you can see this github repo.
Since release 2.7, the mongodriver is now able to serialize sealed traits.
It works exactly like serializing a sealed classes.

scala value class multiple inheritance

I have in my project objects that represent IDs.
Let's say it is ChairId, TableId, LampId. I want them all to inherit from GenericId. And I want to be able to call def f(x: GenericId) = x.id
I want them to hold only single id: String so I would like to make them extend AnyVal.
Also I would like for each type to provide function generate which would generate my specific ID i.e. I would like to type something like ChairId.generate()
I have typed this:
sealed abstract class GenericId(val id: String)
final case class ChairId(override val id: String) extends GenericId(id)
final case class TableId(override val id: String) extends GenericId(id
And I though if GenericId would inherit from AnyVal that would work but so far no luck ;/ I also tried making GenericId a trait and make case classes extend AnyVal with GenericId but also won't compile :/
Another thing with TableId.generate() I can provide companion object just with function generate and that basically solve my problem but I wondered if there is possibility to solve that without defining companion object? (i.e. through implicits somehow)
// edit
regarding comment to provide code which doesn't compile(and I would like to):
sealed abstract class AbstractId(val id: String) extends AnyVal
final case class CatId(override val id: String) extends AbstractId(id)
final case class DogId(override val id: String) extends AbstractId(id)
Value classes cannot work this way for a couple of reasons.
First, from the documentation, value classes cannot be extended by any other class, so AbstractId cannot extend AnyVal. (Limitation #7)
scala> abstract class AbstractId(val id: String) extends AnyVal
<console>:10: error: `abstract' modifier cannot be used with value classes
abstract class AbstractId(val id: String) extends AnyVal
^
Second, even if you make AbstractId a trait, and define the other ids like this:
final case class DogId(val id: String) extends AnyVal with AbstractId
.. the usage of the value class wouldn't fit your case, because the class itself would still get allocated. See the allocation summary:
A value class is actually instantiated when:
a value class is treated as another type.
a value class is assigned to an array.
doing runtime type tests, such as pattern matching.
Some quotes from the value classes SIP that are likely to clarify your doubts:
Value classes...
...must have only a primary constructor with exactly one public, val
parameter whose type is not a value class.
... cannot be extended by another class.
As per 1. it can not be abstract; per 2. your encoding doesn't work.
There is another caveat:
A value class can only extend universal traits and cannot be extended
itself. A universal trait is a trait that extends Any, only has defs
as members, and does no initialization. Universal traits allow basic
inheritance of methods for value classes, but they incur the overhead
of allocation.
With all that in mind, based on your last snippet, this might work:
sealed trait AbstractId extends Any { def id: String }
final case class CatId(id: String) extends AnyVal with AbstractId
final case class DogId(id: String) extends AnyVal with AbstractId
But keep in mind the allocation only occurs if you want to use CatId and DogId as an AbstractId. For better understanding I recommend reading the SIP.

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.

Where in select using Phantom doesn't resolve

I have been toying around with the smiple code provided in the phantom wiki, the follow I have tried;
import com.websudos.phantom.dsl._
case class Student(id: UUID, name: String)
class Students extends CassandraTable[Students, Student] {
object id extends UUIDColumn(this) with PartitionKey[UUID]
object name extends StringColumn(this)
def fromRow(row: Row): Student = {
Student(id(row), name(row))
}
}
object Students extends Students with Connector {
def getByName(name: String): Future[Option[Student]] = {
select.where(_.name eqs name).one()
}
}
But my IDE keeps saying Cannot resolve symbol where and the compiler says value where is not a member of com.websudos.phantom.builder.query.RootSelectBlock[Students,Student]
I'm using Scala 2.11.6 and Phantom 1.10.1, all help is greatly appreciated!
I ran into this issue and resolved this using #flavian's suggestion above.
Ensure that your Connector has an implicit keyspace defined.
This is directly lifted from the example project.
trait KeyspaceDefinition {
implicit val keySpace = KeySpace("sample_keyspace")
}
trait Connector extends SimpleConnector with KeyspaceDefinition
You are missing out on a fundamental Cassandra issue, you cannot query by name as it's not an indexed column. Based on the table you've just defined, the query you are trying to perform is invalid and Cassandra will tell you that at runtime.
Phantom will prevent most bad things at compile time. It's worth reading through this blog series to understand how things work in Cassandra.
To put things in perspective, the only where query that's valid for your Students table is:
def getById(id: UUID): Future[Option[Student]] = {
select.where(_.id eqs id).one()
}