I'm looking up organization members based on a list of organization ids. Each org has a paged list of members with an endCursor. Since each endCursor will be different and each org has different numbers of members (and different number of pages), how can I pass in different cursors back as variables? If so, how would each cursor be associated to the org ID from the previous query?
query($orgIds:[ID!]!, $page_cursor:String) { // not sure how to pass in the cursor when different length lists are returned
nodes(ids:$orgIds) {
... on Organization {
id
members(first: 100, after: $page_cursor) {
edges {
node {
id
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
I've read http://graphql.org/learn/pagination/ but I'm not seeing anything related to passing in multiple cursors for the same edge list.
I haven't found any details in the graphql specs on how supply an array of cursors for the same edge list. GitHub would have to come up with a custom feature for that. Though I have a feeling it is not quite what you are looking for.
A cursor exist per node, so if you add cursor field to your edges request, you will get the cursors for all nodes within your request.
edges {
cursor
node {
id
}
}
Response would become something like this:
"edges": [
{
"cursor": "Y3Vyc29yOnYyOpLOAANaVM4AA1pU",
"node": {
"id": "MDQ6VXNlcjIxOTczMg=="
}
},
Please note that endCursor is not the same if you change the "first:100" parameter to let's say "first:5", because endCursor would be the last cursor of the last node of the 5 first results.
The only reference you will have from your cursor ID to your orginazation ID, would be that the structure of the object being returned from GitHub's graphql API. Any cursor in your example is a child of a specific organisation.
From my point of view, It would be up to your client to remember that reference if needed afterwards. With that in mind, you might want to simply iterate through the pages of a single organisation, before you go to the next. (supplying only 1 organisation pr request, and not an array).
Related
Let's say my graphql server wants to fetch the following data as JSON where person3 and person5 are some id's:
"persons": {
"person3": {
"id": "person3",
"name": "Mike"
},
"person5": {
"id": "person5",
"name": "Lisa"
}
}
Question: How to create the schema type definition with apollo?
The keys person3 and person5 here are dynamically generated depending on my query (i.e. the area used in the query). So at another time I might get person1, person2, person3 returned.
As you see persons is not an Iterable, so the following won't work as a graphql type definition I did with apollo:
type Person {
id: String
name: String
}
type Query {
persons(area: String): [Person]
}
The keys in the persons object may always be different.
One solution of course would be to transform the incoming JSON data to use an array for persons, but is there no way to work with the data as such?
GraphQL relies on both the server and the client knowing ahead of time what fields are available available for each type. In some cases, the client can discover those fields (via introspection), but for the server, they always need to be known ahead of time. So to somehow dynamically generate those fields based on the returned data is not really possible.
You could utilize a custom JSON scalar (graphql-type-json module) and return that for your query:
type Query {
persons(area: String): JSON
}
By utilizing JSON, you bypass the requirement for the returned data to fit any specific structure, so you can send back whatever you want as long it's properly formatted JSON.
Of course, there's significant disadvantages in doing this. For example, you lose the safety net provided by the type(s) you would have previously used (literally any structure could be returned, and if you're returning the wrong one, you won't find out about it until the client tries to use it and fails). You also lose the ability to use resolvers for any fields within the returned data.
But... your funeral :)
As an aside, I would consider flattening out the data into an array (like you suggested in your question) before sending it back to the client. If you're writing the client code, and working with a dynamically-sized list of customers, chances are an array will be much easier to work with rather than an object keyed by id. If you're using React, for example, and displaying a component for each customer, you'll end up converting that object to an array to map it anyway. In designing your API, I would make client usability a higher consideration than avoiding additional processing of your data.
You can write your own GraphQLScalarType and precisely describe your object and your dynamic keys, what you allow and what you do not allow or transform.
See https://graphql.org/graphql-js/type/#graphqlscalartype
You can have a look at taion/graphql-type-json where he creates a Scalar that allows and transforms any kind of content:
https://github.com/taion/graphql-type-json/blob/master/src/index.js
I had a similar problem with dynamic keys in a schema, and ended up going with a solution like this:
query lookupPersons {
persons {
personKeys
person3: personValue(key: "person3") {
id
name
}
}
}
returns:
{
data: {
persons: {
personKeys: ["person1", "person2", "person3"]
person3: {
id: "person3"
name: "Mike"
}
}
}
}
by shifting the complexity to the query, it simplifies the response shape.
the advantage compared to the JSON approach is it doesn't need any deserialisation from the client
Additional info for Venryx: a possible schema to fit my query looks like this:
type Person {
id: String
name: String
}
type PersonsResult {
personKeys: [String]
personValue(key: String): Person
}
type Query {
persons(area: String): PersonsResult
}
As an aside, if your data set for persons gets large enough, you're going to probably want pagination on personKeys as well, at which point, you should look into https://relay.dev/graphql/connections.htm
GraphQL and Relay has a robust pagination algorithm which enables easy pagination for the end user, allowing pagination even in unbounded and order-independent results.
However, I have a use case that I'm not really sure how to go about doing in GraphQL and relay, and it's quite easy that I'm sure I just missed something.
How do I, for example, get the 5th item (and only the 5th item), if my list is ordered (by, say, an orderBy argument)?
This not very well documented, but here's how to do it.
query {
allPeople(first: 5, last: 1) {
edges {
node {
name
}
}
}
}
First you select first: 5 to get the first 5 people in the list. Then, do last:1 which gets the last person from that subset. In other words - get the fifth person.
If you do (first: 5, last: 2) you would get the 4th and the 5th person in the list.
Demo
(if it returns an error - manually re-type the word query in the query and it will work). Then, try again without first and last to see the whole list and you'll see that Leia is 5th.
If you have an ordered list at the backend and you want to get the element at a particular position, just specify the position value as an argument for the query field. The code for the query field looks like the following:
employee: {
type: EmployeeType,
args: {
position: {
type: new GraphQLNonNull(GraphQLInt)
},
...args,
},
resolve: async (context, {position, ...args}) => {
// Get the ordered list of employees, probably from cache.
// Pick the employee with the requested position in the list.
// Return the employee.
},
},
Say I have a doc to save with couchDB and the doc looks like this:
{
"email": "lorem#gmail.com",
"name": "lorem",
"id": "lorem",
"password": "sha1$bc5c595c$1$d0e9fa434048a5ae1dfd23ea470ef2bb83628ed6"
}
and I want to be able to query the doc either by 'id' or 'email'. So when save this as a view I write so:
db.save('_design/users', {
byId: {
map: function(doc) {
if (doc.id && doc.email) {
emit(doc.id, doc);
emit(doc.email, doc);
}
}
}
});
And then I could query like this:
db.view('users/byId', {
key: key
}, function(err, data) {
if (err || data.length === 0) return def.reject(new Error('not found'));
data = data[0] || {};
data = data.value || {};
self.attrs = _.clone(data);
delete self.attrs._rev;
delete self.attrs._id;
def.resolve(data);
});
And it works just fine. I could load the data either by id or email. But I'm not sure if I should do so.
I have another solution which by saving the same doc with two different view like byId and byEmail, but in this way I save the same doc twice and obviously it will cost space of the database.
Not sure which solution is better.
The canonical solution would be to have two views, one by email and one by id. To not waste space for the document, you can just emit null as the value and then use the include_docs=true query paramter when you query the view.
Also, you might want to use _id instead of id. That way, CouchDB ensures that the ID will be unique and you don't have to use a view to loop up documents.
I'd change to the two separate views. That's explicit and clear. When you emit the same doc twice in a single view – by an id and e-mail you're effectively combining the 2 views into one. You may think of it as a search tree with the 2 root branches. I don't see any reason of doing that, and would suggest leaving the data access and storage optimization job to the database.
The views combination may also yield tricky bugs, when for some reason you confuse an id and an e-mail.
There is absolutely nothing wrong with emitting the same document multiple times with a different key. It's about what makes most sense for your application.
If id and email are always valid and interchangeable ways to identify a user then a single view is perfect. For example, when id is some sort of unique account reference and users are allowed to use that or their (more memorable) email address to login.
However, if you need to differentiate between the two values, e.g. id is only meant for application administrators, then separate views are probably better. (You could probably use a complex key instead ... but that's another answer.)
I am creating one application where for every product I have one database and I will create different document based on date. The keys in documents could be different and depend upon user, what he provides. Assumption is user will keep giving same key for tracking with changed value over time. In the end, I need to know all possible keys before creating automatic views on them.
Example:
If I had DB, say, test. It contains, say, two documents,
1. {
"_id":"1",
"_rev":"1-"
"type": "Note",
"content": "Hello World!"
}
2. {
"_id":"2",
"_rev":"1-"
"type": "Note",
"content": "Beyond Hello World!",
"extra":"Boom"
}
Then I want to list all keys in this DB. So, answer should be _id,_rev,type,content and extra.
These keys are dynamic and depend upon users. So, I couldn't assume that I knew them in advance.
I have never used stackoverflow before, I saw your question when trying to solve this problem myself so I have signed up. I think this solves your problem:
create a view where "views" includes this:
{
"keys": {
"map": "function(doc) { for (var thing in doc) { emit(thing,1); } }",
"reduce": "function(key,values) { return sum(values); }"
}
}
then query on that view with group=true e.g.:
http://localhost:5984/mydb/_design/myview/_view/keys?group=true
you should get back a list of all the keys in your database and a count of how often the occur.
does this help?
I'm pretty new to couchDB and even after reading (latest archive as now deleted) http://wiki.apache.org/couchdb/How_to_store_hierarchical_data (via ‘Store the full path to each node as an attribute in that node's document’) it's still not clicking just yet.
Instead of using the full path pattern as described in the wiki I'm hoping to keep track of children as an array of UUIDs and the parent as a single UUID. I'm leaning towards this pattern so I can maintain the order of children by their positions in the children array.
Here are some sample documents in couch, buckets can contain buckets and items, items can only contain other items. (UUIDs abbreviated for clarity):
{_id: 3944
name: "top level bucket with two items"
type: "bucket",
parent: null
children: [8989, 4839]
}
{_id: 8989
name: "second level item with no sub items"
type: "item"
parent: 3944
}
{
_id: 4839
name: "second level bucket with one item"
type: "bucket",
parent: 3944
children: [5694]
}
{
_id: 5694
name: "third level item (has one sub item)"
type: "item",
parent: 4839,
children: [5390]
}
{
_id: 5390
name: "fourth level item"
type: "item"
parent: 5694
}
Is it possible to look up a document by an embedded document id within a map function?
function(doc) {
if(doc.type == "bucket" || doc.type == "item")
emit(doc, null); // still working on my key value output structure
if(doc.children) {
for(var i in doc.children) {
// can i look up a document here using ids from the children array?
doc.children[i]; // psuedo code
emit(); // the retrieved document would be emitted here
}
}
}
}
In an ideal world final JSON output would look something like.
{"_id":3944,
"name":"top level bucket with two items",
"type":"bucket",
"parent":"",
"children":[
{"_id":8989, "name":"second level item with no sub items", "type":"item", "parent":3944},
{"_id": 4839, "name":"second level bucket with one item", "type":"bucket", "parent":3944, "children":[
{"_id":5694", "name":"third level item (has one sub item)", "type":"item", "parent": 4839, "children":[
{"_id":5390, "name":"fourth level item", "type":"item", "parent":5694}
]}
]}
]
}
You can find a general discussion on the CouchDB wiki.
I have no time to test it right now, however your map function should look something like:
function(doc) {
if (doc.type === "bucket" || doc.type === "item")
emit([ doc._id, -1 ], 1);
if (doc.children) {
for (var i = 0, child_id; child_id = doc.children[i]; ++i) {
emit([ doc._id, i ], { _id: child_id });
}
}
}
}
You should query it with include_docs=true to get the documents, as explained in the CouchDB documentation: if your map function emits an object value which has {'_id': XXX} and you query view with include_docs=true parameter, then CouchDB will fetch the document with id XXX rather than the document which was processed to emit the key/value pair.
Add startkey=["3944"]&endkey["3944",{}] to get only the document with id "3944" with its children.
EDIT: have a look at this question for more details.
Can you output a tree structure from a view? No. CouchDB view queries return a list of values, there is no way to have them output anything other than a list. So, you have to deal with your map returning the list of all descendants of a given bucket.
You can, however, plug a _list post-processing function after the view itself, to turn that list back into a nested structure. This is possible if your values know the _id of their parent — the algorithm is fairly straightforward, just ask another question if it gives you trouble.
Can you grab a document by its id in the map function? No. There's no way to grab a document by its identifier from within CouchDB. The request must come from the application, either in the form of a standard GET on the document identifier, or by adding include_docs=true to a view request.
The technical reason for this is pretty simple: CouchDB only runs the map function when the document changes. If document A was allowed to fetch document B, then the emitted data would become invalid when B changes.
Can you output all descendants without storing the list of parents of every node? No. CouchDB map functions emit a set of key-value-id pairs for every document in the database, so the correspondence between the key and the id must be determined based on a single document.
If you have a four-level tree structure A -> B -> C -> D but only let a node know about its parent and children, then none of the nodes above know that D is a descendant of A, so you will not be able to emit the id of D with a key based on A and thus it will not be visible in the output.
So, you have three choices:
Grab only three levels (this is possible because B knows that C is a descendant of A), and grab additional levels by running the query again.
Somehow store the list of descendants of every node within the node (this is costly).
Store the list of parents of every node within the node.