Is it possible in jOOQ to use an adhoc converter for converting nested row-value expressions?
I'm already using an adhoc converter for smaller degree mappings:
row(
TABLE_A.relation1().FIRST_NAME,
TABLE_A.relation1().LAST_NAME,
).mapping { firstname, lastname -> listOfNotNull(firstname, lastname).joinToString(" ") },
However, I would like something similar for higher degree mappings:
row(
TABLE_B.FIRST_FIELD,
...
TABLE_B.TWENTYTHIRD_FIELD,
).mapping { record -> custom converter logic, e.g. MyDataClass(record[TABLE_B.TWENTYTHIRD_FIELD]) }
Is it possible to project these type of row value expressions? I'm using jOOQ 3.16.
In jOOQ 3.15 - 3.16, there has been a missing RowN::mapping method which has been added to 3.17 with #12515.
As a workaround, you can use auxiliary nested records to avoid projecting all of your columns, e.g.:
row(
row(
TABLE_A.relation1().FIRST_NAME,
TABLE_A.relation1().LAST_NAME
).mapping { f, l -> listOfNotNull(f, l).joinToString(" ") },
...
).mapping { rest -> ... }
Or, alternatively, move some of that logic to SQL. Specifically that joinToString(" ") method is just DSL.concat():
row(
concat(
TABLE_A.relation1().FIRST_NAME,
inline(" "),
TABLE_A.relation1().LAST_NAME
),
...
).mapping { rest -> ... }
Or, finally, do this (which is what these Row[N].mapping(...) methods are just convenience for):
field(row(
TABLE_A.relation1().FIRST_NAME,
TABLE_A.relation1().LAST_NAME,
...
)).convertFrom(r -> ...)
Related
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 -> ...),
// ...
Disclaimer: First I have to say that I have a different background - Java/Scala and Typescript is definitely not my biggest strength.
I have two fairly complex classes and pre-defined contract which maps each property from class class A to class B. I have to convert instance of class A to class B and vice versa on demand. I know this could be done with a static mapper which does the translation in code, but I was wondering if there are some packages/techniques which could easy my pain. I already checked https://github.com/typestack/class-transformer, but it doesn't seem to cover everything I want.
Requirements:
Some array fields from one class could be transformed or flattened before mapped to the other class.
Class A is a simple {[key:string], string} map, while B's fields are more complex and type inferred from the contract.
Key in class A might be mapped to key Y in class B.
An example A->B:
class A {
"key1" = "1234"
"key2" = "text"
"key3" = "10/07/2021"
"key5" = "otherText"
}
and after conversion
class B {
"newKey1" = 1234,
"newKey2" = "text",
"newKey3" = Date(..),
"key4": {
"key5": "otherText"
}
Any tips, suggestions will be very much appreciated.
Question
Considering these classes:
class BookCase { ArrayList<Book> books }
class Book { ArrayList<Page> pages }
class Page { String color }
And considering this natural language rule:
When all pages in a bookcase are black, do A
The trivial approach would be to nest forall clauses, but in Drools can't do that, because forall clauses only allow Patterns (not Conditional Elements, what a forall clause is) inside!
How do I express this in Drools then?
This is close, but not quite the right thing:
rule "all black pages"
when
BookCase( $books: books )
$book: Book( $pages: pages ) from $books
not Page( color != "black" ) from $pages
then
System.out.println( "doing A" );
end
The problem is that this will fire once for each book where all pages are black. To assess all pages in all books one could assemble a list of all pages and make sure they are all black:
rule "all black pages, take 2"
when
BookCase( $books: books )
$pages: ArrayList() from accumulate( Book( $ps: pages ) from $books,
init( ArrayList list = new ArrayList(); ),
action( list.addAll( $ps ); ),
result( list ) )
not Page( color != "black" ) from $pages
then
System.out.println( "doing A" );
end
You can actually nest multiple foralls if you don't use forall(p1 p2 p3...), but the equivalent not(p1 and not(and p2 p3...)). Then, to keep individual books and pages from firing the rule, chuck an exists in between there.
rule 'all pages in bookcase are black'
when
(and
$bookCase: BookCase()
(not (exists (and
$book: Book() from $bookCase.books
not( (and
not( (exists (and
$page: Page() from $book.pages
not( (and
// slightly different constraint than I used in question
eval($page.color == $book.color)
) )
) ) )
) )
) ) )
)
then
...
end
Unlike using accumulate to create a flat list of all pages, this will maintain the context of $page, that is, when a page is put in a constraint with its parent book as in the example above, in this solution Drools still 'knows' what the parent book is.
While I was writing the question, I think I found the answer myself:
BookCase($caseContents : books)
$bookWithOnlyBlackPages : ArrayList<Page>() from $caseContents
forall ( $page : Page(this memberOf $bookWithOnlyBlackPages)
Page(this == $page,
color == "black") )
forall ( $bookInCase : ArrayList<Page>(this memberOf $caseContents)
ArrayList<Page>(this == $bookInCase,
this == $bookWithOnlyBlackPages) )
I was trying to make my model strings to be always uppercase, so I tried to use converter and reverseConverter to do this job. Using the mutual option I got hitted by this topic.
Witch leads to my question. Is this the right use case to converter? Without the mutual the value was converted only in the model, and didn't update the view.
Can I use the DocumentFilter approach?
Example
textField(columns: 3, text: bind("score", target: m,
converter: { it.isEmpty()? 0: Integer.parseInt(it) },
reverseConverter: { String.valueOf(it) },
mutual: true))
I'm afraid converter: and reverseConverter: only work with unidirectional bindings at the moment.
I am trying to retrieve all direct indirect method calls for all methods in an assembly using the CQL provided by nDepend.
Issue is I am not able to iterate through all methods inside a assembly to get this info.
The DepthOfIsUsedBy only allows a string type and not a collection of strings.
Is there a way t get this info for all methods inside an assembly?
What about using the method DepthOfIsUsing() instead of DepthOfIsUsedBy() :o)
from m in Assemblies.WithNameNotIn("nunit.uikit").ChildMethods()
let depth0 = m.DepthOfIsUsing("nunit.uikit")
where depth0 >= 0 orderby depth0
select new { m, depth0 }
This query has been generated through the menu below. (Btw a more sophisticated solution could be elaborated with the magic method FillIterative(), but it is not necessary here).
Taking account the comment of Prasad, what about trying this query that list all direct & indirect callers from A, for each method of B:
from m in Assemblies.WithNameIn("AsmB").ChildMethods()
where m.IsPubliclyVisible // Optimization
let indirectcallers = m.MethodsCallingMe
.FillIterative(
callers => callers.SelectMany(m1 => m1.MethodsCallingMe))
.DefinitionDomain
.Where(m1 => m1.ParentAssembly.Name == "AsmA")
.ToArray() // Avoid double enumeration
where indirectcallers.Length > 0
orderby indirectcallers.Length descending
select new { m, indirectcallers }