/* This program has been developed by students from the bachelor Computer Science at Utrecht University within the Software Project course. © Copyright Utrecht University (Department of Information and Computing Sciences) */ package aql import ( "errors" "fmt" "strconv" "git.science.uu.nl/graphpolaris/query-conversion/entity" ) // Version 1.13 /* ConvertQuery converts an IncomingQueryJSON object into AQL JSONQuery: *entity.IncomingQueryJSON, the query to be converted to AQL Returns: *string, the AQL query and a possible error */ func (s *Service) ConvertQuery(JSONQuery *entity.IncomingQueryJSON) (*string, error) { // Check to make sure all indexes exist // The largest possible id for an entity largestEntityID := len(JSONQuery.Entities) - 1 // The largest possible id for a relation largestRelationID := len(JSONQuery.Relations) - 1 // Make sure no entity should be returned that is outside the range of that list for _, e := range JSONQuery.Return.Entities { // If this entity references an entity that is outside the range if e > largestEntityID || e < 0 { return nil, errors.New("non-existing entity referenced in return") } } // Make sure that no relation mentions a non-existing entity for _, r := range JSONQuery.Relations { if r.FromID > largestEntityID && r.FromType == "entity" || r.ToID > largestEntityID && r.ToType == "entity" { return nil, errors.New("non-exisiting entity referenced in relation") } } // Make sure no non-existing relation is tried to be returned for _, r := range JSONQuery.Return.Relations { if r > largestRelationID || r < 0 { return nil, errors.New("non-existing relation referenced in return") } } // Don't run search if we are getting empty queries from unit tests var tree []entity.Tree var topNode entity.QueryEntityStruct if len(JSONQuery.Entities) != 0 && len(JSONQuery.Relations) != 0 { tree, topNode = createHierarchy(JSONQuery) } for i, treeElement := range tree { fmt.Println("I am triple(from,rel,to): " + strconv.Itoa(treeElement.Self.FromNode.ID) + "," + strconv.Itoa(treeElement.Self.Rel.ID) + "," + strconv.Itoa(treeElement.Self.ToNode.ID)) fmt.Println("My relation contains the following nodes(from,to): " + strconv.Itoa(treeElement.Self.Rel.FromID) + "," + strconv.Itoa(treeElement.Self.Rel.ToID)) fmt.Println("My index is: " + strconv.Itoa(i)) fmt.Println("My parent index is: " + strconv.Itoa(treeElement.Parent)) fmt.Println("My children's indices are: ") for j := range treeElement.Children { fmt.Println(treeElement.Children[j]) } fmt.Println("Next please!") } result := createQuery(JSONQuery, tree, topNode) return result, nil } /* createQuery generates a query based on the json file provided JSONQuery: *entity.IncomingQueryJSON, this is a parsedJSON struct holding all the data needed to form a query, Return: *string, a string containing the corresponding AQL query and an error */ func createQuery(JSONQuery *entity.IncomingQueryJSON, tree []entity.Tree, topNode entity.QueryEntityStruct) *string { output := createLetFor("result", fmt.Sprintf("e_%v", topNode.ID), topNode.Name) for constraint := range topNode.Constraints { output += createFilter(topNode.Constraints[constraint], fmt.Sprintf("e_%v", topNode.ID)) } subQuery, subName := createQueryRecurse(JSONQuery, tree, 0, topNode) subNames := []string{subName} output += subQuery output += createZeroFilter(append(subNames, fmt.Sprintf("e_%v", topNode.ID))) output += createReturn(fmt.Sprintf("e_%v", topNode.ID), "", subNames) output += "let nodes = union_distinct(flatten(result[**].nodes),[])\nlet edges = union_distinct(flatten(result[**].rel),[])\nreturn {\"vertices\":nodes,\"edges\":edges}" return &output } func createQueryRecurse(JSONQuery *entity.IncomingQueryJSON, tree []entity.Tree, currentindex int, topNode entity.QueryEntityStruct) (string, string) { currentTree := tree[currentindex] newNode := getTreeNewNode(currentTree, tree, topNode) output := createLetFor(fmt.Sprintf("e%v", newNode.ID), fmt.Sprintf("e_%v", newNode.ID), newNode.Name) output += fmt.Sprintf("FOR r%v IN %v", currentTree.Self.Rel.ID, currentTree.Self.Rel.Name) for constraint := range newNode.Constraints { output += createFilter(newNode.Constraints[constraint], fmt.Sprintf("e_%v", newNode.ID)) } for constraint := range currentTree.Self.Rel.QueryConstraintStruct { output += createFilter(currentTree.Self.Rel.QueryConstraintStruct[constraint], fmt.Sprintf("r%v", currentTree.Self.Rel.ID)) } output += fmt.Sprintf("FILTER r%v._from == e_%v._id AND r%v._to == e_%v._id", currentTree.Self.Rel.ID, currentTree.Self.FromNode.ID, currentTree.Self.Rel.ID, currentTree.Self.ToNode.ID) var subNames []string for i := range currentTree.Children { subQuery, subName := createQueryRecurse(JSONQuery, tree, currentTree.Children[i], topNode) output += subQuery subNames = append(subNames, subName) } output += createZeroFilter(append(subNames, fmt.Sprintf("e_%v", newNode.ID), fmt.Sprintf("r%v", currentTree.Self.Rel.ID))) output += createReturn(fmt.Sprintf("e_%v", newNode.ID), fmt.Sprintf("r%v", currentTree.Self.Rel.ID), subNames) return output, fmt.Sprintf("e%v", newNode.ID) } func getTreeNewNode(currentTree entity.Tree, tree []entity.Tree, topNode entity.QueryEntityStruct) entity.QueryEntityStruct { if currentTree.Parent < 0 { if currentTree.Self.FromNode.ID == topNode.ID { return currentTree.Self.ToNode } else { return currentTree.Self.FromNode } } else if currentTree.Self.FromNode.ID == tree[currentTree.Parent].Self.FromNode.ID || currentTree.Self.FromNode.ID == tree[currentTree.Parent].Self.ToNode.ID { return currentTree.Self.ToNode } else { return currentTree.Self.FromNode } } func createLetFor(variableName string, forName string, enumerableName string) string { return "LET " + variableName + " = (\n\tFOR " + forName + " IN " + enumerableName + "\n" } func createFilter(constraint entity.QueryConstraintStruct, filtered string) string { return "\tFILTER + " + filtered + constraint.Attribute + " " + wordsToLogicalSign(constraint) + " " + constraint.Value + " \n" } func createZeroFilter(subNames []string) string { output := "FILTER" for i := range subNames { output += fmt.Sprintf(" length(%v) != 0", subNames[i]) if i < len(subNames)-1 { output += " AND" } } return output } func createReturn(nodeName string, relName string, subNames []string) string { output := "RETURN {\"nodes\":union_distinct(" for i := range subNames { output += fmt.Sprintf("flatten(%v[**].nodes), ", subNames[i]) } output += "[" + nodeName + "]" if len(subNames) == 0 { output += ", []" } output += "), \"rel\": union_distinct(" for i := range subNames { output += fmt.Sprintf("flatten(%v[**].rel), ", subNames[i]) } output += "[" + relName + "]" if len(subNames) == 0 { output += ", []" } output += ")}\n)\n" return output } func wordsToLogicalSign(element entity.QueryConstraintStruct) string { var match string switch element.DataType { case "string": switch element.MatchType { case "NEQ": match = "!=" case "contains": match = "LIKE" case "excludes": match = "NOT LIKE" default: //EQ match = "==" } case "int": switch element.MatchType { case "NEQ": match = "!=" case "GT": match = ">" case "LT": match = "<" case "GET": match = ">=" case "LET": match = "<=" default: //EQ match = "==" } default: /*bool*/ switch element.MatchType { case "NEQ": match = "!=" default: //EQ match = "==" } } return match }