graphql - Combining results from multiple resolvers into one - merge

I have a set of functions at the server side which each return a list of objects of the same type based on the passed parameters to the resolvers in the GraphQL query-
query {
objListQuery {
objResolver1(params) {
obj-id
}
objResolver2(different params) {
obj-id
}
...
}
}
Here, objResolver1 and objResolver2 send back a list of obj objects.
Server side -
function objResolver1(params) -> returns list of obj
function objResolver2(different params) -> returns list of obj
...
I want to perform a logical AND between the results of the resolvers that is, find out the common objects in the results of the different resolvers.
Instead of getting the individual lists, I only want the combined list.
One way is to aggregate the results at the client side but this will increase the amount of duplicated data sent by the server.
What is the best way to achieve this at the server side? What changes are required in the schema?
--------------------EDIT--------------------
The data source is a JSON array of obj objects which is obtained from an external service at the server. Data source is not a database.
Parameters in each resolver can be one or many. It is used for filtering the objects. For example, the data store will have the structure as:
[
{"dateCreated":"2011-08-12T20:17:46.384Z",
"type":"customer",
....
},
{"dateCreated":"2011-08-14T20:17:46.384Z",
"type":"test",
....
}
]
resolvers will be of the form:
dateResolver(String startDate, String endDate) -> returns list of obj whose dateCreated is within the range
typeResolver(String[] type) -> returns list of obj whose type is anyone of the values passed in the array.

Assumed you're using a database you're somehow asking how to shift constraints from database- or repository-layer on controller-level.
While this has some weakness on model-level perhaps, it might depend on the class-implementation if you can easily change the objResolver in the kind that you just build one that allows more parameters like this:
query {
objListQuery {
objResolver(params1, params2, constraint) {
...
}
}
}
Like this you could create a database-query that is directly fetching the right result or you can perform several queries and resolve them inside the objResolver. If the constraint is always AND you could leave the parameter away, but perhaps you like to offer the possibility to use also OR, XOR, or others.
If the amount of parameter-sets is always 2, then it's simple like my code above, also considering the optional constraint. If the amount of parameter-sets might be variable, i.e. 4 or 5, then it's getting complicated if you still want to offer the constraint-parameter(s). Without constraint-parameter(s) it's simple, you just could note the function without parameters but check for the amount of parameters in the caller and handle them accordingly, in the caller you just use so many parameters as required.
query {
objListQuery {
objResolver() {
paramArray = getArguments();
}
}
}
Like written above it's getting hard, if you still want to offer constraint-parameters here, but I'd suggest that would be material for another question.

You can implement a Connection interface, with a single resolver to allow a one-step querying mechanism. You can reduce query endpoints using this technique.
E.g, an example query would look like:
allObjects(start: "01-01-2019", end: "04-29-2019", types:["test", "sales"]){
nodes {
id,
dateCreated,
type
}
}
In the resolver, you can use this criteria to prepare and return the data.
Benefits:
Less query endpoints.
Filtering and pagination.
Your filter interface can be quite fancy:
allObjects(
dateCreated: {
between:{
start,
end
},
skipWeekends: true
},
types: {
include:[],
exclude: []
}
)
Add new criteria as your needs grow. Start with what you want and take it from there.

Related

OrientDB Server Side JavaScript Function: Iterating over array (arraylist)

