How can I call this jOOQ generated function taking a custom bound type? - jooq

I originally had the following SQL function:
CREATE FUNCTION resolve_device(query JSONB) RETURNS JSONB...
and the following code calling the method generated by jOOQ:
final JsonArray jsonArray = jooqDWH.select(resolveDevice(queryJson)).fetchOne().value1().getAsJsonArray();
final JsonObject o = jsonArray.get(0).getAsJsonObject();
This worked fine. I needed to return a real device object rather than a JSON blob though, so I changed the SQL function to:
CREATE FUNCTION resolve_device(query JSONB) RETURNS SETOF device...
and the code to:
final ResolveDeviceRecord deviceRecord = jooqDWH.fetchOne(resolveDevice(queryJson));
but I am getting a runtime error:
org.jooq.exception.SQLDialectNotSupportedException: Type class com.google.gson.JsonElement is not supported in dialect DEFAULT
Many other parts of my code continue to work fine with the custom binding I have converting JsonElement to JSONB, but something about the change to this function's signature caused it to stop working.
I tried a few different variants of DSL.field() and DSL.val() to try to force it to be recognized but have not had any luck so far.

This could be a bug in jOOQ or a misconfiguration in your code generator. I'll update my answer once it is clear what went wrong.
Workaround:
Meanwhile, here's a workaround using plain SQL:
// Manually create a data type from your custom JSONB binding first:
final DataType<JsonObject> jsonb = SQLDataType.OTHER.asConvertedDataType(jsonBinding);
// Then, create an explicit bind variable using that data type:
final ResolveDeviceRecord deviceRecord =
jooqDWH.fetchOptional(table("resolve_device({0})", val(queryJson, jsonb)))
.map(r -> r.into(ResolveDeviceRecord.class))
.orElse(null);

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 -> ...),
// ...

Error "Failed to construct URI for OData request with request path" SAP Java VDM withQueryParameter

I have create a CAP VDM in java with SAP SDK 3.29.1.
In event handler #On read, i would like to pass the input filters to the VDM.
Es.
#On(event = CdsService.EVENT_READ, entity = "XXXX")
public void readXXX(CdsReadEventContext context) throws ODataException {
final String filter = context.getParameterInfo().getQueryParameter("$filter");
final List<XXXXX> dati = new DefaultXXXService().getAllXXXX().select().withQueryParameter("$filter", filter).executeRequest(dest);
}
When run the query in the log i get the error:
"Failed to construct URI for OData request with request path ..."
"Illegal character in query at index 95. ...."
But the path and filter is right for the call, can you help me?
In the old version sdk i used:
FilterExpression filtriFrontEnd =FilterExpressionConverter.convertTo(queryRequest.getQueryExpression());
final List<XXXX> area = new DefaultXXXXService().getAllXXX().filter(new UncheckedFilterExpression<>(filtriFrontEnd)).select().execute(new ErpConfigContext("XXX"));
Thank you.
When run the query in the log i get the error: "Failed to construct URI for OData request with request path ..." "Illegal character in query at index 95. ...."
Can you check whether the following works for you..?
import com.sap.cloud.sdk.datamodel.odata.client.request.ODataUriFactory;
final String filter = context.getParameterInfo().getQueryParameter("$filter");
final String encodedFilter = ODataUriFactory.encodeQuery(filter);
final List<XXXXX> dati = new DefaultXXXService().getAllXXXX().select().withQueryParameter("$filter", encodedFilter ).executeRequest(dest);
Please let me know, as this could be an issue that needs to be addressed.
The method contract of withQueryParameter which explicitly warns about this API usage:
Using this function to bypass fluent helper method calls can lead to unsupported response handling. There is no contract on the order or priority of parameters added to the query.
Instead please use the filter function directly. There should be an entity field for the postal code, the code should look similar to this:
new DefaultXXXService()
.getAllXXXX()
.filter(Entity.POSTAL_CODE.eq("16100"))
If this is not feasible in your use case I suggest to build this filter expression manually as you did before and apply encoding to it as Alex pointed out in his answer.

Retrieving Properties from DbSqlQuery

