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.
Related
Recently trying to migrate to the latest phantom version 2.24.8. I created a dummy project, but running into a few issues that I can't figure out. Here's my code:
import com.outworkers.phantom.connectors.{CassandraConnection, ContactPoints}
import com.outworkers.phantom.database.Database
import scala.concurrent.Future
import com.outworkers.phantom.dsl._
case class Test(id: String, timestamp: String)
abstract class Tests extends Table[Tests, Test] {
object id extends StringColumn with PartitionKey
object timestamp extends StringColumn with ClusteringOrder
}
abstract class ConcreteTests extends Tests with RootConnector {
def addTest(l: Test): Future[ResultSet] = {
// store(l).consistencyLevel_=(ConsistencyLevel.LOCAL_ONE).future
insert.value(_.id, l.id)
.value(_.timestamp, l.timestamp)
.consistencyLevel_=(ConsistencyLevel.QUORUM).future
}
}
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
tests.create
}
}
object Test{
def main(args: Array[String]): Unit = {
val db = new MyDB(ContactPoints(Seq("127.0.0.1")).keySpace("tests"))
db.init
db.tests.addTest(Test("1", "1323234234"))
println("Done")
}
}
It compiled and ran in IntelliJ and print out 'Done'. However, no table is ever created. Also no exceptions or warnings. It did nothing. I tried to stop the local cassandra database. The code throws the NoHostAvailableException. So it does try to connect the local database. What is the problem?
Another weird thing is that "com.typesafe.play" %% "play-json" % "2.6.9" is in my build.sbt. If I remove the library, the same code throws the following exception:
Exception in thread "main" java.lang.NoClassDefFoundError: scala/reflect/runtime/package$
at com.outworkers.phantom.column.AbstractColumn.com$outworkers$phantom$column$AbstractColumn$$_name(AbstractColumn.scala:55)
at com.outworkers.phantom.column.AbstractColumn.com$outworkers$phantom$column$AbstractColumn$$_name$(AbstractColumn.scala:54)
at com.outworkers.phantom.column.Column.com$outworkers$phantom$column$AbstractColumn$$_name$lzycompute(Column.scala:22)
at com.outworkers.phantom.column.Column.com$outworkers$phantom$column$AbstractColumn$$_name(Column.scala:22)
at com.outworkers.phantom.column.AbstractColumn.name(AbstractColumn.scala:58)
at com.outworkers.phantom.column.AbstractColumn.name$(AbstractColumn.scala:58)
at com.outworkers.phantom.column.Column.name(Column.scala:22)
at com.outworkers.phantom.builder.query.InsertQuery.value(InsertQuery.scala:107)
Really cannot figure what's going on. Any help?
BTW, I'm using scala 2.12.6 and JVM 1.8.181.
You're not using the correct DSL method for table creation, have a look at the official guide. All that table.create does is to create an empty CreateQuery, and you're coercing the return type to Unit manually.
The automated blocking create method is on Database, not on table, so what you want is:
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
this.create
}
}
If you want to achieve the same thing using table, you need:
class MyDB(override val connector: CassandraConnection) extends Database[MyDB](connector) {
object tests extends ConcreteTests with connector.Connector
def init(): Unit = {
import scala.concurrent.duration._
import scala.concurrent.Await
Await.result(tests.create.future(), 10.seconds)
}
}
It's only the call to future() method that will trigger any kind of action to the database, otherwise you're just building a query without executing it. The method name can be confusing, and we will improve the docs and future releases to make it more obvious.
The conflict with play 2.6.9 looks very weird, it's entirely possible there's an incompatible dependency behind the scenes to do with macro compilation. Raise that as a separate issue and we can definitely have a look at it.
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]
}
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.
I'm working with a project which already has some legacy code written in Scala. I was given a task to write some unit tests for one of its classes when I discovered it's not so easy. Here's the problem I've encountered:
We have an object, say, Worker and another object to access the database, say, DatabaseService which also extends other class (I don't think it matters, but still). Worker, in its turn, is called by higher classes and objects.
So, right now we have something like this:
object Worker {
def performComplexAlgorithm(id: String) = {
val entity = DatabaseService.getById(id)
//Rest of the algorithm
}
}
My first though was 'Well, I can probably make a trait for DatabaseService with the getById method'. I don't really like the idea to create an interface/trait/whatever just for the sake of testing because I believe it doesn't necessarily lead to a nice design, but let's forget about it for now.
Now, if Worker was a class, I could easily use DI. Say, via constructor like this:
trait DatabaseAbstractService {
def getById(id: String): SomeEntity
}
object DatabaseService extends SomeOtherClass with DatabaseAbstractService {
override def getById(id: String): SomeEntity = {/*complex db query*/}
}
//Probably just create the fake using the mock framework right in unit test
object FakeDbService extends DatabaseAbstractService {
override def getById(id: String): SomeEntity = {/*just return something*/}
}
class Worker(val service: DatabaseService) {
def performComplexAlgorithm(id: String) = {
val entity = service.getById(id)
//Rest of the algorithm
}
}
The problem is, Worker is not a class so I can't make an instance of it with another service. I could do something like
object Worker {
var service: DatabaseAbstractService = /*default*/
def setService(s: DatabaseAbstractService) = service = s
}
However, it scarcely makes any sense to me since it looks awful and leads to an object with mutable state which doesn't seem very nice.
The question is, how can I make the existing code easily testable without breaking anything and without making any terrible workarounds? Is it possible or should I change the existing code instead so that I could test it easier?
I was thinking about using extending like this:
class AbstractWorker(val service: DatabaseAbstractService)
object Worker extends AbstractWorker(DatabaseService)
and then I somehow could create a mock of Worker but with different service. However, I didn't figure out how to do it.
I'd appreciate any advice as to how either change the current code to make it more testable or test the existing.
If you can alter the code for Worker, you can change it to still allow it to be an object and also allow for swapping of the db service via an implicit with a default definition. This is one solution and I don't even know if this is possible for you, but here it is:
case class MyObj(id:Long)
trait DatabaseService{
def getById(id:Long):Option[MyObj] = {
//some impl here...
}
}
object DatabaseService extends DatabaseService
object Worker{
def doSomething(id:Long)(implicit dbService:DatabaseService = DatabaseService):Option[MyObj] = {
dbService.getById(id)
}
}
So we set up a trait with concrete impl of the getById method. Then we add an object impl of that trait as a singleton instance to use in the code. This is a good pattern to allow for mocking of what was previously only defined as an object. Then, we make Worker accept an implicit DatabaseService (the trait) on it's method and give it a default value of the object DatabaseService so that regular use does not have to worry about satisfying that requirement. Then we can test it like so:
class WorkerUnitSpec extends Specification with Mockito{
trait scoping extends Scope{
implicit val mockDb = mock[DatabaseService]
}
"Calling doSomething on Worker" should{
"pass the call along to the implicit dbService and return rhe result" in new scoping{
mockDb.getById(123L) returns Some(MyObj(123))
Worker.doSomething(123) must beSome(MyObj(123))
}
}
Here, in my scope, I make an implicit mocked DatabaseService available that will supplant the default DatabaseService on the doSomething method for my testing purposes. Once you do that, you can start mocking out and testing.
Update
If you don't want to take the implicit approach, you could redefine Worker like so:
abstract class Worker(dbService:DatabaseService){
def doSomething(id:Long):Option[MyObj] = {
dbService.getById(id)
}
}
object Worker extends Worker(DatabaseService)
And then test it like so:
class WorkerUnitSpec extends Specification with Mockito{
trait scoping extends Scope{
val mockDb = mock[DatabaseService]
val testWorker = new Worker(mockDb){}
}
"Calling doSomething on Worker" should{
"pass the call along to the implicit dbService and return rhe result" in new scoping{
mockDb.getById(123L) returns Some(MyObj(123))
testWorker.doSomething(123) must beSome(MyObj(123))
}
}
}
In this way, you define all the logic of importance in the abstract Worker class and that's what you till focus your testing on. You provide a singleton Worker via an object that is used in the code for convenience. Having an abstract class let's you use a constructor param to specify the database service impl to use. This is semantically the same as the previous solution but it's cleaner in that you don't need the implicit on every method.
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()
}