OrientDB Version: 3.2.6
I wish to create an OrientDB server-side function which zips two arrays into an object, but also format the values (which are always record ids) slightly.
Function name:
idToRidMap
Takes args:
keys - An array of strings representing the keys of the resulting object, Example: ["id1", "id2"]
values - An array of record ids, Example: [#15:1, #15:2]
Returns an object with the id array elements as keys and the record ids as string values without leading "#", i.e. from examples above it would be:
{
"id1": "15:1",
"id2": "15:2"
}
I wish to use this function in queries like this one:
SELECT idToRidMap(configIds, configRids) as configs, * FROM (
SELECT outE("HasConfig").id as configIds, out("HasConfig").#rid as configRids, * FROM 12:0
)
So, depending on the id of the linking Edges and the record-id of the linked Vertices I wish to build one property showing all those relations in the returned record:
{
"#rid": "#12:0",
"someNativeProp": "Hello",
"configs": {
"id1": "15:1",
"id2": "15:2"
},
...
}
Note though, that this would also require me to drop the projections for the intermediate array results as well, extending the query to be something like this:
SELECT idToRidMap(configIds, configRids) as configs, !configIds, !configRids, * FROM (
SELECT outE("HasConfig").id as configIds, out("HasConfig").#rid as configRids, * FROM 12:0
)
The OrientDB JS function definition I've tried (among many others) is:
var result = {};
for (i = 0; i < keys.length; i++) {
result[keys[i]] = String(values[i]).replace('#', '');
}
return result;
But I realized that length is not available (it is undefined) on the keys argument. When testing by using keys.size() (guessing it was java.util.arraylist) I was given an error:
com.orientechnologies.orient.core.command.script.OCommandScriptException: Error on parsing script at position #0: Error on execution of the script Script: idToRidMap ------^ DB name="test" --> javax.script.ScriptException: org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (size) on java.util.ArrayList#1b395482 failed due to: Unknown identifier: size --> org.graalvm.polyglot.PolyglotException: TypeError: invokeMember (size) on java.util.ArrayList#1b395482 failed due to: Unknown identifier: size
Which seems to indicate that it has something to do with graalvm polyglot and that it indeed has to do with java.util.ArrayList. I did check https://www.graalvm.org/22.1/reference-manual/js/JavaInteroperability/#access-java-from-javascript but I'm not sure how relevant it is and I didn't find anything that helped me.
So, to sum up. Basically there's two parts to this question:
Is there any documentation of how the JavaScript server-side functions work type-wise and syntax-wise, etc? It seems really picky to what kind of JavaScript I write as well. How can I do the desired iterations to implement my function?
Is there a better way of achieving my end goal?
Thankful for any insight, I've always had a hard time with OrientDB custom functions.

Getting ElasticSearch document fields inside of loaded records in searchkick

Is it possible to get ElasticSearch document fields inside of loaded AR records?
Here is a gist that illustrates what I mean: https://gist.github.com/allomov/39c30905e94c646fb11637b45f43445d
In this case I want to avoid additional computation of total_price after getting response from ES. The solution that I currently see is to include the relationship and run total_price computation for each record, which is not so optimal way to perform this operation, as I see it.
result = Product.search("test", includes: :product_components).response
products_with_total_prices = result.map do |product|
{
product: product
total_price: product.product_components.map(&:price).compact.sum
}
end
Could you please tell if it is possible to mix ES document fields into AR loaded record?
As far as I'm aware it isn't possible to get a response that merges the document fields into the loaded record.
Usually I prefer to completely rely on the data in the indexed document where possible (using load: false as a search option), and only load the AR record(s) as a second step if necessary. For example:
result = Product.search("test", load: false).response
# If you also need AR records, could do something like:
product_ids = result.map(&:id)
products_by_id = {}
Product.where(id: product_ids).find_each do |ar_product|
products_by_id[ar_product.id] = ar_product
end
merged_result = result.map do |es_product|
es_product[:ar_product] = products_by_id[es_product.id]}
end
Additionally, it may be helpful to retrieve the document stored in the ES index for a specific record, which I would normally do by defining the following method in your Product class:
def es_document
return nil unless doc = Product.search_index.retrieve(self).presence
Hashie::Mash.new doc
end
You can use select: true and the with_hit method to get the record and the search document together. For your example:
result = Product.search("test", select: true)
products_with_total_prices =
result.with_hit.map do |product, hit|
{
product: product,
total_price: hit["_source"]["total_price"]
}
end

Live Update the Number of Items

