-
Kieran van Gaalen authoredKieran van Gaalen authored
convertQuery.go 8.28 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 (
"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) {
// 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, 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), "", subNames)
output += ")\n"
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, indentindex int) (string, string) {
currentTree := tree[currentindex]
newNode := getTreeNewNode(currentTree, tree, topNode)
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.QueryConstraintStruct {
output += fixIndent(createFilter(currentTree.Self.Rel.QueryConstraintStruct[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), 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, 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 += ")}\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
}