Implemented update

This commit is contained in:
2022-02-22 13:14:05 +00:00
parent d628c99371
commit b790338b54
8 changed files with 376 additions and 16 deletions

View File

@@ -154,7 +154,6 @@ The fields can be altered as well, using the query language's built-in keywords:
* update * update
* delete * delete
* match * match
* set
* typeName (this is not used in either language, but rather is used internally) * typeName (this is not used in either language, but rather is used internally)
`create`, `update` and `delete` are still to be defined properly, but they'll probably work as follows. `create`, `update` and `delete` are still to be defined properly, but they'll probably work as follows.
@@ -202,19 +201,19 @@ create Book [
### Update ### Update
When using `update`, `match` will find all existing records and update those using the `set` keyword: When using `update`, `match` will find all existing records and update those using the `update` keyword:
``` ```
update Book { update Book {
match title "The Wind in the Willows" match title "The Wind in the Willows"
set published "1908-4-1" update published "1908-04-01"
} }
``` ```
``` ```
update Book { update Book {
match title "The Wind in the Willows" match title "The Wind in the Willows"
set title "The Fart in the Fronds" update title "The Fart in the Fronds"
} }
``` ```
@@ -224,31 +223,31 @@ You can run multiple updates at once by surrounding them with `[]`:
update Book [ update Book [
{ {
match title "The Philosopher's Kidney Stone" match title "The Philosopher's Kidney Stone"
set published "1997-06-26" update published "1997-06-26"
} }
{ {
match title "The Chamber Pot of Secrets" match title "The Chamber Pot of Secrets"
set published "1998-07-02" update published "1998-07-02"
} }
{ {
match title "The Prisoner of Aunt Kazban" match title "The Prisoner of Aunt Kazban"
set published "1999-07-08" update published "1999-07-08"
} }
{ {
match title "The Goblet of the Fire Cocktail" match title "The Goblet of the Fire Cocktail"
set published "2000-07-08" update published "2000-07-08"
} }
{ {
match title "The Order for Kleenex" match title "The Order for Kleenex"
set published "2003-06-21" update published "2003-06-21"
} }
{ {
match title "The Half-Priced Pharmacy" match title "The Half-Priced Pharmacy"
set published "2005-07-16" update published "2005-07-16"
} }
{ {
match title "Yeah, I Got Nothing" match title "Yeah, I Got Nothing"
set published "2007-07-21" update published "2007-07-21"
} }
] ]
``` ```

View File

@@ -2,6 +2,7 @@ const buildTypeGraph = require('./build-type-graph');
const parseInput = require('./parse-input'); const parseInput = require('./parse-input');
const parseQueryTree = require('./parse-query-tree'); const parseQueryTree = require('./parse-query-tree');
const parseCreateTree = require('./parse-create-tree'); const parseCreateTree = require('./parse-create-tree');
const parseUpdateTree = require('./parse-update-tree');
//the main function to be returned (sineQL()) //the main function to be returned (sineQL())
const sineQL = (schema, { queryHandlers, createHandlers }, options = {}) => { const sineQL = (schema, { queryHandlers, createHandlers }, options = {}) => {
@@ -39,6 +40,20 @@ const sineQL = (schema, { queryHandlers, createHandlers }, options = {}) => {
return [200, result]; return [200, result];
case 'update': case 'update':
if (!updateHandlers) {
return [501, 'Update handlers not implemented'];
}
if (!updateHandlers[tokens[1]]) {
throw `Update handler not implemented for that type: ${tokens[1]}`;
}
const updateTree = parseUpdateTree(tokens, typeGraph, options);
const result = await updateHandlers[tokens[1]](updateTree, typeGraph);
return [200, result];
case 'delete': case 'delete':
return [501, 'Keyword not yet implemented: ' + tokens[0]]; return [501, 'Keyword not yet implemented: ' + tokens[0]];
//TODO: implement these keywords //TODO: implement these keywords

View File

@@ -1 +1 @@
["type", "scalar", "create", "update", "delete", "set", "match", "unique", "typeName"] ["type", "scalar", "create", "update", "delete", "match", "unique", "typeName"]

View File

@@ -130,9 +130,21 @@ const readBlock = (tokens, current, superType, typeGraph, options) => {
//insert the block-level modifier signal //insert the block-level modifier signal
if (modifier) { if (modifier) {
result[fieldName][modifier] = tokens[current++]; //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 { } else {
result[fieldName]['set'] = tokens[current++]; throw `Modifier expected for ${fieldName} (either create or match)`;
} }
if (options.debug) { if (options.debug) {

View File

@@ -91,8 +91,21 @@ const readBlock = (tokens, current, superType, typeGraph, options) => {
//insert the block-level modifier signal //insert the block-level modifier signal
if (modifier) { if (modifier) {
result[fieldName][modifier] = tokens[current++]; //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++];
}
} }
//no else-clause, since queries don't require modifiers
if (options.debug) { if (options.debug) {
console.log(`${fieldName}: `, result[fieldName]); console.log(`${fieldName}: `, result[fieldName]);

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

@@ -0,0 +1,161 @@
//build the tokens into a single object of types representing the initial query
const parseUpdateTree = (tokens, typeGraph, options = {}) => {
let current = 1; //primed
//check this is a update command
if (tokens[current - 1] != 'update') {
throw 'Expected update keyword at the beginning of update 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['update'] = 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 (['update', '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} (either update or 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} (either update or match)`;
}
if (options.debug) {
console.log(`${fieldName}: `, result[fieldName]);
}
continue;
}
}
return [result, current];
};
module.exports = parseUpdateTree;

View File

@@ -24,7 +24,7 @@ const simpleBookQuery = `
create Book { create Book {
create title "The Wind in the Willows" create title "The Wind in the Willows"
create published "1908" create published "1908-04-01"
create rating 9.5 create rating 9.5
} }

View File

@@ -0,0 +1,160 @@
const buildTypeGraph = require('../source/build-type-graph');
const parseInput = require('../source/parse-input');
const parseUpdateTree = require('../source/parse-update-tree');
//schemas
const simpleSchema = `
scalar Date
type Book {
unique String title
Date published
Float rating
}
type Author {
unique String name
Book books
}
`;
const simpleBookQuery = `
update Book {
match title "The Wind in the Willows"
update rating 9.5
}
`;
const compoundBookQuery = `
update Book [
{
match title "The Philosopher's Kidney Stone"
update published "1997-06-26"
}
{
match title "The Chamber Pot of Secrets"
update published "1998-07-02"
}
{
match title "The Prisoner of Aunt Kazban"
update published "1999-07-08"
}
{
match title "The Goblet of the Fire Cocktail"
update published "2000-07-08"
}
{
match title "The Order for Kleenex"
update published "2003-06-21"
}
{
match title "The Half-Priced Pharmacy"
update published "2005-07-16"
}
{
match title "Yeah, I Got Nothing"
update published "2007-07-21"
}
]
`;
const simpleAuthorQuery = `
update Author {
match name "Kenneth Grahame"
update books {
match title "The Wind in the Willows"
}
}
`;
const compoundAuthorQuery = `
update Author {
match name "J. K. Rolling"
update books [
{ 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" }
]
}
`;
//do stuff
test('parseUpdateTree - update a single book', () => {
//setup
const tokens = parseInput(simpleBookQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const updateTree = parseUpdateTree(tokens, graph);
//inspect
expect(updateTree.length).toEqual(1);
expect(updateTree[0].update).toEqual(true);
expect(updateTree[0].title.match).toEqual('The Wind in the Willows');
expect(updateTree[0].rating.update).toEqual(9.5);
});
test('parseUpdateTree - update multiple books', () => {
//setup
const tokens = parseInput(compoundBookQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const updateTree = parseUpdateTree(tokens, graph);
//inspect
expect(updateTree.length).toEqual(7);
expect(updateTree[0].update).toEqual(true);
expect(updateTree[0].title.match).toEqual('The Philosopher\'s Kidney Stone');
expect(updateTree[0].published.update).toEqual('1997-06-26');
});
test('parseUpdateTree - single join', () => {
//setup
const tokens = parseInput(simpleAuthorQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const updateTree = parseUpdateTree(tokens, graph);
//inspect
expect(updateTree.length).toEqual(1);
expect(updateTree[0].typeName).toEqual('Author');
expect(updateTree[0].update).toEqual(true);
expect(updateTree[0].name.match).toEqual('Kenneth Grahame');
expect(updateTree[0].books.length).toEqual(1);
expect(updateTree[0].books[0].title.match).toEqual('The Wind in the Willows');
});
test('parseUpdateTree - multiple join', () => {
//setup
const tokens = parseInput(compoundAuthorQuery, true);
const graph = buildTypeGraph(simpleSchema);
//process
const updateTree = parseUpdateTree(tokens, graph);
//inspect
expect(updateTree.length).toEqual(1);
expect(updateTree[0].typeName).toEqual('Author');
expect(updateTree[0].update).toEqual(true);
expect(updateTree[0].name.match).toEqual('J. K. Rolling');
expect(updateTree[0].books.length).toEqual(7);
expect(updateTree[0].books[0].title.match).toEqual('The Philosopher\'s Kidney Stone');
expect(updateTree[0].books[6].title.match).toEqual('Yeah, I Got Nothing');
});