Skip to content
Snippets Groups Projects
convertQuery2.go 6.82 KiB
Newer Older
  • Learn to ignore specific revisions
  • Kieran van Gaalen's avatar
    Kieran van Gaalen 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"
    )
    
    // 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
    	if len(JSONQuery.Entities) != 0 && len(JSONQuery.Relations) != 0 {
    		tree = search(JSONQuery, 0)
    	}
    	result := createQuery(JSONQuery, tree)
    	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) *string {
    	currentTree := tree[0]
    	output := createLetFor("result", fmt.Sprintf("e_%v", currentTree.Self.In.ID), currentTree.Self.In.Name)
    	for constraint := range currentTree.Self.In.Constraints {
    		output += createFilter(currentTree.Self.In.Constraints[constraint], fmt.Sprintf("e_%v", currentTree.Self.In.ID))
    	}
    	var subNames []string
    	for i := range currentTree.Children {
    		subQuery, subName := createQueryRecurse(JSONQuery, tree, currentTree.Children[i])
    		output += subQuery
    		subNames = append(subNames, subName)
    	}
    	output += createZeroFilter(append(subNames, fmt.Sprintf("e_%v", currentTree.Self.In.ID)))
    	output += createReturn(fmt.Sprintf("e_%v", currentTree.Self.In.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) (string, string) {
    	currentTree := tree[currentindex]
    	newNode, oldNode := getTreeNewAndOldNode(currentTree, tree)
    	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))
    	}
    	if currentTree.Self.Rel.FromID == newNode.ID {
    		output += fmt.Sprintf("FILTER r%v._from == e_%v._id AND r%v._to == e_%v._id", currentTree.Self.Rel.ID, newNode.ID, currentTree.Self.Rel.ID, oldNode.ID)
    	} else {
    		output += fmt.Sprintf("FILTER r%v._from == e_%v._id AND r%v._to == e_%v._id", currentTree.Self.Rel.ID, oldNode.ID, currentTree.Self.Rel.ID, newNode.ID)
    	}
    	var subNames []string
    	for i := range currentTree.Children {
    		subQuery, subName := createQueryRecurse(JSONQuery, tree, currentTree.Children[i])
    		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 getTreeNewAndOldNode(currentTree entity.Tree, tree []entity.Tree) (entity.QueryEntityStruct, entity.QueryEntityStruct) {
    	if currentTree.Self.In.ID == tree[currentTree.Parent].Self.In.ID || currentTree.Self.In.ID == tree[currentTree.Parent].Self.Out.ID {
    		return currentTree.Self.Out, currentTree.Self.In
    	} else {
    		return currentTree.Self.In, currentTree.Self.Out
    	}
    }
    
    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
    }