How to query Slick with an optional foreign key to return all records with and without relationship? - slick

I have an optional foreign key defined on Event which goes to EventType. I want to query for all events, even those events which have a None (null) event type. This is the foreign key defined on Event.
def eventTypeId = column[Option[Long]]("event_type_id")
def eventType = foreignKey("event_type", eventTypeId, EventTypes.eventTypes)(_.id)
My initial query is as follows but it only returns those records which do have the foreign key set since the foreign key is optional. How?
(for {
p <- events
e <- p.eventType
} yield (p, e))
I wish to see all events with AND without the foreign key set.

It sounds like what you're asking for is for Slick to produce an outer join in that situation. I realize you've tagged this as a Slick 2 question, but the enhancement is slated for Slick 3.2: https://github.com/slick/slick/issues/179
In the meantime, you can manage the join yourself. For example:
events.leftJoin(eventTypes).on(_.eventTypeId === _.id).
map { case (e, et) => (e, et.name.?) }
... which will ultimately give you a result of Seq[(Event, Option[String])] (or similar, assuming you have a name that's a String).

Related

Use JOOQ Multiset with custom RecordMapper - How to create Field<List<String>>?

Suppose I have two tables USER_GROUP and USER_GROUP_DATASOURCE. I have a classic relation where one userGroup can have multiple dataSources and one DataSource simply is a String.
Due to some reasons, I have a custom RecordMapper creating a Java UserGroup POJO. (Mainly compatibility with the other code in the codebase, always being explicit on whats happening). This mapper sometimes creates simply POJOs containing data only from the USER_GROUP table, sometimes also the left joined dataSources.
Currently, I am trying to write the Multiset query along with the custom record mapper. My query thus far looks like this:
List<UserGroup> = ctx
.select(
asterisk(),
multiset(select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(new UserGroupMapper()))
Now my question is: How to create the UserGroupMapper? I am stuck right here:
public class UserGroupMapper implements RecordMapper<Record, UserGroup> {
#Override
public UserGroup map(Record rec) {
UserGroup grp = new UserGroup(rec.getValue(USER_GROUP.ID),
rec.getValue(USER_GROUP.NAME),
rec.getValue(USER_GROUP.DESCRIPTION)
javaParseTags(USER_GROUP.TAGS)
);
// Convention: if we have an additional field "datasources", we assume it to be a list of dataSources to be filled in
if (rec.indexOf("datasources") >= 0) {
// How to make `rec.getValue` return my List<String>????
List<String> dataSources = ?????
grp.dataSources.addAll(dataSources);
}
}
My guess is to have something like List<String> dataSources = rec.getValue(..) where I pass in a Field<List<String>> but I have no clue how I could create such Field<List<String>> with something like DSL.field().
How to get a type safe reference to your field from your RecordMapper
There are mostly two ways to do this:
Keep a reference to your multiset() field definition somewhere, and reuse that. Keep in mind that every jOOQ query is a dynamic SQL query, so you can use this feature of jOOQ to assign arbitrary query fragments to local variables (or return them from methods), in order to improve code reuse
You can just raw type cast the value, and not care about type safety. It's always an option, evne if not the cleanest one.
How to improve your query
Unless you're re-using that RecordMapper several times for different types of queries, why not do use Java's type inference instead? The main reason why you're not getting type information in your output is because of your asterisk() usage. But what if you did this instead:
List<UserGroup> = ctx
.select(
USER_GROUP, // Instead of asterisk()
multiset(
select(USER_GROUP_DATASOURCE.DATASOURCE_ID)
.from(USER_GROUP_DATASOURCE)
.where(USER_GROUP.ID.eq(USER_GROUP_DATASOURCE.USER_GROUP_ID))
).as("datasources").convertFrom(r -> r.map(Record1::value1))
)
.from(USER_GROUP)
.where(condition)
.fetch(r -> {
UserGroupRecord ug = r.value1();
List<String> list = r.value2(); // Type information available now
// ...
})
There are other ways than the above, which is using jOOQ 3.17+'s support for Table as SelectField. E.g. in jOOQ 3.16+, you can use row(USER_GROUP.fields()).
The important part is that you avoid the asterisk() expression, which removes type safety. You could even convert the USER_GROUP to your UserGroup type using USER_GROUP.convertFrom(r -> ...) when you project it:
List<UserGroup> = ctx
.select(
USER_GROUP.convertFrom(r -> ...),
// ...

Closing sequence of inserts (that return autoinc id) as a single DBIOAction

TL;DR: I want to create a pure function that will return a sequence of insert operations closed inside a single DBIOAction object (e.g def foo(): DBIOAction). This is trivial when IDs are NOT auto-incremented, it gets difficult for me when ID's ARE auto-incremented by DB).
I've been using Slick for more then a week now and what I really like about it is ability to close sequence of operations as a DBIOAction, that can be returned by my function and later can be applied to DB as a side effect.
That worked all fine when I was inserting data to a single table. But how should I deal with situation when an ID of one inserted row must be used in a second row that I'm inserting (like any 1-many relation), given the fact that IDs are auto-incremented?
Imagine we have a CATEGORIES table with columns: id, name, parent_id, where parent_id is either NULL or points to other row within the table.
If the IDs were not auto-incremented, this is trivial:
def insert(cId: Int, cName: String, subcId: Int, subcName: String) =
DBIO.seq(
categories +=(subcId, subcName, None),
categories +=(cId, cName, Some(subcId))
)
But when ID ARE auto-incrememnted, this get's tricky. As stated in the documentation, I need to use returning combined with projection to column that needs to be returned, like:
val categoryId =
(categories returning categories.map(_.id)) += (0, name, None)
Now I can use returned ID when inserting the second row. Awesome. But then how can I close sequences of DB actions, since I am returned with the ID, but not the action itself?
def insert(cName: String, subcName: String): DBIOAction[Int, Stream, Write] = ???
So I will answer my own question as somebody might find it weird at first as I did.
val categoryId =
(categories returning categories.map(_.id)) += (0, name, None)
Trick is that categoryId is also a DBIOAction, not an Int as I originally expected it to be. Trivial, but there you go.
This is how I would do (I've done something similar in a project of mine):
def insert(cName: String, subcName: String) = {
for {
id <- (categories returning categories.map(_.id)) += (subcName, None)
_ <- categories +=(cName, Some(id))
} yield ()
and then I'd call the function like:
db.run(insert(categoryName, subCategoryName))

How to convert Rep[T] to T in slick 3.0?

I used a code, generated from slick code generator.
My table has more than 22 columns, hence it uses HList
It generates 1 type and 1 function:
type AccountRow
def AccountRow(uuid: java.util.UUID, providerid: String, email: Option[String], ...):AccountRow
How do I write compiled insert code from generated code?
I tried this:
val insertAccountQueryCompiled = {
def q(uuid:Rep[UUID], providerId:Rep[String], email:Rep[Option[String]], ...) = Account += AccountRow(uuid, providerId, email, ...)
Compiled(q _)
}
I need to convert Rep[T] to T for AccountRow function to work. How do I do that?
Thank you
;TLDR; Not possible
Explanation
There are two levels of abstraction in Slick: Querys and DBIOActions.
When you're dealing with Querys, you have to access your schema definitions, and rows, Reps and, basically, it's very constrained as it's the closest level of abstraction to the actual DB you're using. A Rep refers to an hypothetical value in the database, not in your program.
Then you have DBIOActions, which are the next level... not just some definition of a query, but the execution of it. You usually get DBIOActions when getting information out of a query, like with the result method or (TADAN!) when inserting rows.
Inserts and Updates are not queries and so what you're trying to do is not possible. You're dealing with DBIOAction (the += method), and Query stuff (the Rep types). The only way to get a Rep inside a DBIOAction is by executing a Query and obtaining a DBIOAction and then composing both Actions using flatMap or for comprehensions (which is the same).

Get the metatable for foreign key

I'm working on a method that received an EF MetaTable object and I need to return a list of MetaTable objects for all the related tables. This means both the child tables that have a foreign key to this one and the tables that the foreign keys of this table are pointing to. The problem? I only have an object of type MetaTable.
So, how do I solve this?
Solved it...
var parents = table.Columns.OfType<MetaForeignKeyColumn>().Select(s => s.ParentTable).Distinct();
var children = table.Columns.OfType<MetaChildrenColumn>().Select(s => s.ChildTable).Distinct();
Wasn't too difficult once you realise this typecast trick.

GORM - Update object without retrieving it first

I'd like to be able to update a previously persisted object for which I have an id without having to retrieve it first. The main thing that I'm trying to avoid is having to copy multiple values into the object's fields when that object has been retrieved from the database. I have these values in a map with keys corresponding to the field names so it's trivial to create the object via a constructor with the map as an argument. Unfortunately, an object created this way results in a new database record when saved even though the id field is set to that of an existing record.
I'm currently using a slight variation on one of the examples shown here for copying Groovy class properties but it's not a very elegant solution for multiple reasons.
Basically I'd like to be able to do something like this:
class Foo {
int a
String b
}
def data = [id: 99, a: 11, b: "bar"] //99 is the id of an existing record
def foo = new Foo(data)
foo.update() //or some other comparable persistence mechanism
Thanks
As long as your map keys have the same name as your object properties, you can use executeUpdate without specifying the individual property names with a closure or function like the following:
def updateString = { obj, map ->
def str = ""
map.each { key, value ->
str += "${obj}.${key}=:${key},"
}
return str[0..-2]
}
def data= [foo:"bar", machoMan:"RandySavage"]
In this case, println updateString("f", data) returns "f.foo=:foo,f.machoMan=:machoMan".
Then you can do this:
Foo.executeUpdate("update Foo f set ${updateString("f", data)}", data)
Or of course you could combine that all together into one closure or function.
You can use the executeUpdate method on the GORM domain class:
Foo.executeUpdate("update Foo f set f.a=:a, f.b=:b where f.id=:id", data)

Resources