Skip to content

Commit 95dbb16

Browse files
authored
Merge pull request #66 from LiUGraphQL/annotations
24 Annotations on create and update
2 parents 4397251 + 2556f52 commit 95dbb16

File tree

2 files changed

+88
-37
lines changed

2 files changed

+88
-37
lines changed

graphql-api-generator/utils/utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,19 @@ def extend_connect(schema: GraphQLSchema, _type: GraphQLType, field_type: GraphQ
354354
make += f'{create_field} : {create_implementing_type} '
355355
else:
356356
make += f'create: {create_name} '
357+
358+
# Get annotations
359+
edge_from = f'{capitalize(field_name)}EdgeFrom{_type.name}'
360+
annotate_input = f'_InputToAnnotate{edge_from}'
361+
annotations = get_field_annotations(_type.fields[field_name])
362+
363+
if len(annotations) > 0:
364+
# Make sure the annotation type actually exists first, and prevent us from defining it multiple times
365+
if annotate_input not in schema.type_map:
366+
schema = add_to_schema(schema, f'input {annotate_input}{{{annotations}}}')
367+
368+
make += f'annotations: {annotate_input}'
369+
357370
make += '}'
358371
schema = add_to_schema(schema, make)
359372
return schema
@@ -653,7 +666,10 @@ def add_input_to_create_edge_objects(schema: GraphQLSchema):
653666

654667
if len(annotations) > 0:
655668
make += f'input {edge_input} {{sourceID: ID! targetID: ID! annotations: {annotate_input} }}\n'
656-
make += f'input {annotate_input}{{{annotations}}}\n'
669+
670+
# Make sure the annotation type actually exists first, and prevent us from defining it multiple times
671+
if annotate_input not in schema.type_map:
672+
make += f'input {annotate_input}{{{annotations}}}\n'
657673
else:
658674
make += f'input {edge_input} {{sourceID: ID! targetID: ID!}}\n'
659675

graphql-server/drivers/arangodb/driver.js

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,22 @@ function getObjectOrInterfaceFields(type) {
240240
return keys;
241241
}
242242

243+
/**
244+
* Transforms the doc (field_name, field_value) collection to JSON stringify, strip leading and ending '{' and '}' and insert leading ', '
245+
* This allows docs to be appanded to already existing input parameters.
246+
* @param doc (should already have passed through getScalarsAndEnums)
247+
* @returns String
248+
*/
249+
function convertToInputAppendString(doc) {
250+
ret = ''
251+
if (doc != null && doc.size > 0) {
252+
ret = JSON.stringify(doc);
253+
ret[0] = ',';
254+
ret = ret - slice(0, -1);
255+
}
256+
return ret;
257+
}
258+
243259
// ----------------------------------------------------------
244260

245261
async function getEdge(parent, args, info){
@@ -301,15 +317,15 @@ async function getEdge(parent, args, info){
301317
/*
302318
TODO: We should probably call the createEdge function here (when we've defined it).
303319
*/
304-
async function create(isRoot, ctxt, data, returnType, info){
320+
async function create(isRoot, ctxt, data, returnType, info) {
305321
// define transaction object
306-
if(ctxt.trans === undefined) ctxt.trans = initTransaction();
322+
if (ctxt.trans === undefined) ctxt.trans = initTransaction();
307323

308324
// is root op and mutatation is already queued
309-
if(isRoot && ctxt.trans.queue[info.path.key]){
310-
if(ctxt.trans.open) await executeTransaction(ctxt);
311-
if(ctxt.trans.error){
312-
if(ctxt.trans.errorReported) return null;
325+
if (isRoot && ctxt.trans.queue[info.path.key]) {
326+
if (ctxt.trans.open) await executeTransaction(ctxt);
327+
if (ctxt.trans.error) {
328+
if (ctxt.trans.errorReported) return null;
313329
ctxt.trans.errorReported = true;
314330
throw ctxt.trans.error;
315331
}
@@ -338,48 +354,60 @@ async function create(isRoot, ctxt, data, returnType, info){
338354

339355
// for edges
340356
let ob = getTypesAndInterfaces(data, returnType);
341-
for(let fieldName in ob){
357+
for (let fieldName in ob) {
342358
let innerFieldType = graphql.getNamedType(returnType.getFields()[fieldName].type);
343359
let edge = getEdgeCollectionName(returnType.name, fieldName);
344360
ctxt.trans.write.add(edge);
345361
let edgeCollection = asAQLVar(`db.${edge}`);
346362
let values = Array.isArray(ob[fieldName]) ? ob[fieldName] : [ob[fieldName]]; // treat as list even if only one value is present
347363

348-
for(let i in values){
364+
for (let i in values) {
349365
let value = values[i];
350366
console.log(value);
351-
if(graphql.isInterfaceType(innerFieldType)){ // interface
352-
if(value['connect']){
367+
368+
// Prepare annotations
369+
let annotations = null;
370+
if (value['annotations']) {
371+
annotations = getScalarsAndEnums(value['annotations'], info.schema.getType("_InputToAnnotate" + edge));
372+
annotations['_creationDate'] = date.valueOf();
373+
}
374+
375+
if (graphql.isInterfaceType(innerFieldType)) { // interface
376+
if (value['connect']) {
353377
validateType(ctxt, value['connect'], innerFieldType, info.schema);
354378
let typeToConnect = value['connect'].split('/')[0];
355379
// add edge
356380
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
357-
ctxt.trans.code.push(` db._query(aql\`INSERT {_from: ${from}._id, _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
381+
ctxt.trans.code.push(` db._query(aql\`INSERT {_from: ${from}._id, _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
358382
ctxt.trans.code.push(`} else { `);
359383
ctxt.trans.code.push(` throw "${value['connect']} does not exist in ${typeToConnect}";`);
360384
ctxt.trans.code.push(`}`);
361385
} else {
362386
// create
363387
let key = Object.keys(value)[0];
388+
if (key == "annotations") {
389+
// In case the user actually specifies the annotations before the edge
390+
key = Object.keys(value)[1];
391+
}
364392
let typeToCreate = key.replace(/^create(.+)$/, '$1');
365393
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
366394
await create(false, ctxt, value[key], info.schema.getType(typeToCreate), info);
367-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
395+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
368396
}
369397
} else { // type
370398
if (value['connect']) {
371399
validateType(ctxt, value['connect'], innerFieldType, info.schema);
372400
let typeToConnect = value['connect'].split('/')[0];
373401
// add edge
374402
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
375-
ctxt.trans.code.push(` db._query(aql\`INSERT {_from: ${from}._id, _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
403+
ctxt.trans.code.push(` db._query(aql\`INSERT {_from: ${from}._id, _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
376404
ctxt.trans.code.push(`} else { `);
377405
ctxt.trans.code.push(` throw "${value['connect']} does not exist in ${typeToConnect}";`);
378406
ctxt.trans.code.push(`}`);
379-
} else {// create
407+
} else { // create
380408
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
381409
await create(false, ctxt, value['create'], innerFieldType, info);
382-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
410+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: ${from}._id, _to: ${to}._id ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
383411
}
384412
}
385413
}
@@ -389,7 +417,7 @@ async function create(isRoot, ctxt, data, returnType, info){
389417
addFinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema);
390418

391419
// overwrite the current action
392-
if(isRoot) {
420+
if (isRoot) {
393421
ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar};`); // add root result
394422
ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction
395423
getVar(ctxt); // increment varCounter
@@ -549,15 +577,15 @@ function validateType(ctxt, id, type, schema){
549577
}
550578
}
551579

552-
async function update(isRoot, ctxt, id, data, returnType, info){
580+
async function update(isRoot, ctxt, id, data, returnType, info) {
553581
// define transaction object
554-
if(ctxt.trans === undefined) ctxt.trans = initTransaction();
582+
if (ctxt.trans === undefined) ctxt.trans = initTransaction();
555583

556584
// is root op and mutation is already queued
557-
if(isRoot && ctxt.trans.queue[info.path.key]){
558-
if(ctxt.trans.open) await executeTransaction(ctxt);
559-
if(ctxt.trans.error){
560-
if(ctxt.trans.errorReported) return null;
585+
if (isRoot && ctxt.trans.queue[info.path.key]) {
586+
if (ctxt.trans.open) await executeTransaction(ctxt);
587+
if (ctxt.trans.error) {
588+
if (ctxt.trans.errorReported) return null;
561589
ctxt.trans.errorReported = true;
562590
throw ctxt.trans.error;
563591
}
@@ -569,24 +597,24 @@ async function update(isRoot, ctxt, id, data, returnType, info){
569597
// 3) Add key check to transaction
570598
let keyName = getKeyName(returnType.name);
571599
let keyType = info.schema["_typeMap"][keyName];
572-
if(keyType){
600+
if (keyType) {
573601
try {
574602
let collection = db.collection(returnType);
575603
const cursor = await db.query(aql`FOR i IN ${collection} FILTER(i._id == ${id}) RETURN i`);
576604
let doc = await cursor.next();
577-
if(doc == undefined){
605+
if (doc == undefined) {
578606
throw new ApolloError(`ID ${id} is not a document in the type ${returnType}`);
579607
}
580608

581609
let key = {};
582-
for(let f in keyType._fields){
610+
for (let f in keyType._fields) {
583611
key[f] = doc[f];
584-
if(data[f] !== undefined){
612+
if (data[f] !== undefined) {
585613
key[f] = data[f];
586614
}
587615
}
588616
validateKey(ctxt, key, returnType, info.schema, id);
589-
} catch(err) {
617+
} catch (err) {
590618
throw new ApolloError(err);
591619
}
592620
}
@@ -619,6 +647,13 @@ async function update(isRoot, ctxt, id, data, returnType, info){
619647
for (let i in values) {
620648
let value = values[i];
621649

650+
// Prepare annotations
651+
let annotations = null;
652+
if (value['annotations']) {
653+
annotations = getScalarsAndEnums(value['annotations'], info.schema.getType("_InputToAnnotate" + edge));
654+
annotations['_creationDate'] = date.valueOf();
655+
}
656+
622657
if (graphql.isInterfaceType(nestedReturnType)) {
623658
// interface field
624659
if (value['connect']) {
@@ -630,11 +665,11 @@ async function update(isRoot, ctxt, id, data, returnType, info){
630665
// add edge
631666
if (!disableEdgeValidation) { // check the database
632667
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
633-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
668+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
634669
ctxt.trans.code.push(`} else { throw "${value['connect']} does not exist in ${typeToConnect}"; }`);
635670
} else {
636671
console.warn(`Adding connection to ${value['connect']} in ${edge} without validating ID`);
637-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
672+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
638673
}
639674
} else {
640675
// create
@@ -645,7 +680,7 @@ async function update(isRoot, ctxt, id, data, returnType, info){
645680
}
646681
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
647682
await create(false, ctxt, value[key], info.schema.getType(typeToCreate), info);
648-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
683+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
649684
}
650685
} else {
651686
// type field
@@ -658,17 +693,17 @@ async function update(isRoot, ctxt, id, data, returnType, info){
658693
// add edge
659694
if (!disableEdgeValidation) { // check the database
660695
ctxt.trans.code.push(`if(db._collection('${typeToConnect}').exists('${value['connect']}')){`);
661-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
696+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
662697
ctxt.trans.code.push(`} else { throw "${value['connect']} does not exist in ${typeToConnect}"; }`);
663698
} else {
664699
console.warn(`Adding connection to ${value['connect']} in ${edge} without validating ID`);
665-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" } IN ${edgeCollection} RETURN NEW\`);`);
700+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: "${value['connect']}" ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
666701
}
667702
} else {
668703
// create
669704
let to = asAQLVar(getVar(ctxt)); // reference to the object to be added
670705
await create(false, ctxt, value['create'], nestedReturnType, info);
671-
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id } IN ${edgeCollection} RETURN NEW\`);`);
706+
ctxt.trans.code.push(`db._query(aql\`INSERT {_from: "${id}", _to: ${to}._id ${convertToInputAppendString(annotations)}} IN ${edgeCollection} RETURN NEW\`);`);
672707
}
673708
}
674709
}
@@ -678,7 +713,7 @@ async function update(isRoot, ctxt, id, data, returnType, info){
678713
addFinalDirectiveChecksForType(ctxt, returnType, aql`${asAQLVar(resVar)}._id`, info.schema);
679714

680715
// overwrite the current action
681-
if(isRoot) {
716+
if (isRoot) {
682717
ctxt.trans.code.push(`result['${info.path.key}'] = ${resVar}.new;`); // add root result
683718
ctxt.trans.queue[info.path.key] = true; // indicate that this mutation op has been added to the transaction
684719
getVar(ctxt); // increment varCounter
@@ -736,11 +771,11 @@ function formatFixVariable(_type, v) {
736771
if (Array.isArray(v)) {
737772
let newV = []
738773
for (date of v)
739-
newV.push(aql`DATE_TIMESTAMP(${date})`);
774+
newV.push(aql`DATE_TIMESTAMP("${date}")`);
740775
return newV;
741776
}
742777
else
743-
return aql`DATE_TIMESTAMP(${v})`;
778+
return aql`DATE_TIMESTAMP("${v}")`;
744779
else
745780
return v;
746781
}

0 commit comments

Comments
 (0)