diff --git a/aql/convertQuery.go b/aql/convertQuery.go index 9ee0b578ba262ea20839631c7fd0ffec6452b114..46097d4a6dfb459539ab7f237adfe89ba3780452 100644 --- a/aql/convertQuery.go +++ b/aql/convertQuery.go @@ -8,7 +8,6 @@ package aql import ( "errors" "fmt" - "strconv" "git.science.uu.nl/graphpolaris/query-conversion/entity" ) @@ -38,7 +37,7 @@ func (s *Service) ConvertQuery(JSONQuery *entity.IncomingQueryJSON) (*string, er // 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") { + if r.FromID > largestEntityID && r.FromType == "entity" || r.ToID > largestEntityID && r.ToType == "entity" { return nil, errors.New("non-exisiting entity referenced in relation") } } @@ -49,7 +48,6 @@ func (s *Service) ConvertQuery(JSONQuery *entity.IncomingQueryJSON) (*string, er return nil, errors.New("non-existing relation referenced in return") } } - search(JSONQuery) result := createQuery(JSONQuery) return result, nil } @@ -60,468 +58,179 @@ createQuery generates a query based on the json file provided Return: *string, a string containing the corresponding AQL query and an error */ func createQuery(JSONQuery *entity.IncomingQueryJSON) *string { - // Note: Case #4, where there is an edge only query (without any entity), is not supported by frontend - - // If a modifier is used, disable the limit - if len(JSONQuery.Modifiers) > 0 { - JSONQuery.Limit = -1 - } - - var ( - relationsToReturn []string - nodesToReturn []string - nodeUnion string - relationUnion string - ) - // If we've already used an entity we can set the value to true so we skip it in the result later - entityDone := make(map[int]bool) - for o := range JSONQuery.Entities { - entityDone[o] = false - } - // Loop over all relations - ret := "" - - // Add a WITH statement for entityTo - includedTypes := make(map[string]bool) - allTypes := make(map[string]bool) - for _, relation := range JSONQuery.Relations { - if relation.FromID >= 0 && relation.FromType == "entity" { - includedTypes[JSONQuery.Entities[relation.FromID].Name] = true - allTypes[JSONQuery.Entities[relation.FromID].Name] = true - - // If the type is in the entityTo it is a valid type but not yet included - if relation.ToID >= 0 && relation.ToType == "entity" { - allTypes[JSONQuery.Entities[relation.ToID].Name] = true - } - } - if relation.FromType != "entity" && relation.ToID >= 0 && relation.ToType == "entity" { - includedTypes[JSONQuery.Entities[relation.ToID].Name] = true - allTypes[JSONQuery.Entities[relation.ToID].Name] = true - } - } - - // Include all types that are not yet included - first := true - for k := range allTypes { - if !includedTypes[k] { - if first { - ret += fmt.Sprintf("WITH %v", k) - first = false - } else { - ret += fmt.Sprintf(", %v", k) - } - } - } - if !first { - ret += "\n" - } - - for i, relation := range JSONQuery.Relations { - - relationName := fmt.Sprintf("r%v", i) - - if relation.FromID >= 0 && relation.FromType == "entity" { - // if there is a from-node - // create the let for this node - // IF WE'VE ALREADY SEEN THIS ENTITY WE DON'T HAVE TO REQUERY IT, WE CAN JUST REUSE THE LET BINDING - if !entityDone[relation.FromID] { - fromName := fmt.Sprintf("n%v", relation.FromID) - - ret += *createNodeLet(&JSONQuery.Entities[relation.FromID], &fromName, &JSONQuery.Filters) - entityDone[relation.FromID] = true - } - var function *entity.QueryGroupByStruct - for _, f := range JSONQuery.GroupBys { - if i == f.RelationID { - function = &f - } + query := "LET result = {\"vertices\":[],\"edges\":[]}\n" + for list := range listoflists { + for index := range listoflists[list] { + element := listoflists[list][index] + switch element.typename { + case "entity": + entity := JSONQuery.Entities[element.pointer] + query += entityToQuery(entity, JSONQuery) + case "relation": + relation := JSONQuery.Relations[element.pointer] + query += relationToQuery(relation, JSONQuery) + case "function": + function := JSONQuery.GroupBys[element.pointer] + query += functionToQuery(function, JSONQuery, list == len(listoflists)-1) + case "filter": + filter := JSONQuery.Filters[element.pointer] + query += filterToQuery(filter, JSONQuery) } - ret += *createRelationLetWithFromEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit, function, &JSONQuery.Filters) - - } else if relation.ToID >= 0 && relation.ToType == "entity" { - fmt.Println("Joris are you a madman! How did this happen?") - // if there is only a to-node - if !entityDone[relation.ToID] { - toName := fmt.Sprintf("n%v", relation.ToID) - - ret += *createNodeLet(&JSONQuery.Entities[relation.ToID], &toName, &JSONQuery.Filters) - entityDone[relation.ToID] = true - } - - ret += *createRelationLetWithOnlyToEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit, &JSONQuery.Filters) - // Add this relation to the list - } else { - fmt.Println("Relation-only queries are currently not supported") - continue - } - - // Add this relation to the list - relationsToReturn = append(relationsToReturn, relationName) - } - - // Add node let statements for nodes that are not yet returned - // Create a set from all the entity-from's and entity-to's, to check if they are returned - nodeSet := make(map[int]bool) - for _, relation := range JSONQuery.Relations { - if relation.FromType == "entity" { - nodeSet[relation.FromID] = true - } - if relation.ToType == "entity" { - nodeSet[relation.ToID] = true } } + return &query +} - // Check if the entities to return are already returned - for _, entityIndex := range JSONQuery.Return.Entities { - if !nodeSet[entityIndex] { - // If not, return this node - name := fmt.Sprintf("n%v", entityIndex) - ret += *createNodeLet(&JSONQuery.Entities[entityIndex], &name, &JSONQuery.Filters) - - // Add this node to the list - nodesToReturn = append(nodesToReturn, name) - } - } - - if len(JSONQuery.GroupBys) == 0 { - - //If there are modifiers within the query, we run a different set of checks which focus on quantifiable aspects - if len(JSONQuery.Modifiers) > 0 { - modifier := JSONQuery.Modifiers[0] - // There is a distinction between (relations and entities) and (relations or entities) - if len(JSONQuery.Return.Relations) > 0 && len(JSONQuery.Return.Entities) > 0 { - - var pathDistinction string // .vertices or .edges - - // Select the correct addition to the return of r0[**] - if modifier.SelectedType == "entity" { - // ASSUMING THERE IS ONLY 1 RELATION - if JSONQuery.Relations[0].FromID == modifier.SelectedTypeID && JSONQuery.Relations[0].FromType == "entity" { - // This should always be 0, because that is the start of the path - pathDistinction = ".vertices[0]" - - } else { - // Otherwise take the depth.max -1 to get the last - pathDistinction = fmt.Sprintf(".vertices[%v]", JSONQuery.Relations[0].Depth.Max) - - } - } else { - pathDistinction = ".edges[**]" - } - - // Getting the attribute if there is one - if modifier.AttributeIndex != -1 { - if modifier.SelectedType == "entity" { - nodeFilters := GetFiltersOnNode(&JSONQuery.Entities[modifier.SelectedTypeID], &JSONQuery.Filters) - pathDistinction += fmt.Sprintf(".%v", (*nodeFilters)[modifier.AttributeIndex].Attribute) - - } else { - relationFilters := GetFiltersOnRelation(&JSONQuery.Relations[modifier.SelectedTypeID], &JSONQuery.Filters) - pathDistinction += fmt.Sprintf(".%v", (*relationFilters)[modifier.AttributeIndex].Attribute) - - } - } - - // If count is used it has to be replaced with Length + unique else use the modifier type - if modifier.Type == "COUNT" { - ret += fmt.Sprintf("RETURN LENGTH (unique(r0[*]%v))", pathDistinction) - - } else { - ret += fmt.Sprintf("RETURN %v (r0[*]%v)", modifier.Type, pathDistinction) - - } +func entityToQuery(element entity.QueryEntityStruct, JSONQuery *entity.IncomingQueryJSON) string { + thisname := fmt.Sprintf("e%v", element.ID) + ret := createLetFor(thisname, element.Name) + ret += "\tRETURN x\n)" + return ret +} +func relationToQuery(element entity.QueryRelationStruct, JSONQuery *entity.IncomingQueryJSON) string { + thisname := fmt.Sprintf("r%v", element.ID) + ret := createLetFor(thisname, element.Name) + filters := tryGetFilterTo("relation", element.ID, JSONQuery) + var ydefined bool = false + var zdefined bool = false + if len(filters) > 0 { + for i := range filters { + filter := filters[i] + if filter.FromID == element.FromID { + ret += fmt.Sprintf("\tFOR y in f%v\n", filter.ID) + ydefined = true } else { - // Check if the modifier is on an attribute - if modifier.AttributeIndex == -1 { - ret += fmt.Sprintf("RETURN LENGTH (n%v)", modifier.SelectedTypeID) - } else { - var attribute string - - // Selecting the right attribute from either the entity constraint or relation constraint - if modifier.SelectedType == "entity" { - nodeFilters := GetFiltersOnNode(&JSONQuery.Entities[modifier.SelectedTypeID], &JSONQuery.Filters) - attribute = (*nodeFilters)[modifier.AttributeIndex].Attribute - - } else { - relationFilters := GetFiltersOnRelation(&JSONQuery.Relations[modifier.SelectedTypeID], &JSONQuery.Filters) - attribute = (*relationFilters)[modifier.AttributeIndex].Attribute - - } - - // If count is used it has to be replaced with Length + unique else use the modifier type - if modifier.Type == "COUNT" { - ret += fmt.Sprintf("RETURN LENGTH (unique(n%v[*].%v))", modifier.SelectedTypeID, attribute) - - } else { - ret += fmt.Sprintf("RETURN %v (n%v[*].%v)", modifier.Type, modifier.SelectedTypeID, attribute) - - } - } + ret += fmt.Sprintf("\tFOR z in f%v\n", filter.ID) + zdefined = true } - - } else { - - // Create UNION statements that create unique lists of all the nodes and relations - // Thus removing all duplicates - nodeUnion = "\nLET nodes = first(RETURN UNION_DISTINCT(" - for _, relation := range relationsToReturn { - nodeUnion += fmt.Sprintf("flatten(%v[**].vertices), ", relation) - } - - for _, node := range nodesToReturn { - nodeUnion += fmt.Sprintf("%v,", node) - } - nodeUnion += "[],[]))\n" - - relationUnion = "LET edges = first(RETURN UNION_DISTINCT(" - for _, relation := range relationsToReturn { - relationUnion += fmt.Sprintf("flatten(%v[**].edges), ", relation) - } - relationUnion += "[],[]))\n" - - ret += nodeUnion + relationUnion - ret += "RETURN {\"vertices\":nodes, \"edges\":edges }" - } - - } else { - ret += createTableWithFunctions(&JSONQuery.GroupBys, &JSONQuery.Relations, &JSONQuery.Entities, &JSONQuery.Filters) } - - return &ret -} - -/* -GetFiltersOnNode gets all the filters on a certain node - node: *entity.QueryEntityStruct, node is an entityStruct containing the information of a single node, - JSONQuery: *entity.IncomingQueryJSON, the full incoming query, - Return: *[]entity.QueryFilterStruct, a list of filters attached to the node -*/ -func GetFiltersOnNode(node *entity.QueryEntityStruct, filters *[]entity.QueryFilterStruct) *[]entity.QueryFilterStruct { - var retval []entity.QueryFilterStruct - for _, filter := range *filters { - if filter.FilteredType == "entity" && filter.FilteredID == node.ID { - retval = append(retval, filter) - } + if !ydefined { + ret += fmt.Sprintf("\tFOR y in %v%v\n", typeToPrefix(element.FromType), element.FromID) } - return &retval -} - -func GetFiltersOnRelation(relation *entity.QueryRelationStruct, filters *[]entity.QueryFilterStruct) *[]entity.QueryFilterStruct { - var retval []entity.QueryFilterStruct - for _, filter := range *filters { - if filter.FilteredType == "entity" && filter.FilteredID == relation.ID { - retval = append(retval, filter) - } + if !zdefined { + ret += fmt.Sprintf("\tFOR z in %v%v\n", typeToPrefix(element.ToType), element.ToID) } - return &retval + ret += "\tFILTER x._from == y._id AND x._to == z._id\n" + ret += "\tLET nodes = APPEND(result.vertices, [y, z])\n" + ret += "\tRETURN DISTINCT {\n" + ret += "\t\"edges\": x,\n" + ret += "\t\"vertices\": nodes\n\t}\n)" + return ret } -func GetFiltersOnFunction(function *entity.QueryGroupByStruct, filters *[]entity.QueryFilterStruct) *[]entity.QueryFilterStruct { - var retval []entity.QueryFilterStruct - for _, filter := range *filters { - if filter.FilteredType == "entity" && filter.FilteredID == function.ID { - retval = append(retval, filter) - } - } - return &retval +func functionToQuery(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON, final bool) string { + ret := getTupleVar(element, JSONQuery) + ret += createGroupBy(element, JSONQuery, final) + return ret } -/* -createNodeLet generates a 'LET' statement for a node related query - node: *entity.QueryEntityStruct, node is an entityStruct containing the information of a single nod, - name: *string, is the autogenerated name of the node consisting of "n" + the index of the node, - Return: *string, a string containing a single LET-statement in AQL -*/ -func createNodeLet(node *entity.QueryEntityStruct, name *string, filters *[]entity.QueryFilterStruct) *string { - header := createLetFor(*name, "x", node.Name) - footer := "\tRETURN x\n)\n" - attachedfilters := GetFiltersOnNode(node, filters) - constraints := *createConstraintStatements(attachedfilters, "x", false) - - ret := header + constraints + footer - return &ret +func filterToQuery(element entity.QueryFilterStruct, JSONQuery *entity.IncomingQueryJSON) string { + thisname := fmt.Sprintf("f%v", element.ID) + ret := createLetFor(thisname, fmt.Sprintf("%v%v", typeToPrefix(element.FromType), element.FromID)) + ret += fmt.Sprintf("\tFILTER x.%v %v %v\n", element.Attribute, wordsToLogicalSign((element.MatchType)), element.Value) + ret += "\tRETURN x\n)" + return ret } -/* -createRelationLetWithFromEntity generates a 'LET' statement for relations with an 'EntityFrom' property and optionally an 'EntitiyTo' property - relation: *entity.QueryRekationStruct, relation is a relation struct containing the information of a single relation, - name: string, is the autogenerated name of the node consisting of "r" + the index of the relation, - entities: *[]entity.QueryEntityStrucy, is a list of entityStructs that are needed to form the relation LET-statement, - Return: *string, a string containing a single LET-statement in AQL -*/ -// JSON FORMAT CHANGES COMMENT -// THIS ONLY GETS CALLED WHEN THE RELATION FROM HAS ALREADY BEEN VERIFIED TO BE AN ENTITY -// THAT IS WHY WE CAN USE FROMID WITHOUT CHECKING IF IT IS AN ENTITY BEFOREHAND -func createRelationLetWithFromEntity(relation *entity.QueryRelationStruct, name string, entities *[]entity.QueryEntityStruct, limit int, function *entity.QueryGroupByStruct, filters *[]entity.QueryFilterStruct) *string { - header := createLetFor(name, "x", "n"+strconv.Itoa(relation.FromID)) - forStatement := fmt.Sprintf("\tFOR v, e, p IN %v..%v OUTBOUND x %s \n", relation.Depth.Min, relation.Depth.Max, relation.Name) - - // Guarantees that there is no path returned with a duplicate edge - // This way there are no cycle paths possible, TODO: more research about this needed - optionStmtn := "\tOPTIONS { uniqueEdges: \"path\" }\n" - - vFilterStmnt := "" - if relation.ToType == "entity" { - // If there is a to-node, generate the filter statement - toFilters := GetFiltersOnNode(&(*entities)[relation.ToID], filters) - vFilterStmnt += *createConstraintStatements(toFilters, "v", false) - } - relationFilters := GetFiltersOnRelation(relation, filters) - relationFilterStmnt := *createConstraintStatements(relationFilters, "p", true) - - // Dont use a limit on quantifing queries - footer := "" - if limit != -1 { - footer += fmt.Sprintf("\tLIMIT %v \n", limit) - } - if function != nil { - footer += "RETURN DISTINCT v )\n" - } else { - footer += "RETURN DISTINCT p )\n" - } - - ret := header + forStatement + optionStmtn + vFilterStmnt + relationFilterStmnt + footer - return &ret +func createLetFor(variableName string, enumerableName string) string { + return "LET " + variableName + " = (\n\tFOR x IN " + enumerableName + "\n" } -/* -createRelationLetWithOnlyToEntity generates a 'LET' statement for relations with only an 'EntityTo' property - relation: *entity.QueryRelationStruct, relation is a relation struct containing the information of a single relation, - name: string, is the autogenerated name of the node consisting of "r" + the index of the relation, - entities: *[]entity.QueryEntityStruct, is a list of entityStructs that are needed to form the relation LET-statement, - Return: *string, a string containing a single LET-statement in AQL -*/ -// JSON FORMAT CHANGES COMMENT -// THIS ONLY GETS CALLED WHEN THE RELATION FROM HAS ALREADY BEEN VERIFIED TO BE AN ENTITY -// THAT IS WHY WE CAN USE FROMID WITHOUT CHECKING IF IT IS AN ENTITY BEFOREHAND -func createRelationLetWithOnlyToEntity(relation *entity.QueryRelationStruct, name string, entities *[]entity.QueryEntityStruct, limit int, filters *[]entity.QueryFilterStruct) *string { - header := createLetFor(name, "x", "n"+strconv.Itoa(relation.ToID)) - forStatement := fmt.Sprintf("\tFOR v, e, p IN %v..%v INBOUND x %s \n", relation.Depth.Min, relation.Depth.Max, relation.Name) - - // Guarantees that there is no path returned with a duplicate edge - // This way there are no cycle paths possible, TODO: more research about this needed - optionStmtn := "\tOPTIONS { uniqueEdges: \"path\" }\n" - - relationFilters := GetFiltersOnRelation(relation, filters) - relationFilterStmnt := *createConstraintStatements(relationFilters, "p", true) - - // Dont use a limit on quantifing queries - footer := "" - if limit != -1 { - footer += fmt.Sprintf("\tLIMIT %v \n", limit) +func typeToPrefix(pillType string) string { + switch pillType { + case "entity": + return "e" + case "relation": + return "r" + case "groupBy": + return "g" + case "filter": + return "f" + default: + return "" } - footer += "RETURN DISTINCT p )\n" - - ret := header + forStatement + optionStmtn + relationFilterStmnt + footer - return &ret -} - -func createLetFor(variableName string, iteratorName string, enumerableName string) string { - return "LET " + variableName + " = (\n\tFOR " + iteratorName + " IN " + - enumerableName + " \n" -} - -type variableNameGeneratorToken struct { - token int } -func newVariableNameGeneratorToken() *variableNameGeneratorToken { - v := variableNameGeneratorToken{token: 0} - return &v -} - -func variableNameGenerator(vngt *variableNameGeneratorToken) string { - result := "variable_" + strconv.Itoa(vngt.token) - vngt.token++ - return result -} - -func createTableWithFunctions(functions *[]entity.QueryGroupByStruct, relations *[]entity.QueryRelationStruct, entities *[]entity.QueryEntityStruct, filters *[]entity.QueryFilterStruct) string { - result := "" - v := newVariableNameGeneratorToken() - for _, function := range *functions { - currRelation := (*relations)[function.RelationID] - if true { - a := variableNameGenerator(v) - b := variableNameGenerator(v) - c := variableNameGenerator(v) - d := variableNameGenerator(v) - e := variableNameGenerator(v) - f := variableNameGenerator(v) - g := variableNameGenerator(v) - h := variableNameGenerator(v) - rName := fmt.Sprintf("r%v", function.RelationID) - nName := fmt.Sprintf("n%v", function.GroupID) - result += createTupleVariable(a, b, c, d, e, f, g, currRelation, rName, nName, function) - result += createFunction(function, a, f, g, h, filters) +func tryGetFilterTo(toType string, toID int, JSONQuery *entity.IncomingQueryJSON) []entity.QueryFilterStruct { + var list []entity.QueryFilterStruct + for i := range JSONQuery.Filters { + filter := JSONQuery.Filters[i] + if filter.ToType == toType && filter.ToID == toID { + list = append(list, filter) } - } - result += "RETURN {" - if len(*functions) > 1 { - for l := 0; l < len(*functions)-1; l++ { - result += "function_" + strconv.Itoa((*functions)[l].ID) + ", " + return list +} + +func tryGetFilterFrom(fromType string, fromID int, JSONQuery *entity.IncomingQueryJSON) []entity.QueryFilterStruct { + var list []entity.QueryFilterStruct + for i := range JSONQuery.Filters { + filter := JSONQuery.Filters[i] + if filter.FromType == fromType && filter.FromID == fromID { + list = append(list, filter) } } - result += "function_" + strconv.Itoa((*functions)[len(*functions)-1].ID) + "}" - return result + return list } -func createTupleVariable(a string, b string, c string, d string, e string, f string, g string, currRelation entity.QueryRelationStruct, forName1 string, forName2 string, function entity.QueryGroupByStruct) string { - result := "" - result += createLetFor(a, "r", currRelation.Name) - result += createSubVariable(b, c, forName1, "_id", "r._to", function.ByAttribute) - result += createSubVariable(d, e, forName2, "_id", "r._from", function.GroupAttribute) - result += createTupleReturn(c, e, "\""+f+"\"", "\""+g+"\"") - return result +func wordsToLogicalSign(word string) string { + if word == "LT" { + return "<" + } else if word == "LTE" { + return "<=" + } else if word == "EQ" { + return "==" + } else if word == "GTE" { + return ">=" + } else if word == "NEQ" { + return "!=" + } else { + return ">" + } } -func createFunction(function entity.QueryGroupByStruct, variableName1 string, variableName2 string, variableName3 string, variableName4 string, filters *[]entity.QueryFilterStruct) string { +func getTupleVar(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string { result := "" - result += createLetFor("function_"+strconv.Itoa(function.ID), "r", variableName1) - result += createCollect("c", "r."+variableName2, "r."+variableName3, variableName4, function) - functionFilters := GetFiltersOnFunction(&function, filters) - if len(*functionFilters) > 0 { - result += createFilter(variableName4, function, filters) - } - result += createTupleReturn("c", variableName4, function.ByAttribute, function.AppliedModifier+"_"+function.GroupAttribute) + thisname := fmt.Sprintf("gt%v", element.ID) + result += createLetFor(thisname, JSONQuery.Relations[element.RelationID].Name) + result += createSubVariable("variable_0", "variable_1", fmt.Sprintf("r%v", element.RelationID), "_id", "r._to", element.ByAttribute) + result += createSubVariable("variable_2", "variable_3", fmt.Sprintf("%v%v", typeToPrefix(element.GroupType), element.GroupID), "_id", "r._to", element.GroupAttribute) + result += "\tRETURN {\n\t\t\"variable_0\": variable_1, \n\t\t\"variable_1\": variable_3\n\t}\n)\n" return result } func createSubVariable(variableName string, variableName2 string, forName string, filter1 string, filter2 string, returnValue string) string { - result := "\t" - result += createLetFor(variableName, "c", forName) - return result + "\t\tFILTER c." + filter1 + " == " + filter2 + "\n\t\tRETURN c." + returnValue + "\n\t) " + + result := "\tLET " + variableName + " = (\n\tFOR y IN " + forName + "\n" + return result + "\t\tFILTER y." + filter1 + " == " + filter2 + "\n\t\tRETURN y." + returnValue + "\n\t) " + "\n\tLET " + variableName2 + " = " + variableName + "[0] \n" } -func createTupleReturn(assignVariable1 string, assignVariable2 string, variableName1 string, variableName2 string) string { - return "\tRETURN {\n\t\t" + variableName1 + " : " + assignVariable1 + ", \n\t\t" + - "" + variableName2 + " : " + assignVariable2 + "\n\t}\n) \n" -} - -func createCollect(collectionName string, variableName1 string, variableName2 string, variableName3 string, function entity.QueryGroupByStruct) string { - return "\tCOLLECT " + collectionName + " = " + variableName1 + " INTO groups = " + variableName2 + " \n\t\t" + - "LET " + variableName3 + " = " + function.AppliedModifier + "(groups) \n\t" +func createGroupBy(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON, final bool) string { + thisname := fmt.Sprintf("g%v", element.ID) + tuplename := fmt.Sprintf("gt%v", element.ID) + result := createLetFor(thisname, tuplename) + result += createCollect(element) + filters := tryGetFilterFrom("groupBy", element.ID, JSONQuery) + if len(filters) > 0 { + for i := range filters { + result += createFilter(filters[i]) + } + } + if final { + result += fmt.Sprintf("\tRETURN {\n\tname: c,\n\t%v: variable_0\n\t}\n)", element.GroupAttribute) + } else { + result += "\tRETURN c\n)" + } + return result } -func createFilter(variableName string, function entity.QueryGroupByStruct, filters *[]entity.QueryFilterStruct) string { - functionFilters := GetFiltersOnFunction(&function, filters) - return "FILTER " + variableName + " " + wordsToLogicalSign((*functionFilters)[0].MatchType) + " " + (*functionFilters)[0].Value + " \n\t" +func createCollect(element entity.QueryGroupByStruct) string { + return "\tCOLLECT c = x.variable_0 INTO groups = x.variable_1\n\t\t" + + "LET variable_0 = " + element.AppliedModifier + "(groups) \n" } -func wordsToLogicalSign(word string) string { - if word == "LT" { - return "<" - } else if word == "LTE" { - return "<=" - } else if word == "EQ" { - return "==" - } else if word == "GTE" { - return ">=" - } else { - return ">" - } +func createFilter(filter entity.QueryFilterStruct) string { + return "\tFILTER variable_0 " + wordsToLogicalSign(filter.MatchType) + " " + filter.Value + " \n" } diff --git a/aql/convertQuery2.go b/aql/convertQuery2.go deleted file mode 100644 index 28388d557957e8d6d486258214f574d137a17930..0000000000000000000000000000000000000000 --- a/aql/convertQuery2.go +++ /dev/null @@ -1,156 +0,0 @@ -/* -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" - - "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.EntityFrom > largestEntityID || r.EntityTo > largestEntityID { - 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") - } - } - result := createQuery(JSONQuery) - 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) *string { - query := "" - for list := range listoflists { - for index := range listoflists[list] { - element := listoflists[list][index] - switch element.typename { - case "entity": - entity := JSONQuery.Entities[element.pointer] - query += entityToQuery(entity, JSONQuery) - case "relation": - relation := JSONQuery.Relations[element.pointer] - query += relationToQuery(relation, JSONQuery) - case "function": - function := JSONQuery.GroupBys[element.pointer] - query += functionToQuery(function, JSONQuery) - case "filter": - filter := JSONQuery.Filters[element.pointer] - query += filterToQuery(filter, JSONQuery) - } - } - } - return &query -} - -func entityToQuery(element entity.QueryEntityStruct, JSONQuery *entity.IncomingQueryJSON) string { - thisname := fmt.Sprintf("e%v", element.ID) - ret := createLetFor(thisname, element.Name) - ret += "\tRETURN x\n)" - return ret -} - -func relationToQuery(element entity.QueryRelationStruct, JSONQuery *entity.IncomingQueryJSON) string { - thisname := fmt.Sprintf("r%v", element.ID) - ret := createLetFor(thisname, element.Name) - ret += fmt.Sprintf("\tFOR y in %v%v\n") - return ret -} - -func functionToQuery(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string { - thisname := fmt.Sprintf("g%v", element.ID) - element - ret := createLetFor(thisname) - return ret -} - -func filterToQuery(element entity.QueryFilterStruct, JSONQuery *entity.IncomingQueryJSON) string { - thisname := fmt.Sprintf("f%v", element.ID) - ret := createLetFor(thisname, fmt.Sprintf("%v%v", typeToPrefix(element.FromType), element.FromID)) - ret += fmt.Sprintf("\tFILTER x.%v %v %v\n", element.Attribute, wordsToLogicalSign((element.MatchType)), element.Value) - ret += "\tRETURN x\n)" - return ret -} - -func createLetFor(variableName string, enumerableName string) string { - return "LET " + variableName + " = (\n\tFOR x IN " + enumerableName + "\n" -} - -func typeToPrefix(pillType string) string { - switch pillType { - case "entity": - return "e" - case "relation": - return "r" - case "function": - return "g" - case "filter": - return "f" - default: - return "" - } -} - -func tryGetFilter(toType string, toID int, JSONQuery *entity.IncomingQueryJSON) *entity.QueryFilterStruct { - for i := range JSONQuery.Filters { - filter := JSONQuery.Filters[i] - if filter.ToType == toType && filter.ToID == toID { - return &filter - } - } - return nil -} - -func wordsToLogicalSign(word string) string { - if word == "LT" { - return "<" - } else if word == "LTE" { - return "<=" - } else if word == "EQ" { - return "==" - } else if word == "GTE" { - return ">=" - } else if word == "NEQ" { - return "!=" - } else { - return ">" - } -} diff --git a/test.json b/test.json index c863d48f8f6cbd1cfa0649d4fa9224262cb77d31..a8d15a4a56a72376eeb2563508d1f19847cb469d 100644 --- a/test.json +++ b/test.json @@ -37,8 +37,10 @@ "filters": [ { "ID": 0, - "filteredType": "entity", - "filteredID": 0, + "fromType": "entity", + "fromID": 0, + "toType": "entity", + "toID": 0, "attribute": "age", "dataType": "int", "matchType": "NEQ", @@ -48,8 +50,10 @@ }, { "ID": 1, - "filteredType": "relation", - "filteredID": 0, + "fromType": "relation", + "fromID": 0, + "toType": "relation", + "toID": 0, "attribute": "isChairman", "dataType": "bool", "matchType": "exact",