diff --git a/server/database.js b/server/database.js index 1f68e22..763fdae 100644 --- a/server/database.js +++ b/server/database.js @@ -19,6 +19,17 @@ let authors = [ { title: 'The Wind in the Willows', published: '1 April 1908' } ] }, + + { + name: 'KayneRuse', + books: [ + { title: 'alpha', published: "1" }, + { title: 'beta', published: "2" }, + { title: 'gamma', published: "3" }, + { title: 'delta', published: "4" }, + { title: 'epsilon', published: "5" }, + ] + }, ]; //insert the authors into the books (relationship) diff --git a/server/handler.js b/server/handler.js index b35d3fe..dd65011 100644 --- a/server/handler.js +++ b/server/handler.js @@ -2,7 +2,7 @@ const database = require('./database.js'); /* DOCS: The handler module connects the database to simpleQL. * this will be user-implemented, but for now uses a fake database provided in database.js - * The 'scalars' will be objects containing a name and the type for each scalar field requested by a query. + * The 'scalars' will be objects containing a name and theh type for each scalar field requested by a query. * This can be used in several ways... * * The handlers should only handle scalar fields - compound fields will be queried separately by simpleQL. @@ -11,19 +11,26 @@ const database = require('./database.js'); //the handler routines const handler = { Book: (parent, scalars) => { - //takes an object which is the result of the parent query, if there is one { typeName: 'Author', scalars: [scalars], context: the parent object } - //takes an array of scalar types as objects: { typeName: 'String', name: 'title' } + //takes an object which is the result of the parent query, if there is one { typeName: 'Author', scalars: [scalars], context: the parent object, match: I am being matched } + //takes an array of scalar types as objects: { typeName: 'String', name: 'title', match: filter } //must return an array of objects containing the results let books = database.books; - //if this is a sub-query, use the parent to narrow the search + //if this is a sub-query of Author, use the parent to narrow the search if (parent && parent.typeName == 'Author') { //filter based on parent object books = books.filter(b => b.author.name == parent.context.name); } - //return all books + //if this query has a matched scalar, filter by that match + books = books.filter(b => { + return scalars.every(s => { + return !s.match || b[s.name] === s.match; //other filter methods, such as ranges of numbers, can also be implemented + }); + }); + + //return all books after filtering const fields = scalars.map(s => s.name); return books.map(b => { const ret = {}; @@ -43,19 +50,19 @@ const handler = { Author: (parent, scalars) => { let authors = database.authors; - //if this is a sub-query, use the parent to find the author + //if this is a sub-query of Book, use the parent to find the author if (parent && parent.typeName == 'Book') { - const author = authors.find(a => a.books.filter(b => b.title == parent.context.title).length > 0); - - //ensure only the named scalars are returned (hack) - const ret = {}; - if (scalars.filter(s => s.name == 'name').length > 0) { - ret.name = author.name; - } - - return [ret]; //must return an array + //filter based on parent object + authors = authors.filter(a => a.books.some(b => b.title === parent.context.title)); } + //if this query has a matched scalar, filter by that match + authors = authors.filter(a => { + return scalars.every(s => { + return !s.match || a[s.name] === s.match; //other filter methods, such as ranges of numbers, can also be implemented + }); + }); + //return all authors const fields = scalars.map(s => s.name); return authors.map(a => { diff --git a/server/simpleQL/index.js b/server/simpleQL/index.js index 3600c7c..f0203aa 100644 --- a/server/simpleQL/index.js +++ b/server/simpleQL/index.js @@ -17,11 +17,11 @@ const main = (schema, handler) => { //the receiving function - this will be called multiple times return async reqBody => { - //parse the query - const tokens = reqBody.split(/(\s+)/).filter(s => s.trim().length > 0); //TODO: proper token parsing - let pos = 0; - try { + //parse the query + const tokens = reqBody.split(/(\s+)/).filter(s => s.trim().length > 0); //TODO: proper token parsing + let pos = 0; + //check for keywords switch(tokens[pos]) { case 'create': @@ -168,11 +168,18 @@ const parseQuery = async (handler, tokens, pos, typeGraph, parent = null) => { throw 'Expected \'{\' after queried type'; } - //the scalars to pass to the handler + //the scalars to pass to the handler - these are NEIGHBOURS in the hierarchy const scalarFields = []; const deferredCalls = []; //functions (promises) that will be called at the end of this function while(tokens[pos] && tokens[pos] != '}') { //while not at the end of this block + let match = false; + + if (tokens[pos] === 'match') { + match = true; + pos++; + } + //prevent using keywords if (keywords.includes(tokens[pos])) { throw 'Unexpected keyword ' + tokens[pos]; @@ -181,7 +188,12 @@ const parseQuery = async (handler, tokens, pos, typeGraph, parent = null) => { //type is a scalar, and can be queried if (typeGraph[queryType] && typeGraph[queryType][tokens[pos]] && typeGraph[typeGraph[queryType][tokens[pos]].typeName].scalar) { //push the scalar object to the queryFields - scalarFields.push({ typeName: typeGraph[queryType][tokens[pos]].typeName, name: tokens[pos] }); + scalarFields.push({ typeName: typeGraph[queryType][tokens[pos]].typeName, name: tokens[pos], match: match ? tokens[++pos] : null }); + + //if I am a scalar child of a match amd I do not match + if (parent && parent.match && !match) { + throw 'Broken match chain in scalar type ' + tokens[pos]; + } pos++; } @@ -189,24 +201,34 @@ const parseQuery = async (handler, tokens, pos, typeGraph, parent = null) => { const pos2 = pos; //cache the value to keep it from changing //recurse - deferredCalls.push(async (result) => [tokens[pos2], await parseQuery( - handler, - tokens, - pos2, - typeGraph, - { typeName: queryType, scalars: scalarFields, context: result } //parent object - )]); + deferredCalls.push(async (result) => { + //if I am a compound child of a match amd I do not match + if (parent && parent.match && !match) { + throw 'Broken match chain in compound type ' + tokens[pos2]; + } + + return [tokens[pos2], await parseQuery( + handler, + tokens, + pos2, + typeGraph, + { typeName: queryType, scalars: scalarFields, context: result, match: match } //parent object (this one) + ), match]; //HACK: match piggybacking on the tuple + }); pos = eatBlock(tokens, pos + 2); } else { //token is something else? - console.log('something else: ', tokens[pos], pos); - pos++; + throw 'Found something not in the type graph: ' + tokens[pos] + " " + pos; } } //eat the end bracket pos++; + + if (!handler[queryType]) { + throw 'Unrecognized type ' + queryType; + } let results = handler[queryType](parent, scalarFields); @@ -214,11 +236,17 @@ const parseQuery = async (handler, tokens, pos, typeGraph, parent = null) => { results = await Promise.all(results.map(async res => { const tuples = await Promise.all(deferredCalls.map(async call => await call(res))); + if (!tuples.every(tuple => !tuple[2] || tuple[1].length > 0)) { + console.log('discarding', tuples); + return []; + } + tuples.forEach(tuple => res[tuple[0]] = tuple[1]); return res; })); + results = results.filter(r => Array.isArray(r) && r.length == 0 ? false : true); return results; };