Background: Project is a Data Import utility for importing data from tsv files into a EF5 DB through DbContext.
Problem: I need to do a lookup for ForeignKeys while doing the import. I have a way to do that but the retrieval if the ID is not functioning.
So I have a TSV file example will be
Code Name MyFKTableId
codevalue namevalue select * from MyFKTable where Code = 'SE'
So when I process the file and Find a '...Id' column I know I need to do a lookup to find the FK The '...' is always the entity type so this is super simple. The problem I have is that I don't have access to the properties of the results of foundEntity
string childEntity = column.Substring(0, column.Length - 2);
DbEntityEntry recordType = myContext.Entry(childEntity.GetEntityOfReflectedType());
DbSqlQuery foundEntity = myContext.Set(recordType.Entity.GetType()).SqlQuery(dr[column])
Any suggestion would be appreciated. I need to keep this generic so we can't use known type casting. The Id Property accessible from IBaseEntity so I can cast that, but all other entity types must be not be fixed
Note: The SQL in the MyFKTableId value is not a requirement. If there is a better option allowing to get away from SqlQuery() I would be open to suggestions.
SOLVED:
Ok What I did was create a Class called IdClass that only has a Guid Property for Id. Modified my sql to only return the Id. Then implemented the SqlQuery(sql) call on the Database rather than the Set([Type]).SqlQuery(sql) like so.
IdClass x = ImportFactory.AuthoringContext.Database.SqlQuery<IdClass>(sql).FirstOrDefault();
SOLVED:
Ok What I did was create a Class called IdClass that only has a Guid Property for Id. Modified my sql to only return the Id. Then implemented the SqlQuery(sql) call on the Database rather than the Set([Type]).SqlQuery(sql) like so.
IdClass x = ImportFactory.AuthoringContext.Database.SqlQuery<IdClass>(sql).FirstOrDefault();

return codes for Jira workflow script validators

I'm writing a workflow validator in Groovy to link two issues based on a custom field value input at case creation. It is required that the custom filed value to Jira issue link be unique. In other words, I need to ensure only one issue has a particular custom field value. If there is more than one issue that has the input custom field value, the validation should fail.
How or what do I return to cause a workflow validator to fail?
Example code:
// Set up jqlQueryParser object
jqlQueryParser = ComponentManager.getComponentInstanceOfType(JqlQueryParser.class) as JqlQueryParser
// Form the JQL query
query = jqlQueryParser.parseQuery('<my_jql_query>')
// Set up SearchService object used to query Jira
searchService = componentManager.getSearchService()
// Run the query to get all issues with Article number that match input
results = searchService.search(componentManager.getJiraAuthenticationContext().getUser(), query, PagerFilter.getUnlimitedFilter())
// Throw a FATAL level log statement because we should never have more than one case associated with a given KB article
if (results.getIssues().size() > 1) {
for (r in results.getIssues()) {
log.fatal('Custom field has more than one Jira ssue associated with it. ' + r.getKey() + ' is one of the offending issues')
}
return "?????"
}
// Create link from new Improvement to parent issue
for (r in results) {
IssueLinkManager.createIssueLink(issue.getId(), r.getId(), 10201, 1, getJiraAuthenticationContext().getUser())
}
try something like
import com.opensymphony.workflow.InvalidInputException
invalidInputException = new InvalidInputException("Validation failure")
this is based of the groovy script runner. If it doesn't work for you, i would recommend you using some sort of framework to make scripting easier, I like using either groovy script runner , Jira Scripting Suite or Behaviours Plugin
. All of them really makes script writing easier and much more intuitive.

Usage of SQL Server function in SqlExpression in Update query

In SubSonic, version 2.2, the following (MSSQL-specific) code fails:
SqlQuery update =
new Update(SomeTable)
.SetExpression(SomeTable.SomeDateTimeColumn).IsEqualTo("GETDATE()")
.Where(SomeTable.IdColumn).IsEqualTo(id);
At this point update.ToString() produces a perfectly legal SQL sentence:
UPDATE [dbo].[SomeTable] SET [SomeDateTime]=GETDATE()
WHERE [dbo].[SomeTable].[ID] = #ID0
update.Execute() however fails with:
{"Failed to convert parameter value from a String to a DateTime."}
at SubSonic.Update.Execute()
Is there any possibility to use sql server functions in expressions?
Ok, I've found a workaround - it is possible to use SQL Server functions outside of InlineQuery. The trick is that you must not use "strongly typed" version of SetExpression that uses TableColumn parameter, but pass column name strings, like this:
SqlQuery update =
new Update(SomeTable)
.SetExpression(SomeTable.Columns.SomeDateTime).IsEqualTo("GETDATE()")
.Where(SomeTable.IdColumn).IsEqualTo(id);
The important part being: SomeTable.Columns.SomeDateTime instead of SomeTable.SomeDateTimeColumn.
For the specific example you've posted you could just do the following:
SqlQuery update = new Update(SomeTable)
.SetExpression(SomeTable.SomeDateTimeColumn).IsEqualTo(DateTime.Now)
.Where(SomeTable.IdColumn).IsEqualTo(id);

Resources