I have a requirement where I need to live update the number of list items to Page's sub-header. I want use sap.ui.base.EventProvider, aggregation binding, or expression binding. Please walk me through as I have never used it before.
If I delete a list item, the number of list item should live update.
Client-side Models
If a client-side model such as JSONModel is used (i.e. assuming all the data are already available on the client) and if the target collection is an array, a simple expression binding is sufficient:
title="{= ${myJSONModel>/myProducts}.length}"
Sample 1 (ClientModel): Updating count after deleting items from sap.m.Table
Sample 2 (ClientModel): Updating count after filtering sap.ui.table.Table
As you can see in the above samples, when the number of items changes, the framework notifies the Expression Binding which eventually updates the property value automatically.
Server-side Models (e.g. OData)
OData V2
Using updateFinished event from sap.m.ListBaseapi
Especially if the growing feature is enabled, this event comes in handy to get always the new count value which the framework assigns to the event parameter total.
[The parameter total] can be used if the growing property is set to true.
Sample 3 (v2.ODataModel): Updating count after filtering sap.m.ListBase
<List
growing="true"
items="{/Products}"
updateFinished=".onUpdateFinished"
>
onUpdateFinished: function(event) {
const reason = event.getParameter("reason"); // "Filter", "Sort", "Refresh", "Growing", ..
const count = event.getParameter("total"); // Do something with this $count value
// ...
},
The updateFinished event is fired after items binding is updated and processed by the control. The event parameter "total" provides the value of $count that has been requested according to the operation such as filtering, sorting, etc..
Using change event from sap.ui.model.Bindingapi
This event can be applied to any bindings which comes in handy especially if the control doesn't support the updateFinished event.
someAggregation="{
path: '/Products',
events: {
change: '.onChange'
}
}"
onChange: function(event) {
const reason = event.getParameter("reason"); // See: sap.ui.model.ChangeReason
const count = event.getSource().getLength();
// ...
},
event.getSource() returns the corresponding (List)Binding object which has the result of $count (or $inlinecount) stored internally. We can get that count result by calling the public API getLength().
One downside is that there is no "growing" reason included in sap.ui.model.ChangeReason. But if the control can grow, it's probably derived from the ListBase anyway which supports the updateFinished event.
Manual trigger (Only in V2)
If there is no list binding at all but the count value is still required, we can always send a request manually to get the count value. For this, append the system query $count to the path in the read method:
myV2ODataModel.read("/Products/$count", {
filters: [/*...*/],
success: function(data) {
const count = +data; // "+" parses the string to number.
// ...
}.bind(this),
})
OData V4
Please, take a look at the documentation topic Binding Collection Inline Count.
I will guess that "MyListModel" is your model name and inside it you have something like this:
[
{objectName: "object1"},
{objectName: "object2"},
{objectName: "object3"}
]
Then try:
<Page title="{= ${myListModel>/}.length}">

CouchDB Map/Reduce view query from Ektorp

