Skip to content
Snippets Groups Projects
convertQuery.go 7.4 KiB
Newer Older
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)
*/

	"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")
		}
	}
	// Don't run search if we are getting empty queries from unit tests
	var tree []entity.Tree
	var topNode entity.QueryEntityStruct
	if len(JSONQuery.Entities) != 0 && len(JSONQuery.Relations) != 0 {
		tree, topNode = createHierarchy(JSONQuery)
	}

	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!")
	result := createQuery(JSONQuery, tree, topNode)
	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) *string {
	output := createLetFor("result", fmt.Sprintf("e_%v", topNode.ID), topNode.Name)
	for constraint := range topNode.Constraints {
		output += createFilter(topNode.Constraints[constraint], fmt.Sprintf("e_%v", topNode.ID))
	}
	subQuery, subName := createQueryRecurse(JSONQuery, tree, 0, topNode)
	subNames := []string{subName}
	output += subQuery
	output += createZeroFilter(append(subNames, fmt.Sprintf("e_%v", topNode.ID)))
	output += createReturn(fmt.Sprintf("e_%v", topNode.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, topNode entity.QueryEntityStruct) (string, string) {
	currentTree := tree[currentindex]
	newNode := getTreeNewNode(currentTree, tree, topNode)
	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))
	}
	output += fmt.Sprintf("FILTER r%v._from == e_%v._id AND r%v._to == e_%v._id", currentTree.Self.Rel.ID, currentTree.Self.FromNode.ID, currentTree.Self.Rel.ID, currentTree.Self.ToNode.ID)
	var subNames []string
	for i := range currentTree.Children {
		subQuery, subName := createQueryRecurse(JSONQuery, tree, currentTree.Children[i], topNode)
		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 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) 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