Skip to content
Snippets Groups Projects
convertQuery.go 10.52 KiB
/*
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 (
	//"encoding/json"
	"encoding/json"
	"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) {
	// TODO: MICHAEL WANT A SINGLE ENTITY TO RETURN A SUMMARY OF ATTRIBUTE VALUES (HISTOGRAM THINGIES)
	// Check to make sure all indexes exist
	// The count of entities
	entityCount := len(JSONQuery.Entities)
	// The count of relations
	relationCount := len(JSONQuery.Relations)
	// There are no entities or relations, our query is empty
	if entityCount <= 0 && relationCount <= 0 {
		fmt.Println("Empty query sent, returning default response")
		return defaultReturn()
	}
	// There are no enities or there are no relations
	if entityCount <= 0 || relationCount <= 0 {
		return nil, errors.New("no relations or entities sent")
	}

	potentialErrors := entity.ValidateStruct(*JSONQuery)
	// If we find the JSONQuery to be invalid we return a error
	if len(potentialErrors) != 0 {
		for _, err := range potentialErrors {
			fmt.Printf("err: %v\n", err)
		}
		return nil, errors.New("JSONQuery invalid")
	}

	entityMap, relationMap, _ := entity.FixIndices(JSONQuery)

	var tree []entity.Tree
	var topNode entity.QueryEntityStruct
	if len(JSONQuery.Entities) != 0 && len(JSONQuery.Relations) != 0 {
		tree, topNode = createHierarchy(JSONQuery, entityMap, relationMap)
	}

	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!")
	}
	fuckshitprinter, _ := json.Marshal(tree)
	fmt.Println(string(fuckshitprinter))
	result := createQuery(JSONQuery, tree, topNode, entityMap, relationMap)
	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, entityMap map[int]int, relationMap map[int]int) *string {
	modName := ""
	if len(JSONQuery.Modifiers) > 0 {
		modifier := JSONQuery.Modifiers[0]
		fmt.Println(modifier.SelectedType + ", " + strconv.Itoa(modifier.SelectedTypeID))
		if modifier.SelectedType == "entity" && modifier.SelectedTypeID == topNode.ID {
			modName = fmt.Sprintf("e_%v", topNode.ID)
		}
	}
	output := createLetFor("result", fmt.Sprintf("e_%v", topNode.ID), topNode.Name, 0)
	for constraint := range topNode.Constraints {
		output += createFilter(topNode.Constraints[constraint], fmt.Sprintf("e_%v", topNode.ID))
	}
	subQuery, subName := createQueryRecurse(JSONQuery, tree, 0, topNode, 1)
	subNames := []string{subName}
	output += subQuery
	output += createZeroFilter(append(subNames, fmt.Sprintf("e_%v", topNode.ID)))
	output += createReturn(fmt.Sprintf("e_%v", topNode.ID), "", modName, subNames)
	output += ")\n"
	//output += "LET nodes = union_distinct(flatten(result[**].nodes),[])\nLET edges = union_distinct(flatten(result[**].rel),[])\nLET modif = union_distinct(flatten(result[**].mod),[])\n"
	if len(JSONQuery.Modifiers) == 0 {
		output += "LET nodes = union_distinct(flatten(result[**].nodes),[])\nLET edges = union_distinct(flatten(result[**].rel),[])\nRETURN {\"vertices\":nodes,\"edges\":edges}"
	} else {
		output += "LET modif = union_distinct(flatten(result[**].mod),[])\n"
		modifier := JSONQuery.Modifiers[0]
		if modifier.AttributeIndex == -1 {
			output += "RETURN " + getModifierType(modifier) + "(modif)"
		} else {
			var attribute string
			// Selecting the right attribute from either the entity constraint or relation constraint
			if modifier.SelectedType == "entity" {
				attribute = JSONQuery.Entities[entityMap[modifier.SelectedTypeID]].Constraints[modifier.AttributeIndex].Attribute
			} else {
				attribute = JSONQuery.Relations[relationMap[modifier.SelectedTypeID]].Constraints[modifier.AttributeIndex].Attribute
			}
			output += "RETURN " + getModifierType(modifier) + "(modif[**]." + attribute + ")"
		}
	}
	return &output
}

func createQueryRecurse(JSONQuery *entity.IncomingQueryJSON, tree []entity.Tree, currentindex int, topNode entity.QueryEntityStruct, indentindex int) (string, string) {
	currentTree := tree[currentindex]
	newNode := getTreeNewNode(currentTree, tree, topNode)
	modName := ""
	if len(JSONQuery.Modifiers) > 0 {
		modifier := JSONQuery.Modifiers[0]
		if modifier.SelectedType == "entity" && modifier.SelectedTypeID == newNode.ID {
			modName = fmt.Sprintf("e_%v", newNode.ID)
		} else if modifier.SelectedType == "relation" && modifier.SelectedTypeID == currentTree.Self.Rel.ID {
			modName = fmt.Sprintf("r%v", currentTree.Self.Rel.ID)
		}
	}
	output := ""
	output += fixIndent(createLetFor(fmt.Sprintf("e%v", newNode.ID), fmt.Sprintf("e_%v", newNode.ID), newNode.Name, indentindex), indentindex)
	output += fixIndent(fmt.Sprintf("\tFOR r%v IN %v\n", currentTree.Self.Rel.ID, currentTree.Self.Rel.Name), indentindex)
	for constraint := range newNode.Constraints {
		output += fixIndent(createFilter(newNode.Constraints[constraint], fmt.Sprintf("e_%v", newNode.ID)), indentindex)
	}
	for constraint := range currentTree.Self.Rel.Constraints {
		output += fixIndent(createFilter(currentTree.Self.Rel.Constraints[constraint], fmt.Sprintf("r%v", currentTree.Self.Rel.ID)), indentindex)
	}
	output += fixIndent(fmt.Sprintf("\tFILTER r%v._from == e_%v._id AND r%v._to == e_%v._id\n", currentTree.Self.Rel.ID, currentTree.Self.FromNode.ID, currentTree.Self.Rel.ID, currentTree.Self.ToNode.ID), indentindex)
	var subNames []string
	for i := range currentTree.Children {
		subQuery, subName := createQueryRecurse(JSONQuery, tree, currentTree.Children[i], topNode, indentindex+1)
		output += subQuery
		subNames = append(subNames, subName)
	}
	output += fixIndent(createZeroFilter(append(subNames, fmt.Sprintf("e_%v", newNode.ID), fmt.Sprintf("r%v", currentTree.Self.Rel.ID))), indentindex)
	output += fixIndent(createReturn(fmt.Sprintf("e_%v", newNode.ID), fmt.Sprintf("r%v", currentTree.Self.Rel.ID), modName, subNames), indentindex)
	output += fixIndent(")\n", indentindex)
	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, indentindex int) string {
	output := "LET " + variableName + " = (\n"
	for i := 0; i < indentindex; i++ {
		output += "\t"
	}
	output += "\tFOR " + forName + " IN " + enumerableName + "\n"
	return output
}

func createFilter(constraint entity.QueryConstraintStruct, filtered string) string {
	output := "\tFILTER " + filtered + "." + constraint.Attribute + " " + wordsToLogicalSign(constraint)
	if constraint.DataType == "string" {
		output += " \""
	} else {
		output += " "
	}
	if constraint.MatchType == "contains" {
		output += "%"
	}
	output += constraint.Value
	if constraint.MatchType == "contains" {
		output += "%"
	}
	if constraint.DataType == "string" {
		output += "\" "
	} else {
		output += " "
	}
	output += " \n"
	return output
}

func createZeroFilter(subNames []string) string {
	output := "\tFILTER"
	for i := range subNames {
		output += fmt.Sprintf(" length(%v) != 0", subNames[i])
		if i < len(subNames)-1 {
			output += " AND"
		}
	}
	output += "\n"
	return output
}

func createReturn(nodeName string, relName string, modName string, subNames []string) string {
	output := "\tRETURN {\"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 += "), \"mod\": union_distinct("
	for i := range subNames {
		output += fmt.Sprintf("flatten(%v[**].mod), ", subNames[i])
	}
	output += "[" + modName + "]"
	if len(subNames) == 0 {
		output += ", []"
	}
	output += ")}\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
}

func fixIndent(input string, indentCount int) string {
	output := ""
	for i := 0; i < indentCount; i++ {
		output += "\t"
	}
	output += input
	return output
}

func getModifierType(modifier entity.QueryModifierStruct) string {
	if modifier.Type == "COUNT" {
		return "LENGTH"
	}
	return modifier.Type
}

func defaultReturn() (*string, error) {
	defaultReturn := `LET nodes = first(RETURN UNION_DISTINCT([],[]))
		LET edges = first(RETURN UNION_DISTINCT([],[]))
		RETURN {"vertices":nodes, "edges":edges }`
	return &defaultReturn, nil
}