Skip to content
Snippets Groups Projects
convertQuery.go 9.36 KiB
Newer Older
  • Learn to ignore specific revisions
  • LoLo5689's avatar
    LoLo5689 committed
    /*
    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
    
    	// 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")
    
    	search(JSONQuery, 0)
    
    	result := createQuery(JSONQuery)
    
    LoLo5689's avatar
    LoLo5689 committed
    /*
    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's avatar
    Kieran van Gaalen committed
    	query := ""
    
    	for list := range listoflists {
    		for index := range listoflists[list] {
    			element := listoflists[list][index]
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    			fmt.Println(element.typename)
    
    			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)
    
    				function := JSONQuery.GroupBys[element.pointer]
    				query += functionToQuery(function, JSONQuery, list == len(listoflists)-1)
    			case "filter":
    				filter := JSONQuery.Filters[element.pointer]
    				query += filterToQuery(filter, JSONQuery)
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen 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 }"
    	}
    
    func entityToQuery(element entity.QueryEntityStruct, JSONQuery *entity.IncomingQueryJSON) string {
    	thisname := fmt.Sprintf("e%v", element.ID)
    	ret := createLetFor(thisname, element.Name)
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	ret += "\tRETURN x\n)\n"
    
    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]
    
    			fmt.Println(filter.FromID)
    			fmt.Println(element.FromID)
    			fmt.Println(filter.FromType)
    			fmt.Println(element.FromType)
    			if filter.FromType == element.FromType && filter.FromID == element.FromID {
    
    				ret += fmt.Sprintf("\tFOR y in f%v\n", filter.ID)
    				ydefined = true
    
    				ret += fmt.Sprintf("\tFOR z in f%v\n", filter.ID)
    				zdefined = true
    
    	if !ydefined {
    		ret += fmt.Sprintf("\tFOR y in %v%v\n", typeToPrefix(element.FromType), element.FromID)
    
    	if !zdefined {
    		ret += fmt.Sprintf("\tFOR z in %v%v\n", typeToPrefix(element.ToType), element.ToID)
    
    	ret += "\tFILTER x._from == y._id AND x._to == z._id\n"
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	ret += "\tLET nodes = APPEND([], [y, z])\n"
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	ret += "\t\"vertices\": nodes\n\t}\n)\n"
    
    func functionToQuery(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON, final bool) string {
    	ret := getTupleVar(element, JSONQuery)
    
    	if final {
    		ret += createFinalGroupBy(element, JSONQuery)
    	} else {
    		ret += createGroupBy(element, JSONQuery)
    	}
    
    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))
    
    	if element.FromType == "groupBy" {
    		ret += fmt.Sprintf("\tFILTER x.modifier %v %v\n", wordsToLogicalSign((element.MatchType)), element.Value)
    	} else {
    		ret += fmt.Sprintf("\tFILTER x.%v %v %v\n", element.Attribute, wordsToLogicalSign((element.MatchType)), element.Value)
    	}
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	ret += "\tRETURN x\n)\n"
    
    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 "groupBy":
    		return "g"
    	case "filter":
    		return "f"
    	default:
    		return ""
    
    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)
    
    	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)
    
    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 getTupleVar(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string {
    
    	thisname := fmt.Sprintf("gt%v", element.ID)
    	result += createLetFor(thisname, JSONQuery.Relations[element.RelationID].Name)
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	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._from", 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 {
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	result := "\tLET " + variableName + " = (\n\t\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 createGroupBy(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string {
    
    	thisname := fmt.Sprintf("g%v", element.ID)
    	tuplename := fmt.Sprintf("gt%v", element.ID)
    	result := createLetFor(thisname, tuplename)
    	result += createCollect(element)
    
    	result += "\tRETURN {\n\t_id: c,\n\t\"modifier\": variable_0\n\t}\n)\n"
    	return result
    }
    
    func createFinalGroupBy(element entity.QueryGroupByStruct, JSONQuery *entity.IncomingQueryJSON) string {
    	tuplename := fmt.Sprintf("gt%v", element.ID)
    	result := "FOR x IN " + tuplename + "\n"
    	result += createCollect(element)
    
    	filters := tryGetFilterFrom("groupBy", element.ID, JSONQuery)
    	if len(filters) > 0 {
    		for i := range filters {
    			result += createFilter(filters[i])
    		}
    	}
    
    	result += fmt.Sprintf("\tRETURN {\n\tname: c,\n\t%v: variable_0\n\t}\n)\n", element.GroupAttribute)
    
    func createCollect(element entity.QueryGroupByStruct) string {
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    	return "\tCOLLECT c = x.variable_0 INTO groups = x.variable_1\n\t" +
    
    		"LET variable_0 = " + element.AppliedModifier + "(groups) \n"
    
    func createFilter(filter entity.QueryFilterStruct) string {
    	return "\tFILTER variable_0 " + wordsToLogicalSign(filter.MatchType) + " " + filter.Value + " \n"
    
    Kieran van Gaalen's avatar
    Kieran van Gaalen committed
    
    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
    }