I have n to m database (movie and cast). I need to get a list of cast associated with a movie.
#Entity(tableName = "movie")
data class MovieDbModel(
#PrimaryKey(autoGenerate = false)
val id: Int,
val poster_path: String,
val overview: String,
val title: String)
#Entity(tableName = "cast")
#TypeConverters(CastConverter::class)
data class CastDbModel(
#PrimaryKey(autoGenerate = false)
val id : Int,
val cast: Cast //Arraylist of casts
)
data class Cast(
#Embedded
var name: String,
var profile_path: String?,
var character: String
)
Crossreferenced class:
#Entity(
tableName = "movie_cast",
primaryKeys = ["movieIdMap","castIdMap"],
foreignKeys = [
ForeignKey(
entity = MovieDbModel::class,
parentColumns = ["id"],
childColumns = ["movieIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
ForeignKey(
entity = CastDbModel::class,
parentColumns = ["id"],
childColumns = ["castIdMap"],
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class MovieCastCrossRef(
var movieIdMap: Int,
#ColumnInfo(index = true)
var castIdMap: Int
)
Relation:
data class MovieWithListOfCast(
#Embedded /* The parent */
var movie: MovieDbModel,
#Relation(
entity = CastDbModel::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = MovieCastCrossRef::class,
parentColumn = "castIdMap",
entityColumn = "movieIdMap"
)
)
var castList: List<CastDbModel>
)
The Query looks like this:
#Transaction
#Query("select * FROM `cast` WHERE id = :id")
fun getAllCastAssociatedWithMovie(id: Int): List<MovieWithListOfCast>
But I get the following warning:
The query returns some columns [cast] which are not used by MovieWithListOfCast. You can use #ColumnInfo annotation on the fields to specify the mapping. You can annotate the method with #RewriteQueriesToDropUnusedColumns to direct Room to rewrite your query to avoid fetching unused columns.
MovieWithListOfCast has some fields [poster_path, overview, title] which are not returned by the query. If they are not supposed to be read from the result, you can mark them with #Ignore annotation. You can suppress this warning by annotating the method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: id, cast.
How do I query to get a list of Cast that contains name, profile_path, and character?
I believe that you have concepts reversed.
First,
the #Embedded is the parent thus the parentColumn should specify a column in the #Embedded, you have parentColumn = "castIdMap" when it should be parentColumn = "movieIdMap" along with changing to use entityColumn = "castIdMap".
This would not necessarily be apparent at first but would likely lead to confusion (all depends upon the reversed id's)
Second,
the MovieWithListOfCast will get the list of cast for a movie, therefore it needs to get the movie from the movie table not the cast table.
So MovieWithListOfClass could be:-
data class MovieWithListOfCast(
#Embedded /* The parent */
var movie: MovieDbModel,
#Relation(
entity = CastDbModel::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = MovieCastCrossRef::class,
parentColumn = "movieIdMap",
entityColumn = "castIdMap"
)
)
var castList: List<CastDbModel>
)
Along with:-
#Transaction
#Query("select * FROM `movie` WHERE id = :id")
fun getAllCastAssociatedWithMovie(id: Int): List<MovieWithListOfCast>
You could also have (just in case that is what you are thinking):-
data class CastWithListOfMovies(
#Embedded
var castList: CastDbModel,
#Relation(
entity = MovieDbModel::class,
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
value = MovieCastCrossRef::class,
parentColumn = "castIdMap",
entityColumn = "movieIdMap"
)
)
var movieList: List<MovieDbModel>
)
Along with:-
#Transaction
#Query("select * FROM `cast` WHERE id = :id")
fun getAllMoviesAssociatedWithCast(id: Int): List<CastWithListOfMovies>
Putting both into action using your code amended as above and with a suitable #Database annotated class then using:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
val m1id = dao.insert(MovieDbModel(100,"posetr001","Western","The Good, the Bad and the Ugly"))
val m2id = dao.insert(MovieDbModel(200,"poster002","Sci-Fi","Star Wars"))
val m3id = dao.insert(MovieDbModel(300,"poster003","Soppy","Gone with the Wind"))
val c1id = dao.insert(CastDbModel(1,Cast("Actor1","acts","an actor")))
val c2id = dao.insert(CastDbModel(2,Cast("Actor2","acts","an actor")))
val c3id = dao.insert(CastDbModel(3,Cast("Actor3","acts","an actor")))
val c4id = dao.insert(CastDbModel(4,Cast("Actor4","acts","an actor")))
val c5id = dao.insert(CastDbModel(5,Cast("Actor5","acts","an actor")))
val c6id = dao.insert(CastDbModel(6,Cast("Actor6","acts","an actor")))
val c7id = dao.insert(CastDbModel(7,Cast("Actor7","acts","an actor")))
dao.insert(MovieCastCrossRef(m1id.toInt(),c1id.toInt()))
dao.insert(MovieCastCrossRef(m1id.toInt(),c3id.toInt()))
dao.insert(MovieCastCrossRef(m1id.toInt(),c5id.toInt()))
dao.insert(MovieCastCrossRef(m1id.toInt(),c7id.toInt()))
dao.insert(MovieCastCrossRef(m2id.toInt(),c2id.toInt()))
dao.insert(MovieCastCrossRef(m2id.toInt(),c4id.toInt()))
dao.insert(MovieCastCrossRef(m2id.toInt(),c6id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c1id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c2id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c3id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c4id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c5id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c6id.toInt()))
dao.insert(MovieCastCrossRef(m3id.toInt(),c7id.toInt()))
logCastWithMovie(c1id.toInt(),"C1")
logMovieWithCast(m1id.toInt(),"M1")
logCastWithMovie(c2id.toInt(),"C2")
logMovieWithCast(m2id.toInt(),"M2")
logCastWithMovie(c3id.toInt(),"C3")
logMovieWithCast(m3id.toInt(),"M3")
}
fun logMovieWithCast(movieId: Int, tagSuffix: String) {
var sb = StringBuilder()
for (cawm in dao.getAllCastAssociatedWithMovie(movieId)) {
for (c in cawm.castList) {
sb.append("\n\t Name is ${c.cast.name} Profile is ${c.cast.profile_path} Character is ${c.cast.character}")
}
Log.d("DBINFO_CAWM_$tagSuffix","Movie is ${cawm.movie.title} cast are:$sb")
}
}
fun logCastWithMovie(castId: Int, tagSuffix: String) {
var sb = StringBuilder()
for (mawc in dao.getAllMoviesAssociatedWithCast(castId)) {
for (m in mawc.movieList) {
sb.append("\n\tTitle is ${m.title} Overview is ${m.overview} Poster is ${m.poster_path}")
}
Log.d("DBINFO_MAWC_$tagSuffix","Cast is ${mawc.castList.cast.name} Movies are $sb")
}
}
}
Results in the log including:-
2022-09-03 08:15:25.084 D/DBINFO_MAWC_C1: Cast is Actor1 Movies are
Title is The Good, the Bad and the Ugly Overview is Western Poster is posetr001
Title is Gone with the Wind Overview is Soppy Poster is poster003
2022-09-03 08:15:25.095 D/DBINFO_CAWM_M1: Movie is The Good, the Bad and the Ugly cast are:
Name is Actor1 Profile is acts Character is an actor
Name is Actor3 Profile is acts Character is an actor
Name is Actor5 Profile is acts Character is an actor
Name is Actor7 Profile is acts Character is an actor
2022-09-03 08:15:25.102 D/DBINFO_MAWC_C2: Cast is Actor2 Movies are
Title is Star Wars Overview is Sci-Fi Poster is poster002
Title is Gone with the Wind Overview is Soppy Poster is poster003
2022-09-03 08:15:25.109 D/DBINFO_CAWM_M2: Movie is Star Wars cast are:
Name is Actor2 Profile is acts Character is an actor
Name is Actor4 Profile is acts Character is an actor
Name is Actor6 Profile is acts Character is an actor
2022-09-03 08:15:25.111 D/DBINFO_MAWC_C3: Cast is Actor3 Movies are
Title is The Good, the Bad and the Ugly Overview is Western Poster is posetr001
Title is Gone with the Wind Overview is Soppy Poster is poster003
2022-09-03 08:15:25.117 D/DBINFO_CAWM_M3: Movie is Gone with the Wind cast are:
Name is Actor1 Profile is acts Character is an actor
Name is Actor2 Profile is acts Character is an actor
Name is Actor3 Profile is acts Character is an actor
Name is Actor4 Profile is acts Character is an actor
Name is Actor5 Profile is acts Character is an actor
Name is Actor6 Profile is acts Character is an actor
Name is Actor7 Profile is acts Character is an actor
Maybe i have an understanding problem. I try to make 2 tabeles in one database. But additionaly i need to have some temporary values in one class that i doen´t want to write to the database.
I try to switch to peewee and read the dokumentation but i find no solution at my own.
without peewee i would make an init method where i write my attributes. But where did i have to write them now?
from peewee import *
import datetime
db = SqliteDatabase('test.db', pragmas={'foreign_keys': 1})
class BaseModel(Model):
class Meta:
database = db
class Sensor(BaseModel):
id = IntegerField(primary_key=True)
sort = IntegerField()
name = TextField()
#def __init__(self):
#self.sometemporaryvariable = "blabla"
def meineparameter(self, hui):
self.hui = hui
print(self.hui)
class Sensor_measure(BaseModel):
id = ForeignKeyField(Sensor, backref="sensorvalues")
timestamp = DateTimeField(default=datetime.datetime.now)
value = FloatField()
class Meta:
primary_key = CompositeKey("id", "timestamp")
db.connect()
db.create_tables([Sensor_measure, Sensor])
sensor1 = Sensor.create(id=2, sort=20, name="Sensor2")
#sensor1.sometemporaryvariable = "not so important to write to the database"
sensor1.save()
Remember to call super() whenever overriding a method in a subclass:
class Sensor(BaseModel):
id = IntegerField(primary_key=True)
sort = IntegerField()
name = TextField()
def __init__(self, **kwargs):
self.sometemporaryvariable = "blabla"
super().__init__(**kwargs)
So I have SQLite database using Slick and I want to add and remove tables from it.
Here is what I have now:
Here is the database element class:
class Data(tag: Tag)
extends Table[(Int, String)](tag, "myDB") {
// This is the primary key column:
def id = column[Int]("ID", O.PrimaryKey)
def name = column[String]("NAME")
// Every table needs a * projection with the same type as the table's type parameter
def * : ProvenShape[(Int, String)] = (id, name)
}
I need to be able to create multiple tables using the class above. Something like this:
def addTable(name:String){
db withSession { implicit session =>
val newTable = TableQuery[Data]
newTable.ddl.create
}
}
Problem is that I cant create new table because one already exists with name "myDB". I tried to add a parameter for the name of the Table in the class Data like so:
class Data(tag: Tag,tableName:String)
But then I couldn't create a table at all and got an error
unspecified value parameter tableName
And how can I query a specific table from the database given the table name?
I tried to Implement this using Map with table name pointing to a table, but it doesnt work because the Map is not saved anywhere and is reset everytime the program starts.
This is what I had for querying a table:
def getDataFromTable(tableName:String)
{
var res = ""
db withSession { implicit session =>
tables(tableName) foreach{
case (id,name)=>
res += id + " " + name + " "
}
}
res
}
Any help is appreciated!
Thanks!
Definition
class Data(tag: Tag, tableName: String)
extends Table[(Int, String)](tag, tableName){
...
Usage
(new TableQuery(Data(_,"table_1"))).ddl.create
(new TableQuery(Data(_,"table_2"))).ddl.create
...
I can't find a string result contained in a column...
here is the table:
object Equivalences extends Table[(Option[Int], String, String)]("EQUIVALENCES") {
def id = column[Int]("EQV_ID", O.PrimaryKey, O.AutoInc)
def racine = column[String]("RAC")
def definition = column[String]("DEF")
def * = id.? ~ racine ~ definition
}
and here is the wrong code:
def ajoute_si_racine_absente(racine_ajoutée: String, definition_ajoutée: String) = {
val cul = Query(Equivalences).filter(
equ => {
println(equ.racine)
equ.racine.toString.contains(racine_ajoutée)
})
if (cul.list().length == 0) {
Equivalences.insert(None, racine_ajoutée, definition_ajoutée)
}
}
the wrong code aims to insert a value if it does not exists, but the "println" within displays this result: "(EQUIVALENCES #409303125).RAC" and it does not match the column's content.
Maybe should I use the "getResult" method but I did not found any example on the net.
thanks.
Karol S is right. This does what you want:
def ajoute_si_racine_absente(racine_ajoutée: String, definition_ajoutée: String) = {
val cul = Query(Equivalences).list().filter(
equ => {
println(equ.racine)
equ.racine.toString.contains(racine_ajoutée)
})
if (cul.length == 0) {
Equivalences.insert(None, racine_ajoutée, definition_ajoutée)
}
}
But it may not be efficient, because you fetch the complete table. Slick is a query builder with a collection like API. Everything you write just resembles and builds up a query description until you finally call .listor .run. Only then the query is executed. Everything before are just placeholder objects representing tables, queries and columns. And the placeholder object for column racine prints as "(EQUIVALENCES #409303125).RAC".
I'm struggling with Slick's lifted embedding and mapped tables. The API feels strange to me, maybe just because it is structured in a way that's unfamiliar to me.
I want to build a Task/Todo-List. There are two entities:
Task: Each task has a an optional reference to the next task. That way a linked list is build. The intention is that the user can order the tasks by his priority. This order is represented by the references from task to task.
TaskList: Represents a TaskList with a label and a reference to the first Task of the list.
case class Task(id: Option[Long], title: String, nextTask: Option[Task])
case class TaskList(label: String, firstTask: Option[Task])
Now I tried to write a data access object (DAO) for these two entities.
import scala.slick.driver.H2Driver.simple._
import slick.lifted.MappedTypeMapper
implicit val session: Session = Database.threadLocalSession
val queryById = Tasks.createFinderBy( t => t.id )
def task(id: Long): Option[Task] = queryById(id).firstOption
private object Tasks extends Table[Task]("TASKS") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc)
def title = column[String]("TITLE")
def nextTaskId = column[Option[Long]]("NEXT_TASK_ID")
def nextTask = foreignKey("NEXT_TASK_FK", nextTaskId, Tasks)(_.id)
def * = id ~ title ~ nextTask <> (Task, Task.unapply _)
}
private object TaskLists extends Table[TaskList]("TASKLISTS") {
def label = column[String]("LABEL", O.PrimaryKey)
def firstTaskId = column[Option[Long]]("FIRST_TASK_ID")
def firstTask = foreignKey("FIRST_TASK_FK", firstTaskId, Tasks)(_.id)
def * = label ~ firstTask <> (Task, Task.unapply _)
}
Unfortunately it does not compile. The problems are in the * projection of both tables at nextTask respective firstTask.
could not find implicit value for evidence parameter of type
scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]
could not find implicit value for evidence parameter of type scala.slick.lifted.TypeMapper[scala.slick.lifted.ForeignKeyQuery[SlickTaskRepository.this.Tasks.type,justf0rfun.bookmark.model.Task]]
I tried to solve that with the following TypeMapper but that does not compile, too.
implicit val taskMapper = MappedTypeMapper.base[Option[Long], Option[Task]](
option => option match {
case Some(id) => task(id)
case _ => None
},
option => option match {
case Some(task) => task.id
case _ => None
})
could not find implicit value for parameter tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]]
not enough arguments for method base: (implicit tm: scala.slick.lifted.TypeMapper[Option[justf0rfun.bookmark.model.Task]])scala.slick.lifted.BaseTypeMapper[Option[Long]]. Unspecified value parameter tm.
Main question: How to use Slick's lifted embedding and mapped tables the right way? How to I get this to work?
Thanks in advance.
The short answer is: Use ids instead of object references and use Slick queries to dereference ids. You can put the queries into methods for re-use.
That would make your case classes look like this:
case class Task(id: Option[Long], title: String, nextTaskId: Option[Long])
case class TaskList(label: String, firstTaskId: Option[Long])
I'll publish an article about this topic at some point and link it here.