diff --git a/cmd/query-service/main.go b/cmd/query-service/main.go index 39009d83626c5fdf121a6cd894b09b46396522e2..68159a0559a89b038bfe259efa5bdf4d2798c19d 100644 --- a/cmd/query-service/main.go +++ b/cmd/query-service/main.go @@ -27,6 +27,7 @@ func main() { // MARK: Create relevant services for consuming a message convertQueryService := convertquery.NewService() + requestSenderService := request.NewService() consumeService := consume.NewService(aliceBroker, produceService, convertQueryService, requestSenderService) diff --git a/internal/usecases/convertquery/aql.go b/internal/usecases/convertquery/aql.go index bc33f4aae307bcb01ead940889e184cc6417e99c..4535d00f821b9e0e5ca57b59caa7ffa208c9039a 100644 --- a/internal/usecases/convertquery/aql.go +++ b/internal/usecases/convertquery/aql.go @@ -3,8 +3,6 @@ package convertquery import ( "encoding/json" "fmt" - "strconv" - "strings" ) // Service is a model for the convertquery use case @@ -16,79 +14,6 @@ func NewService() *Service { return &Service{} } -/* -// Query format for exporting to JSON -export type JSONFormat = { - Return: { - Entities: number[]; - Relations: number[]; - }; - Entities: Entity[]; - Relations: Relation[]; -}; -type Entity = { - Type: string; - Constraints: Constraint[]; -}; -type Relation = { - Type: string; - EntityFrom: number; - EntityTo: number; - Depth: { min: number; max: number }; - Constraints: Constraint[]; -}; -type Constraint = { - Attribute: string; - Value: string; - - // Constraint datatypes - // text MatchTypes: exact/contains/startswith/endswith - // number MatchTypes: GT/LT/EQ - // bool MatchTypes: EQ/NEQ - DataType: string; - MatchType: string; -}; - -{ - "Return":{ - "Entities":[ - 0 - ], - "Relations":[ - 0 - ] - }, - "Entities":[ - { - "Type":"Airport", - "Constraints":[ - { - "Attribute":"month", - "Value":"8", - "DataType":"text", - "MatchType":"exact" - } - ] - } - ], - "Relations":[ - { - "Type":"Flight", - "Depth":{ - "min":1, - "max":3 - }, - "EntityFrom":0, - "EntityTo":-1, - "Constraints":[ - - ] - } - ] -} - -*/ - // Constraint datatypes // text MatchTypes: exact/contains/startswith/endswith // number MatchTypes: GT/LT/EQ @@ -98,9 +23,9 @@ type Constraint = { // Struct used for JSON conversion type parsedJSON struct { - Return returnStruct - Entities []entityStruct - Relations []relationStruct + Return returnStruct //`json:"return"` + Entities []entityStruct //`json:"entities"` + Relations []relationStruct //`json:"relations"` } type returnStruct struct { @@ -145,86 +70,129 @@ func (s *Service) ConvertQuery(jsonMsg *[]byte) (*string, error) { //relations koppelen ze samen //return statement - var result *string - if len(jsonStruct.Return.Relations) > 0 { - result = createEdgeQuery(jsonStruct, "e0") - } else { + // var result *string + // if len(jsonStruct.Return.Relations) > 0 { + // result = createEdgeQuery(jsonStruct, "e0") + // } else { + + // result = createAllNodesQuery(jsonStruct.Return.Entities, jsonStruct.Entities) + // } + result := createQuery(jsonStruct) + return result, nil +} - result = createAllNodesQuery(jsonStruct.Return.Entities, jsonStruct.Entities) +func convertJSONToStruct(jsonMsg *[]byte) (*parsedJSON, error) { + jsonStruct := parsedJSON{} + err := json.Unmarshal(*jsonMsg, &jsonStruct) + + if err != nil { + return nil, err } - return result, nil + return &jsonStruct, nil } -// createAllNodesQuery creates node queries in AQL -func createAllNodesQuery(returnEntitiesIndices []int, entities []entityStruct) *string { - var ( - result string - ret string - ) +func createQuery(jsQuery *parsedJSON) *string { + // GROTE SIDENOTE: + // Vrij zeker dat een query waar alléén edges worden opgevraagd (#4) + // niet wordt gesupport door zowel de result parser als de frontend reciever + + jsonQuery := *jsQuery + + // TODO: + // NODES + // EDGES (als ze er zijn) + // RETURN STATEMENT + var ret string + for k, v := range jsonQuery.Entities { + name := fmt.Sprintf("n%v", k) + ret += *createNodeLet(&v, &name) + } - ret += "RETURN { \n" - for _, entityIndex := range returnEntitiesIndices { - nodeID := fmt.Sprintf("n%s", strconv.Itoa(entityIndex)) + var onlyEdge bool + if len(jsonQuery.Entities) == 0 { + onlyEdge = true + } + + for k, v := range jsonQuery.Relations { + name := fmt.Sprintf("e%v", k) + ret += *createEdgeLet(&v, &name, onlyEdge) + } - nodeQueryString := *createNodeQuery(&entities[entityIndex], nodeID) - ret += "\t" + nodeID + ":" + nodeID + ",\n" + finalReturn := "RETURN {" - result += fmt.Sprintf(" \n%s", nodeQueryString) + // If there is an edge you can only return the edge, because the nodes are included. + // If there are no edges, return the nodes + if l := len(jsonQuery.Relations); l > 0 { + for k := range jsonQuery.Relations { + finalReturn += fmt.Sprintf("e%v:e%v", k, k) + if k < l-1 { + finalReturn += "," + } + } + } else { + for k := range jsonQuery.Entities { + finalReturn += fmt.Sprintf("n%v:n%v", k, k) + if k < l-1 { + finalReturn += "," + } + } } - ret = strings.TrimSuffix(ret, ",\n") - ret += "\n}" - result += "\n" + ret - return &result + finalReturn += "}" + ret += finalReturn + return &ret +} + +func createNodeLet(node *entityStruct, name *string) *string { + header := fmt.Sprintf("LET %v = (\n\tFOR x IN %v \n", *name, node.Type) + footer := "\tRETURN x\n)\n" + + constraints := *createConstraints(&node.Constraints, false, false) + + ret := header + constraints + footer + return &ret } -// createNodeQuery converts the node part of the json to a subquery -func createNodeQuery(node *entityStruct, name string) *string { - /* - LET alices = ( - FOR x IN female - FILTER x.name == "Alice" AND x.birth_year > 1997 - RETURN x - ) - - NAAR --> - - LET {NAAM**} = ( - FOR x IN {NODETYPE} - FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE} - AND x.{CONSTRAINT[1]} {{CONSTRAINT[1]}.MATCHTYPE} {CONSTRAINT[1].VALUE} - RETURN x - ) - - */ - - letStatement := fmt.Sprintf("LET %s = (\n", name) - forStatement := fmt.Sprintf("\tFOR x IN %s \n", node.Type) - - // Generate all constraints as FILTER statements - first := true - filter := "\tFILTER " - for _, constraint := range node.Constraints { - constraint := createQueryConstraint(&constraint) - - if first { - filter += fmt.Sprintf("\t%s ", *constraint) - first = false +func createEdgeLet(edge *relationStruct, name *string, onlyEdge bool) *string { + var ( + header string + forEdge string + forSecondNode string + ) + + footer := "\n\tLIMIT 100\n\tRETURN { vertices: p.vertices[*], edges: p.edges[*] }\n)\n" + + // WICKED SWITCHES LETSAGO + if edge.EntityFrom != -1 { + // # 1 (2) + header = fmt.Sprintf("LET %v = (\n\tFOR x IN n%v \n", *name, edge.EntityFrom) + forEdge = fmt.Sprintf("\tFOR v, e, p IN %v..%v OUTBOUND x %s \n", edge.Depth.Min, edge.Depth.Max, edge.Type) + if edge.EntityTo != -1 { + // # 2 + forSecondNode = fmt.Sprintf("\tFILTER v IN n%v \n", edge.EntityTo) + } + } else { + if edge.EntityTo != -1 { + // # 3 + header = fmt.Sprintf("LET %v = (\n\tFOR x IN n%v \n", *name, edge.EntityTo) + forEdge = fmt.Sprintf("\tFOR v, e, p IN %v..%v INBOUND x %s \n", edge.Depth.Min, edge.Depth.Max, edge.Type) } else { - filter += fmt.Sprintf("AND\n\t\t%s", *constraint) + // # 4 + header = fmt.Sprintf("LET %v = (\n\tFOR x IN %v \n", *name, edge.Type) + footer = "\tLIMIT 100\n\tRETURN x\n)\n" } } - returnStatement := "\n\tRETURN x\n)" + constraints := *createConstraints(&edge.Constraints, true, onlyEdge) - // Concatenate all the statements - result := letStatement + forStatement + filter + returnStatement - return &result + ret := header + forEdge + forSecondNode + constraints + footer + return &ret } /* - { +#1 +{ "Return": { "Entities": [ 0, @@ -237,31 +205,11 @@ func createNodeQuery(node *entityStruct, name string) *string { "Entities": [ { "Type": "airports", - "Constraints": [ - { - "Attribute": "country", - "Value": "USA", - "DataType": "text", - "MatchType": "exact" - } - ] + "Constraints": [] }, { "Type": "airports", - "Constraints": [ - { - "Attribute": "city", - "Value": "New York", - "DataType": "text", - "MatchType": "exact" - }, - { - "Attribute": "vip", - "Value": "true", - "DataType": "bool", - "MatchType": "exact" - } - ] + "Constraints": [] } ], "Relations": [ @@ -273,325 +221,143 @@ func createNodeQuery(node *entityStruct, name string) *string { }, "EntityFrom": 0, "EntityTo": 1, + "Constraints": [] + } + ] +} +*/ + +/* +#2 + +{ + "Return": { + "Entities": [ + 0 + ], + "Relations": [ + 0 + ] + }, + "Entities": [ + { + "Type": "airports", + "Constraints": [] + } + ], + "Relations": [ + { + "Type": "flights", + "Depth": { + "min": 1, + "max": 1 + }, + "EntityFrom": 0, + "EntityTo": -1, + "Constraints": [] + } + ] +} +*/ + +/* +#3 +{ + "Return": { + "Entities": [ + 0 + ], + "Relations": [ + 0 + ] + }, + "Entities": [ + { + "Type": "airports", + "Constraints": [] + } + ], + "Relations": [ + { + "Type": "flights", + "Depth": { + "min": 1, + "max": 1 + }, + "EntityFrom": -1, + "EntityTo": 0, + "Constraints": [] + } + ] +} +*/ + +/* +#4 +{ + "Return": { + "Entities": [], + "Relations": [ + 0 + ] + }, + "Entities": [], + "Relations": [ + { + "Type": "flights", + "Depth": { + "min": 1, + "max": 1 + }, + "EntityFrom": -1, + "EntityTo": -1, "Constraints": [ { "Attribute": "Month", "Value": "1", "DataType": "number", - "MatchType": "exact" + "MatchType": "EQ" }, { "Attribute": "Day", "Value": "15", "DataType": "number", - "MatchType": "exact" + "MatchType": "EQ" } ] } ] } - - - LET n0 = ( - FOR n IN airports - FILTER n.country == "USA" - RETURN n -) - -LET n1 = ( - FOR n IN airports - FILTER n.city == "New York" AND - n.vip == true - RETURN n -) - -LET e0 = ( - FOR a IN n0 - FOR v, e, p IN 1..1 OUTBOUND a flights - FILTER v in n1 - FILTER p.edges[0].Month == 1 AND - p.edges[0].Day == 15 - RETURN { vertices: p.vertices[*], edges: p.edges[*] } -) - -RETURN e0 - */ -// createEdgeQuery creates an aql query for relations -func createEdgeQuery(q *parsedJSON, name string) *string { - //Creation of n0 n1 let statements - query := "" - entityList := q.Entities - for i, x := range entityList { - query += "\n" - n := fmt.Sprintf("n%v", i) - query += *createNodeQuery(&x, n) - } - - query += *createLetStatementForRelation(&q.Relations[0], name) //DIT GAAN WE EVEN NEGEREN - - //ZEER GEHARDCODED ivm demo - // finalReturn := "\nRETURN { " - - // for x := range q.Return.Entities { - // finalReturn += fmt.Sprintf("n%v, ", x) - // } - - // finalReturn += fmt.Sprintf("e%v }", q.Return.Relations[0]) - // query += finalReturn - query += "\nRETURN {e0:e0}" - - return &query -} - -// createLetStatementForRelation creates the relation query -func createLetStatementForRelation(relation *relationStruct, name string) *string { - - letStatement := fmt.Sprintf("\nLET %s = (\n", name) - upperForStatement := fmt.Sprintf("\tFOR x IN n%v \n", relation.EntityFrom) - edgeForStatement := fmt.Sprintf("\tFOR v, e, p IN %v..%v OUTBOUND x %s \n", relation.Depth.Min, relation.Depth.Max, relation.Type) - upperFilter := fmt.Sprintf("\tFILTER v IN n%v \n", relation.EntityTo) - // Generate all constraints as FILTER statements - first := true - nestedFilter := "\tFILTER " - for _, constraint := range relation.Constraints { - constraint := createEdgeQueryConstraint(&constraint) - - if first { - nestedFilter += fmt.Sprintf("\t%s ", *constraint) - first = false - } else { - nestedFilter += fmt.Sprintf("AND\n\t\t%s", *constraint) - } - } - - returnStatement := "\n\tLIMIT 100\n\tRETURN { vertices: p.vertices[*], edges: p.edges[*] }\n)" - - // Concatenate all the statements - result := letStatement + upperForStatement + edgeForStatement + upperFilter + nestedFilter + returnStatement - return &result -} - -// Constraint datatypes -// text MatchTypes: exact/contains/startswith/endswith -// number MatchTypes: GT/LT/EQ -// bool MatchTypes: EQ/NEQ - -// createQueryConstraint creates a sinlge line of AQL filtering/constraint -func createQueryConstraint(constraint *constraintStruct) *string { - //FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE} - // name match value + dataType - var ( - match string - value string - line string - ) - - //Wicked switches letsgo - switch constraint.DataType { - case "text": - value = fmt.Sprintf("\"%s\"", constraint.Value) - switch constraint.MatchType { - case "contains": - match = "IN" - case "startswith": - match = "LIKE" - value = fmt.Sprintf("\"%s%%\"", constraint.Value) - case "endswith": - match = "LIKE" - value = fmt.Sprintf("\"_%s\"", constraint.Value) - default: //exact - match = "==" - } - case "number": - value = constraint.Value - switch constraint.MatchType { - case "GT": - match = ">" - case "LT": - match = "<" - case "GET": - match = ">=" - case "LET": - match = "<=" - default: //EQ - match = "==" - } - default: /*bool*/ - value = constraint.Value - switch constraint.MatchType { - case "NEQ": - match = "!=" - default: //EQ - match = "==" - } - } - line = fmt.Sprintf("x.%s %s %s", constraint.Attribute, match, value) - return &line -} - -// createEdgeQueryConstraint translates the constraints of an edge query to aql -func createEdgeQueryConstraint(constraint *constraintStruct) *string { - //FILTER x.{CONSTRAINT[0]} {{CONSTRAINT[0]}.MATCHTYPE} {CONSTRAINT[0].VALUE} - // name match value + dataType - var ( - match string - value string - line string - ) - - //Wicked switches letsgo - switch constraint.DataType { - case "text": - value = fmt.Sprintf("\"%s\"", constraint.Value) - switch constraint.MatchType { - case "contains": - match = "IN" - case "startswith": - match = "LIKE" - value = fmt.Sprintf("\"%s%%\"", constraint.Value) - case "endswith": - match = "LIKE" - value = fmt.Sprintf("\"_%s\"", constraint.Value) - default: //exact - match = "==" - } - case "number": - value = constraint.Value - switch constraint.MatchType { - case "GT": - match = ">" - case "LT": - match = "<" - case "GET": - match = ">=" - case "LET": - match = "<=" - default: //EQ - match = "==" - } - default: /*bool*/ - value = constraint.Value - switch constraint.MatchType { - case "NEQ": - match = "!=" - default: //EQ - match = "==" - } - } - line = fmt.Sprintf("p.edges[*].%s ALL %s %s", constraint.Attribute, match, value) - return &line -} - -// convertJSONToStruct takes the json as byte array and converts it to a struct -func convertJSONToStruct(jsonMsg *[]byte) (*parsedJSON, error) { - jsonStruct := parsedJSON{} - err := json.Unmarshal(*jsonMsg, &jsonStruct) - - if err != nil { - return nil, err - } - - return &jsonStruct, nil -} /* - desired output - - WITH male - FOR a1 IN female - FILTER a1.name == "Alice" - - (FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation - FILTER r1v.name == "Bob") - OR - (FOR r2v,r2e,r2p IN 1..1 OUTBOUND a1 relation - FILTER r2v.name == "Martin" && r2v.hasdog == true) - // constraints for Bob - OR r1v.name == "Martin" - FILTER r1e.type == "married" - - FOR r2v,r2e,r2p IN 1..1 OUTBOUND r1v relation - FILTER r2v.name == "Figo" - FILTER r2e.type == "has_dog" - - RETURN {a1,r1v,r2v} - - - of - - - LET alices = ( - FOR x IN female - FILTER x.name == "Alice" - RETURN x - ) - - LET bobs = ( - FOR x IN male - FILTER x.name == "Bob" - RETURN x - ) - - LET alices = ( - FOR alice IN alices - FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation - FILTER r1v IN bobs AND r1e.type == "married" - RETURN {alice} - ) - - LET alices = ( - FOR alice IN alices - FOR r1v,r1e,r1p IN 1..1 OUTBOUND alice relation - FILTER r1v IN marting AND r1e.type == "friend" - RETURN {alice} - ) - - FOR a2 IN male - FILTER a2.name == "Bob" - - FOR r1v,r1e,r1p IN 1..1 OUTBOUND a1 relation - FILTER r1v._id == a2._id - FILTER r1e.type == "married" - RETURN {a1,a2,r1e} - -*/ -/* +#5 { - "NodeType": "female", - "Constraints": - { - "name": { "Value": "Alice", "DataType": "text", "MatchType": "exact" }, - "birth_year": { "Value": "1997", "DataType": "number", "MatchType": "GT" } - } - } - - NAAR --> - - LET alices = ( - FOR x IN female - FILTER x.name == "Alice" AND x.birth_year > 1997 - RETURN x - ) - + "Return": { + "Entities": [ + 0 + ], + "Relations": [] + }, + "Entities": [ + { + "Type": "airports", + "Constraints": [ + { + "Attribute": "city", + "Value": "New York", + "DataType": "text", + "MatchType": "exact" + }, + { + "Attribute": "country", + "Value": "USA", + "DataType": "text", + "MatchType": "exact" + } + ] + } + ], + "Relations": [] +} */ - -//Nog een manier vinden om namen te syncen over de queries heen ** - -// func forGlory() *constraintStruct { -// var yeet constraintStruct -// yeet = constraintStruct{ -// ConstraintName: "name", -// Value: "Alice", -// MatchType: "exact", -// DataType: "text", -// } -// return &yeet -// } - -// func alsoForGlory() { -// con := forGlory() - -// toPrint := createQueryConstraint(*con) -// fmt.Println(*toPrint) -// } diff --git a/internal/usecases/convertquery/createConstraints.go b/internal/usecases/convertquery/createConstraints.go new file mode 100644 index 0000000000000000000000000000000000000000..79bf92d5009b86a1f26aeeaf62f4bac0f369331c --- /dev/null +++ b/internal/usecases/convertquery/createConstraints.go @@ -0,0 +1,74 @@ +package convertquery + +import "fmt" + +func createConstraints(constraints *[]constraintStruct, isRelation bool, onlyEdge bool) *string { + s := "" + if len(*constraints) == 0 { + return &s + } + + newLineStatement := "\tFILTER" + + for _, v := range *constraints { + s += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintLine(&v, isRelation, onlyEdge)) + newLineStatement = "\tAND" + } + + return &s +} + +func createConstraintLine(constraint *constraintStruct, isRelation bool, onlyEdge bool) *string { + var ( + match string + value string + line string + ) + + //Wicked switches letsgo + switch constraint.DataType { + case "text": + value = fmt.Sprintf("\"%s\"", constraint.Value) + switch constraint.MatchType { + case "contains": + match = "IN" + case "startswith": + match = "LIKE" + value = fmt.Sprintf("\"%s%%\"", constraint.Value) + case "endswith": + match = "LIKE" + value = fmt.Sprintf("\"_%s\"", constraint.Value) + default: //exact + match = "==" + } + case "number": + value = constraint.Value + switch constraint.MatchType { + case "GT": + match = ">" + case "LT": + match = "<" + case "GET": + match = ">=" + case "LET": + match = "<=" + default: //EQ + match = "==" + } + default: /*bool*/ + value = constraint.Value + switch constraint.MatchType { + case "NEQ": + match = "!=" + default: //EQ + match = "==" + } + } + + if isRelation && !onlyEdge { + line = fmt.Sprintf("p.edges[*].%s ALL %s %s", constraint.Attribute, match, value) + } else { + line = fmt.Sprintf("x.%s %s %s", constraint.Attribute, match, value) + } + return &line +}