I'm trying to execute a query from java against a Map/Reduce view I have created on the CouchDB.
My map function looks like the following:
function(doc) {
if(doc.type == 'SPECIFIC_DOC_TYPE_NAME' && doc.userID){
for(var g in doc.groupList){
emit([doc.userID,doc.groupList[g].name],1);
}
}
}
and Reduce function:
function (key, values, rereduce) {
return sum(values);
}
The view seems to be working when executed from the Futon interface (without keys specified though).
What I'm trying to do is to count number of some doc types belonging to a single group. I want to query that view using 'userID' and name of the group as a keys.
I'm using Ektorp library for managing CouchDB data, if I execute this query without keys it returns the scalar value, otherwise it just prints an error saying that for reduce query group=true must be specified.
I have tried the following:
ViewQuery query = createQuery("some_doc_name");
List<String> keys = new ArrayList<String>();
keys.add(grupaName);
keys.add(uzytkownikID);
query.group(true);
query.groupLevel(2);
query.dbPath(db.path());
query.designDocId(stdDesignDocumentId);
query.keys(keys);
ViewResult r = db.queryView(query);
return r.getRows().get(0).getValueAsInt();
above example works without 'keys' specified.
I have other queries working with ComplexKey like eg:
ComplexKey key = ComplexKey.of(userID);
return queryView("list_by_userID",key);
but this returns only a list of type T (List) - using CouchDbRepositorySupport of course - and cannot be used with reduce type queries (from what I know).
Is there any way to execute the query with reduce function specified and a complex key with 2 or more values using Ektorp library? Any examples highly appreciated.
Ok, I've found the solution using trial and error approach:
public int getNumberOfDocsAssigned(String userID, String groupName) {
ViewQuery query = createQuery("list_by_userID")
.group(true)
.dbPath(db.path())
.designDocId(stdDesignDocumentId)
.key(new String[]{userID,groupName});
ViewResult r = db.queryView(query);
return r.getRows().get(0).getValueAsInt();
}
So, the point is to send the complex key (not keys) actually as a single (but complex) key containing the String array, for some reason method '.keys(...)' didn't work for me (it takes a Collection as an argument). (for explanation on difference between .key() and .keys() see Hendy's answer)
This method counts all documents assigned to the specific user (specified by 'userID') and specific group (specified by 'groupName').
Hope that helps anybody executing map/reduce queries for retrieving scalar values from CouchDB using Ektorp query.
Addition to Kris's answer:
Note that ViewQuery.keys() is used when you want to query for documents matching a set of keys, not for finding document(s) with a complex key.
Like Kris's answer, the following samples will get document(s) matching the specified key (not "keys")
viewQuery.key("hello"); // simple key
viewQuery.key(documentSlug); // simple key
viewQuery.key(new String[] { userID, groupName }); // complex key, using array
viewQuery.key(ComplexKey.of(userID, groupName)); // complex key, using ComplexKey
The following samples, on the other hand, will get document(s) matching the specified keys, where each key may be either a simple key or a complex key:
// simple key: in essence, same as using .key()
viewQuery.keys(ImmutableSet.of("hello"));
viewQuery.keys(ImmutableSet.of(documentSlug1));
// simple keys
viewQuery.keys(ImmutableSet.of("hello", "world"));
viewQuery.keys(ImmutableSet.of(documentSlug1, documentSlug2));
// complex key: in essence, same as using .key()
viewQuery.keys(ImmutableSet.of(
new String[] { "hello", "world" } ));
viewQuery.keys(ImmutableSet.of(
new String[] { userID1, groupName1 } ));
// complex keys
viewQuery.keys(ImmutableSet.of(
new String[] { "hello", "world" },
new String[] { "Mary", "Jane" } ));
viewQuery.keys(ImmutableSet.of(
new String[] { userID1, groupName1 },
new String[] { userID2, groupName2 } ));
// a simple key and a complex key. while technically possible,
// I don't think anybody actually does this
viewQuery.keys(ImmutableSet.of(
"hello",
new String[] { "Mary", "Jane" } ));
Note: ImmutableSet.of() is from guava library.
new Object[] { ... } seems to have same behavior as ComplexKey.of( ... )
Also, there are startKey() and endKey() for querying using partial key.
To send an empty object {}, use ComplexKey.emptyObject(). (only useful for partial key querying)

MongoDB C# offic. List<BsonObject> query issue and always olds values?

I have not clearly issue during query using two criterials like Id and Other. I use a Repository storing some data like id,iso,value. I have created an index("_id","Iso") to performs queries but queries are only returning my cursor if i use only one criterial like _id, but is returning nothing if a use two (_id, Iso) (commented code).
Are the index affecting the response or the query method are failing?
use :v1.6.5 and C# official.
Sample.
//Getting Data
public List<BsonObject> Get_object(string ID, string Iso)
{
using (var helper = BsonHelper.Create())
{
//helper.Db.Repository.EnsureIndex("_Id","Iso");
var query = Query.EQ("_Id", ID);
//if (!String.IsNullOrEmpty(Iso))
// query = Query.And(query, Query.EQ("Iso", Iso));
var cursor = helper.Db.Repository.FindAs<BsonObject>(query);
return cursor.ToList();
}
}
Data:
{
"_id": "2345019",
"Iso": "UK",
"Data": "Some data"
}
After that I have Updated my data using Update.Set() methods. I can see the changed data using MongoView. The new data are correct but the query is always returning the sames olds values. To see these values i use a page that can eventually cached, but if add a timestamp at end are not changing anything, page is always returning the same olds data. Your comments are welcome, thanks.
I do not recall offhand how the C# driver creates indexes, but the shell command for creating an index is like this:
db.things.ensureIndex({j:1});
Notice the '1' which is like saying 'true'.
In your code, you have:
helper.Db.Repository.EnsureIndex("_Id","Iso");
Perhaps it should be:
helper.Db.Repository.EnsureIndex("_Id", 1);
helper.Db.Repository.EnsureIndex("Iso", 1);
It could also be related to the fact that you are creating indexes on "_Id" and the actual id field is called "_id" ... MongoDB is case sensitive.
Have a quick look through the index documentation: http://www.mongodb.org/display/DOCS/Indexes