Delete implemented

This commit is contained in:
2022-02-22 13:29:41 +00:00
parent b790338b54
commit eec7b059b6
5 changed files with 292 additions and 10 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "sineql", "name": "sineql",
"version": "0.4.0", "version": "0.5.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sineql", "name": "sineql",
"version": "0.4.0", "version": "0.5.0",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"jest": "^27.5.1" "jest": "^27.5.1"

View File

@@ -1,6 +1,6 @@
{ {
"name": "sineql", "name": "sineql",
"version": "0.4.1", "version": "0.5.0",
"description": "A simple to use graphQL clone", "description": "A simple to use graphQL clone",
"main": "source/index.js", "main": "source/index.js",
"scripts": { "scripts": {

View File

@@ -55,9 +55,19 @@ const sineQL = (schema, { queryHandlers, createHandlers }, options = {}) => {
return [200, result]; return [200, result];
case 'delete': case 'delete':
return [501, 'Keyword not yet implemented: ' + tokens[0]]; if (!deleteHandlers) {
//TODO: implement these keywords return [501, 'Delete handlers not implemented'];
break; }
if (!deleteHandlers[tokens[1]]) {
throw `Delete handler not implemented for that type: ${tokens[1]}`;
}
const deleteTree = parseDeleteTree(tokens, typeGraph, options);
const result = await deleteHandlers[tokens[1]](deleteTree, typeGraph);
return [200, result];
//no leading keyword - regular query //no leading keyword - regular query
default: { default: {
@@ -65,12 +75,12 @@ const sineQL = (schema, { queryHandlers, createHandlers }, options = {}) => {
return [501, 'Query handlers not implemented']; return [501, 'Query handlers not implemented'];
} }
const queryTree = parseQueryTree(tokens, typeGraph, options); if (!queryHandlers[tokens[0]]) {
throw `Query handler not implemented for that type: ${tokens[0]}`;
if (!queryHandlers[queryTree.typeName]) {
throw `Query handler not implemented for that type: ${queryTree.typeName}`;
} }
const queryTree = parseQueryTree(tokens, typeGraph, options);
const result = await queryHandlers[queryTree.typeName](queryTree, typeGraph); const result = await queryHandlers[queryTree.typeName](queryTree, typeGraph);
if (options.debug) { if (options.debug) {

161
source/parse-delete-tree.js Normal file
View File

@@ -0,0 +1,161 @@
//build the tokens into a single object of types representing the initial query
const parseDeleteTree = (tokens, typeGraph, options = {}) => {
let current = 1; //primed
//check this is a update command
if (tokens[current - 1] != 'delete') {
throw 'Expected delete keyword at the beginning of delete command';
}
current++;
//get a token that matches a type
if (!typeGraph[tokens[current - 1]]) {
throw `Expected a type in the type graph (found ${tokens[current - 1]})`;
}
//check that there are the correct number of '{' and '}'
if (tokens.reduce((running, tok) => tok == '{' ? running + 1 : tok == '}' ? running - 1 : running, 0) != 0) {
throw `Unequal number of '{' and '}' found`;
}
//check that there are the correct number of '[' and ']'
if (tokens.reduce((running, tok) => tok == '[' ? running + 1 : tok == ']' ? running - 1 : running, 0) != 0) {
throw `Unequal number of '[' and ']' found`;
}
//the return
const result = [];
const type = tokens[current - 1];
if (tokens[current] == '[') {
current++;
}
do {
//read the block of lines
const [block, pos] = readBlock(tokens, current, type, typeGraph, options);
//insert the typename into the top-level block
block['typeName'] = type;
//insert update into the top-level block
block['delete'] = true;
current = pos;
result.push(block);
} while (tokens[current] && tokens[current] != ']');
//finally
return result;
};
const readBlock = (tokens, current, superType, typeGraph, options) => {
//scan the '{'
if (tokens[current++] != '{') {
throw `Expected '{' at beginning of a block (found ${tokens[current - 1]})`;
}
//result
const result = {};
//scan each "line" in this block
while(tokens[current++] && tokens[current - 1] != '}') {
//check for block-level keywords (modifiers need to form a chain from the leaf)
let modifier = null;
if (['match'].includes(tokens[current - 1])) {
modifier = tokens[current - 1];
current++;
}
//read field name
const fieldName = tokens[current - 1];
if (options.debug) {
console.log(`Trying to process field ${fieldName}`);
}
//if the field is not present in the type
if (!typeGraph[superType][fieldName]) {
throw `Unexpected field name ${fieldName} in type ${superType}`;
}
//if the field is non-scalar, read the sub-block
if (!typeGraph[typeGraph[superType][fieldName].typeName].scalar) {
if (tokens[current] == '[') {
current++;
}
do {
//recurse
const [block, pos] = readBlock(tokens, current, typeGraph[superType][fieldName].typeName, typeGraph, options);
//insert the typename into the block
block['typeName'] = typeGraph[superType][fieldName].typeName;
//insert the unique modifier if it's set
block['unique'] = typeGraph[superType][fieldName].unique;
//insert the block-level modifier signal
if (modifier) {
block[modifier] = true;
} else {
throw `Modifier expected for ${fieldName} (match)`;
}
//insert into result
result[fieldName] = result[fieldName] || [];
result[fieldName].push(block);
current = pos; //pos points past the end of the block
if (options.debug) {
console.log(`${fieldName}:`);
console.dir(result[fieldName], { depth: null });
}
} while (tokens[current] && tokens[current] == '{');
current++;
continue;
}
//scalar
else {
//save the typeGraph type into result
result[fieldName] = JSON.parse(JSON.stringify( typeGraph[ typeGraph[superType][fieldName].typeName ] ));
//insert the unique modifier if it's set
result[fieldName]['unique'] = typeGraph[superType][fieldName].unique;
//insert the block-level modifier signal
if (modifier) {
//handle integers and floats exposed to the user
switch(result[fieldName].typeName) {
case 'Integer':
result[fieldName][modifier] = parseInt(tokens[current++]);
break;
case 'Float':
result[fieldName][modifier] = parseFloat(tokens[current++]);
break;
default: //everything else is a string (including booleans)
result[fieldName][modifier] = tokens[current++];
}
} else {
throw `Modifier expected for ${fieldName} (match)`;
}
if (options.debug) {
console.log(`${fieldName}: `, result[fieldName]);
}
continue;
}
}
return [result, current];
};
module.exports = parseDeleteTree;

View File

@@ -0,0 +1,111 @@
const buildTypeGraph = require('../source/build-type-graph');
const parseInput = require('../source/parse-input');
const parseDeleteTree = require('../source/parse-delete-tree');
//schemas
const simpleSchema = `
scalar Date
type Book {
unique String title
Date published
Float rating
}
type Author {
unique String name
Book books
}
`;
const simpleBookQuery = `
delete Book {
match title "The Wind in the Willows"
}
`;
const compoundBookQuery = `
delete Book [
{
match title "The Philosopher's Kidney Stone"
}
{
match title "The Chamber Pot of Secrets"
}
{
match title "The Prisoner of Aunt Kazban"
}
{
match title "The Goblet of the Fire Cocktail"
}
{
match title "The Order for Kleenex"
}
{
match title "The Half-Priced Pharmacy"
}
{
match title "Yeah, I Got Nothing"
}
]
`;
const multipleFieldsQuery = `
delete Book {
match title "The Wind in the Willows"
match published "1908-04-01"
}
`;
//do stuff
test('parseDeleteTree - delete a single book', () => {
//setup
const tokens = parseInput(simpleBookQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const deleteTree = parseDeleteTree(tokens, graph);
//inspect
expect(deleteTree.length).toEqual(1);
expect(deleteTree[0].delete).toEqual(true);
expect(deleteTree[0].title.match).toEqual('The Wind in the Willows');
});
test('parseDeleteTree - delete multiple books', () => {
//setup
const tokens = parseInput(compoundBookQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const deleteTree = parseDeleteTree(tokens, graph);
//inspect
expect(deleteTree.length).toEqual(7);
expect(deleteTree[0].delete).toEqual(true);
expect(deleteTree[0].title.match).toEqual('The Philosopher\'s Kidney Stone');
});
test('parseDeleteTree - delete a book based on multiple match conditions', () => {
//setup
const tokens = parseInput(multipleFieldsQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const deleteTree = parseDeleteTree(tokens, graph);
//inspect
expect(deleteTree.length).toEqual(1);
expect(deleteTree[0].delete).toEqual(true);
expect(deleteTree[0].title.match).toEqual('The Wind in the Willows');
expect(deleteTree[0].published.match).toEqual('1908-04-01');
});