Imagine the following relation
One book consists of many chapters, a chapter belongs to exactly one book. Classical one to many relation.
I modeled it as this:
case class Book(id: Option[Long] = None, order: Long, val title: String)
class Books(tag: Tag) extends Table[Book](tag, "books")
{
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def order = column[Long]("order")
def title = column[String]("title")
def * = (id, order, title) <> (Book.tupled, Book.unapply)
def uniqueOrder = index("order", order, unique = true)
def chapters: Query[Chapters, Chapter, Seq] = Chapters.all.filter(_.bookID === id)
}
object Books
{
lazy val all = TableQuery[Books]
val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}
def add(order: Long, title: String) = all += new Book(None, order, title)
def delete(id: Long) = all.filter(_.id === id).delete
// def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
case class Chapter(id: Option[Long] = None, bookID: Long, order: Long, val title: String)
class Chapters(tag: Tag) extends Table[Chapter](tag, "chapters")
{
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def bookID = column[Long]("book_id")
def order = column[Long]("order")
def title = column[String]("title")
def * = (id, bookID, order, title) <> (Chapter.tupled, Chapter.unapply)
def uniqueOrder = index("order", order, unique = true)
def bookFK = foreignKey("book_fk", bookID, Books.all)(_.id.get, onUpdate = ForeignKeyAction.Cascade, onDelete = ForeignKeyAction.Restrict)
}
object Chapters
{
lazy val all = TableQuery[Chapters]
val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}
def add(bookId: Long, order: Long, title: String) = all += new Chapter(None, bookId, order, title)
def delete(id: Long) = all.filter(_.id === id).delete
}
Now what I want to do:
I want to query all or a specific book (by id) with all their chapters
Translated to plain SQL, something like:
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
but in Slick I can't really get this whole thing to work.
What I tried:
object Books
{
//...
def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
}
as well as:
object Books
{
//...
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
but to no avail. (I use ScalaTest and I get an empty result (for def withChapters(...)) or another exception for the val withChapters = for...)
How to go on about this? I tried to keep to the documentation, but I'm doing something wrong obviously.
Also: Is there an easy way to see the actual query as a String? I only found query.selectStatement and the like, but that's not available for my joined query. Would be great for debugging to see if the actual query was wrong.
edit: My test looks like this:
class BookWithChapters extends FlatSpec with Matchers with ScalaFutures with BeforeAndAfter
{
val db = Database.forConfig("db.test.h2")
private val books = Books.all
private val chapters = Chapters.all
before { db.run(setup) }
after {db.run(tearDown)}
val setup = DBIO.seq(
(books.schema).create,
(chapters.schema).create
)
val tearDown = DBIO.seq(
(books.schema).drop,
(chapters.schema).drop
)
"Books" should "consist of chapters" in
{
db.run(
DBIO.seq
(
Books.add(0, "Book #1"),
Chapters.add(0, 0, "Chapter #1")
)
)
//whenReady(db.run(Books.withChapters(books).result)) {
whenReady(db.run(Books.withChapters(1).result)) {
result => {
// result should have length 1
print(result(0)._1)
}
}
}
}
like this I get an IndexOutOfBoundsException.
I used this as my method:
object Books
{
def withChapters(id: Long) = Books.all.filter(_.id === id) join Chapters.all on (_.id === _.bookID)
}
also:
logback.xml looks like this:
<configuration>
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG/>
</configuration>
Where can I see the logs? Or what else do I have to do to see them?
To translate your query...
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
...to Slick we can filter the books:
val bookTenChapters =
Books.all.filter(_.id === 10L) join Chapters.all on (_.id === _.bookID)
This will give you a query that returns Seq[(Books, Chapters)]. If you want to select different books, you can use a different filter expression.
Alternatively, you may prefer to filter on the join:
val everything =
Books.all join Chapters.all on (_.id === _.bookID)
val bookTenChapters =
everything.filter { case (book, chapter) => book.id === 10L }
That will probably be closer to your join. Check the SQL generated with the database you use to see which you prefer.
You can log the query by creating a src/main/resources/logback.xml file and set:
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG"/>
I have an example project with logging set up. You will need to change INFO to DEBUG in the xml file in, e.g., the chapter-01 folder.
Related
considering the following pseudo code:
object EntityTable : Table("ENTITY") {
val uid = uuid("uid")
val idCluster = integer("id_cluster")
val idDataSchema = integer("id_data_schema")
val value = varchar("value", 1024)
override val primaryKey = PrimaryKey(uid, idCluster, idDataSchema, name = "ENTITY_PK")
}
var toBeFound = listOf(
EntityDTO(uid = UUID.fromString("4..9"), idCluster = 1, idDataSchema = 1),
EntityDTO(uid = UUID.fromString("7..3"), idCluster = 1, idDataSchema = 2),
EntityDTO(uid = UUID.fromString("6..2"), idCluster = 2, idDataSchema = 1)
)
fun selectManyEntity() : List<EntityDTO> {
val entityDTOs = transaction {
val queryResultRows = EntityTable.select {
(EntityTable.uid, EntityTable.idCluster, EntityTable.idDataSchema) // <-- every row for which the compound key combination of all three
inList
toBeFound.map {
(it.uid, it.idCluster, it.idDataSchema) // <-- has an element in 'toBeFound` list with the same compound key combination
}
}
queryResultRows.map { resultRow -> Fillers().newEntityDTO(resultRow) }.toList()
}
return entityDTOs
}
how do I have to write the query that it selects
all rows of EntityTable for which the compound primary key of (id, idCluster, idDataSchema)
is also contained in the given List supposed that every EntityDTO in the List<>
also has fields id, idCluster, idDataSchema) ???
if it helps: EntityDTO has hash() and equals() overloaded for exactly these three fields.
The only way is to make a compound expression like:
fun EntityDTO.searchExpression() = Op.build {
(EntityTable.uid eq uid) and (EntityTable.idCluster eq idCluster) and (EntityTable.idDataSchema eq idDataSchema)
}
val fullSearchExpression = toBeFound.map { it.searchExpression() }.compoundOr()
val queryResultRows = EntityTable.select(fullSearchExpression)
I am trying to create order amount from the catalogue which works like a shopping cart but the amount returned is 1 for all orders made:
views.py
def get_user_pending_order(request):
#get order from correct profile
user_profile = get_object_or_404(Profile,user=request.user)
order = Order.objects.filter(owner=user_profile,is_ordered=True)
if order.exists():
#to get an order in the list of filtered orders
return order[0]
return 0
def add_to_catalogue(request,employee_id):#product_id,employee_id
user_profile= get_object_or_404(Profile, user =request.user)
order_to_purchase = get_user_pending_order(request)
amount= self.order_to_purchase.get_catalogue_total(),
employee = Employee.objects.get(pk=employee_id)
if employee in request.user.profile.ebooks.all():
messages.info(request,'you already own this ebook')
return redirect(reverse('freelance:employee_list'))
order_task,status =
OrderTask.objects.get_or_create(employee=employee)
user_order,status = Order.objects.get_or_create(owner=user_profile,
is_ordered=False,order_amount=amount)####TThis IS WHWERE TO EDIT TO PREVENT
RE ORDERNG OF FREELANCING
user_order.tasks.add(order_task)
if status:
user_order.ref_code = generate_order_id()
user_order.save()
messages.info(request,"task added to catalogue")
return redirect(reverse('freelance:employee_list'))
def get_user_pending_order(request):
#get order from correct profile
user_profile = get_object_or_404(Profile,user=request.user)
order = Order.objects.filter(owner=user_profile,is_ordered=True)
if order.exists():
#to get an order in the list of filtered orders
return order[0]
return 0
models.py
class Order(models.Model):
ref_code = models.CharField(max_length=15)
owner = models.ForeignKey(Profile, on_delete=models.SET_NULL, null=
True)
is_ordered = models.BooleanField(default=False)
tasks = models.ManyToManyField(OrderTask)
date_ordered = models.DateTimeField(auto_now= True)
order_amount = models.DecimalField(default=0.01, max_digits= 10,
decimal_places=2)
def order_tasks(self):
return ','.join([str(c.employee) for c in self.tasks.all()])
def get_catalogue_tasks(self):
return self.tasks.all()
def get_catalogue_total(self):
return sum([task.employee.pricing for task in self.tasks.all()])
def __str__(self):
return '{0} - {1}'.format(self.owner, self.ref_code, self.order_amount)
def tasks_summary(request):
existing_order = get_user_pending_order(request)
my_user_profile = Profile.objects.filter(user=
request.user).first()
my_orders = Order.objects.filter(is_ordered= True, owner=
my_user_profile)
order_to_purchase = get_user_pending_order(request)
amount= order_to_purchase.get_catalogue_total(),
order = Order.objects.filter(is_ordered= True)
context = {
'my_orders':my_orders,
'order':order,
'amount':amount
# 'total':total,
}
return render(request,
'freelance/tasks_summary.html',context)###Belongs to the admin sisde
Output of the template
I am getting this error when I try to add anything to the catalogue:
AttributeError at /admin/tasks_summary/
'int' object has no attribute 'get_catalogue_total'
I have a table:
trait Schema {
val db: Database
class CoffeeTable(tag: Tag) extends Table[Coffee](tag, "coffees") {
def from = column[String]("from")
def kind = column[String]("kind")
def sold = column[Boolean]("sold")
}
protected val coffees = TableQuery[Coffee]
}
I want to update entries which are sold. Here is a method I end up with:
def markAsSold(soldCoffees: Seq[Coffee]): Future[Int] = {
val cmd = coffees
.filter { coffee =>
soldCoffees
.map(sc => coffee.from === sc.from && coffee.kind === sc.kind)
.reduceLeftOption(_ || _)
.getOrElse(LiteralColumn(false))
}
.map(coffee => coffee.sold)
.update(true)
db.db.stream(cmd)
}
While it works for a small soldCoffee collection, it badly fails with an input of hundreds of elements:
java.lang.StackOverflowError
at slick.ast.TypeUtil$$colon$at$.unapply(Type.scala:325)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.expr(JdbcStatementBuilderComponent.scala:311)
at slick.jdbc.H2Profile$QueryBuilder.expr(H2Profile.scala:99)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.$anonfun$expr$8(JdbcStatementBuilderComponent.scala:381)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.$anonfun$expr$8$adapted(JdbcStatementBuilderComponent.scala:381)
at slick.util.SQLBuilder.sep(SQLBuilder.scala:31)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.expr(JdbcStatementBuilderComponent.scala:381)
at slick.jdbc.H2Profile$QueryBuilder.expr(H2Profile.scala:99)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.$anonfun$expr$8(JdbcStatementBuilderComponent.scala:381)
at slick.jdbc.JdbcStatementBuilderComponent$QueryBuilder.$anonfun$expr$8$adapted(JdbcStatementBuilderComponent.scala:381)
at slick.util.SQLBuilder.sep(SQLBuilder.scala:31)
So the question is - is there another way to do such update?
What comes to my mind is using a plain SQL query or introduction of some artificial column holding concatenated values of from and type columns and filter against it.
lets say we have a custom class named orderFile and this class contains three properties.
class orderFile {
var name = String()
var id = Int()
var status = String()
}
a lot of them stored into an array
var aOrders : Array = []
var aOrder = orderFile()
aOrder.name = "Order 1"
aOrder.id = 101
aOrder.status = "closed"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "open"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "cancelled"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "confirmed"
aOrders.append(aOrder)
Question is: How will I sort them based on status according to open, confirm, close and cancelled?
You have to provide a value that will yield the appropriate ordering when compared in the sort function.
For example:
extension orderFile
{
var statusSortOrder: Int
{ return ["open","confirmed","closed","cancelled"].index(of: status) ?? 0 }
}
let sortedOrders = aOrders.sorted{$0.statusSortOrder < $1. statusSortOrder}
In your code you should make an array to store each aOrder with aOrders.append(aOrder) at each aOrder defination.
Then sort it with below code, refor this for more.
aOrders.sorted({ $0.status > $1.status })
The answer for swift 3 is as following
Ascending:
aOrders = aOrders.sorted(by:
{(first: orderFile, second: orderFile) -> Bool in
first.status > second.status
}
)
Descending:
aOrders = aOrders.sorted(by:
{(first: orderFile, second: orderFile) -> Bool in
first.status < second.status
}
)
OpenJPA and Scala question.
There are 2 tables in database (h2) - tasks and taskLists.
TaskList contains collection ArrayList.
Each TaskList can contain tasks from another TaskLists
When taskX modified in TaskList1 it have to be modified in TaskList2, if it contains taskX.
How to implement this?
I can add new task to task list and save it to DB, but when I try to add taskX from TaskList1 tasks collection to TaskList2 tasks collection and persist it, taskX is not persisted in db.
Example:
1. Adding task1 to taskList1 and task2 to taskList2 will result:
TaskList1: [task1]
TaskList2: [task2]
2. Adding task2 to taskList1 will result:
TaskList1: [task1]
3. Adding task3 to taskList1 and task4 to taskList2 will result:
TaskList1: [task1, task3]
TaskList2: [task2, task4]
What is wrong?
Task class:
import _root_.javax.persistence._
import org.apache.openjpa.persistence._
#Entity
#Table(name="tasks")
#ManyToMany
class CTask (name: String) {
def this() = this("new task")
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
var id: Int = _
var title: String = name
def getId = id
def getTitle = title
def setTitle(titleToBeSet: String) = title = titleToBeSet
override def toString: String = title
}
TaskList class:
import _root_.javax.persistence._
import _root_.java.util.ArrayList
import org.apache.openjpa.persistence._
#Entity
#Table(name="tasklists")
class CTaskList(titleText: String) {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
var id: Int = _
#Column(nullable = false)
var title: String = titleText
#ManyToMany
var tasks: ArrayList[CTask] = _;
var tags: String = ""
var rank: Int = 0
def getTasks = tasks
def this() = this("new task list")
def createTasks: ArrayList[CTask] = {
tasks = new ArrayList[CTask]
tasks
}
def getID = id
def getTags = tags
def setTags(tagsParam: String) = tags = tagsParam
def getRank = rank
def setRank(rankParam: Int) = rank = rankParam
def getTitle: String = title
def getTaskById(index: Int): CTask = {
null
}
def equals(anotherTaskList: CTaskList): Boolean = {
if (anotherTaskList == null) return false
if (anotherTaskList.getTasks == null)
return id == anotherTaskList.getID && title == anotherTaskList.getTitle && tags == anotherTaskList.getTags && rank == anotherTaskList.getRank && tasks == null;
else
return id == anotherTaskList.getID && title == anotherTaskList.getTitle && tags == anotherTaskList.getTags && rank == anotherTaskList.getRank && tasks != null && tasks.size == anotherTaskList.getTasks.size
}
def addTask(taskToBeAdded: CTask): Int = {
if (tasks == null)
tasks = new ArrayList[CTask]
tasks.add(taskToBeAdded)
tasks.indexOf(taskToBeAdded)}
def printTasks = println("TaskList's " + this + " tasks: " + tasks)
}
As far as I know, Many-To-Many association (as specified in your mapping annotations) implies association table between two entities (i.e. #JoinTable annotation should be specified).