Newer
Older
/*
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"
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
largestEntityID := len(JSONQuery.Entities) - 1
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
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 {
Kieran van Gaalen
committed
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 {
return nil, errors.New("non-existing relation referenced in return")
result := createQuery(JSONQuery)
/*
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 {
Kieran van Gaalen
committed
for list := range listoflists {
for index := range listoflists[list] {
element := listoflists[list][index]
Kieran van Gaalen
committed
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)
Geurtjens,D. (Douwe Geurtjens)
committed
}
unusedRelations := findUnusedRelations(JSONQuery)
if len(unusedRelations) >= 0 {
query += "LET nodes = first(RETURN UNION_DISTINCT("
for i := range unusedRelations {
query += "flatten(" + unusedRelations[i] + "[**].vertices),"
}
query += "[],[]))\n"
query += "LET edges = first(RETURN UNION_DISTINCT("
for i := range unusedRelations {
query += "flatten(" + unusedRelations[i] + "[**].edges),"
}
query += "[],[]))\nRETURN {\"vertices\":nodes, \"edges\":edges }"
}
Kieran van Gaalen
committed
return &query
}
Kieran van Gaalen
committed
func entityToQuery(element entity.QueryEntityStruct, JSONQuery *entity.IncomingQueryJSON) string {
thisname := fmt.Sprintf("e%v", element.ID)
ret := createLetFor(thisname, element.Name)
Kieran van Gaalen
committed
return ret
}
Kieran van Gaalen
committed
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
Kieran van Gaalen
committed
ret += fmt.Sprintf("\tFOR z in f%v\n", filter.ID)
zdefined = true
Kieran van Gaalen
committed
if !ydefined {
ret += fmt.Sprintf("\tFOR y in %v%v\n", typeToPrefix(element.FromType), element.FromID)
Geurtjens,D. (Douwe Geurtjens)
committed
}
Kieran van Gaalen
committed
if !zdefined {
ret += fmt.Sprintf("\tFOR z in %v%v\n", typeToPrefix(element.ToType), element.ToID)
Geurtjens,D. (Douwe Geurtjens)
committed
}
Kieran van Gaalen
committed
ret += "\tFILTER x._from == y._id AND x._to == z._id\n"
Kieran van Gaalen
committed
ret += "\tRETURN DISTINCT {\n"
ret += "\t\"edges\": x,\n"
Kieran van Gaalen
committed
return ret
Geurtjens,D. (Douwe Geurtjens)
committed
}
Kieran van Gaalen
committed
func functionToQuery(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON, final bool) string {
ret := getTupleVar(element, JSONQuery)
ret += createGroupBy(element, JSONQuery, final)
return ret
Geurtjens,D. (Douwe Geurtjens)
committed
}
Kieran van Gaalen
committed
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)
Kieran van Gaalen
committed
return ret
Kieran van Gaalen
committed
func createLetFor(variableName string, enumerableName string) string {
return "LET " + variableName + " = (\n\tFOR x IN " + enumerableName + "\n"
Kieran van Gaalen
committed
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 ""
}
Kieran van Gaalen
committed
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)
}
}
Kieran van Gaalen
committed
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)
}
}
Kieran van Gaalen
committed
return list
}
Kieran van Gaalen
committed
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 ">"
}
Kieran van Gaalen
committed
func getTupleVar(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string {
Kieran van Gaalen
committed
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[**].vertices[1]", element.RelationID), "_id", "x._to", "_id")
result += createSubVariable("variable_2", "variable_3", fmt.Sprintf("%v%v", typeToPrefix(element.GroupType), element.GroupID), "_id", "x._to", element.GroupAttribute)
Kieran van Gaalen
committed
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 := "\tLET " + variableName + " = (\n\t\tFOR y IN " + forName + "\n"
Kieran van Gaalen
committed
return result + "\t\tFILTER y." + filter1 + " == " + filter2 + "\n\t\tRETURN y." + returnValue + "\n\t) " +
"\n\tLET " + variableName2 + " = " + variableName + "[0] \n"
}
Kieran van Gaalen
committed
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)\n", element.GroupAttribute)
Kieran van Gaalen
committed
} else {
Kieran van Gaalen
committed
}
return result
Kieran van Gaalen
committed
func createCollect(element entity.QueryGroupByStruct) string {
return "\tCOLLECT c = x.variable_0 INTO groups = x.variable_1\n\t" +
Kieran van Gaalen
committed
"LET variable_0 = " + element.AppliedModifier + "(groups) \n"
Kieran van Gaalen
committed
func createFilter(filter entity.QueryFilterStruct) string {
return "\tFILTER variable_0 " + wordsToLogicalSign(filter.MatchType) + " " + filter.Value + " \n"
}
func findUnusedRelations(JSONQuery *entity.IncomingQueryJSON) []string {
var unused []string
for i := range JSONQuery.Relations {
relationUnused := true
relation := JSONQuery.Relations[i]
for j := range JSONQuery.GroupBys {
groupBy := JSONQuery.GroupBys[j]
if groupBy.RelationID == relation.ID {
relationUnused = false
}
}
if relationUnused {
unused = append(unused, fmt.Sprintf("r%v", relation.ID))
}
}
return unused
}