mirror of
https://github.com/Ratstail91/sineQL.git
synced 2025-11-29 02:34:28 +11:00
Implemented update
This commit is contained in:
21
README.md
21
README.md
@@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
["type", "scalar", "create", "update", "delete", "set", "match", "unique", "typeName"]
|
["type", "scalar", "create", "update", "delete", "match", "unique", "typeName"]
|
||||||
@@ -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) {
|
||||||
|
//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++];
|
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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
//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++];
|
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
161
source/parse-update-tree.js
Normal 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;
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
160
test/parse-update-tree.test.js
Normal file
160
test/parse-update-tree.test.js
Normal 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');
|
||||||
|
});
|
||||||
|
|
||||||
Reference in New Issue
Block a user