diff --git a/development/docker-compose.yaml b/development/docker-compose.yaml new file mode 100644 index 0000000000000000000000000000000000000000..99d2c089f88eb365a1dfc32f9e3aa8569a7b1e28 --- /dev/null +++ b/development/docker-compose.yaml @@ -0,0 +1,15 @@ +version: '3.7' +services: + arangodb_db_container: + image: arangodb:latest + environment: + ARANGO_ROOT_PASSWORD: DikkeDraak + ports: + - 8529:8529 + volumes: + - arangodb_data_container:/var/lib/arangodb3 + - arangodb_apps_data_container:/var/lib/arangodb3-apps + +volumes: + arangodb_data_container: + arangodb_apps_data_container: diff --git a/integration-testing/config.json b/integration-testing/config.json index f57f640f847826c72dbd9799df69173835e8a5e8..9840589a283ab5faf4f78b6a17ec3c1f99d89c1d 100644 --- a/integration-testing/config.json +++ b/integration-testing/config.json @@ -6,13 +6,14 @@ "exchange": "requests-exchange", "exchangeType": "direct", "messages": [ + { "routingKey": "aql-query-request", "headers": { "sessionID": "test-session", "clientID": "test-client-id" }, - "data":"This is not a valid query" + "data":"{\"DatabaseName\":\"test\",\"Return\":{\"Entities\":[0,1],\"Relations\":[0]},\"Entities\":[{\"Type\":\"airports\",\"Constraints\":[{\"Attribute\":\"city\",\"Value\":\"New York\",\"DataType\":\"text\",\"MatchType\":\"exact\"}]},{\"Type\":\"airports\",\"Constraints\":[{\"Attribute\":\"city\",\"Value\":\"San Francisco\",\"DataType\":\"text\",\"MatchType\":\"exact\"},{\"Attribute\":\"vip\",\"Value\":\"true\",\"DataType\":\"bool\",\"MatchType\":\"exact\"}]}],\"Relations\":[{\"Type\":\"flights\",\"Depth\":{\"min\":1,\"max\":1},\"EntityFrom\":0,\"EntityTo\":1,\"Constraints\":[{\"Attribute\":\"Month\",\"Value\":\"1\",\"DataType\":\"number\",\"MatchType\":\"exact\"},{\"Attribute\":\"Day\",\"Value\":\"15\",\"DataType\":\"number\",\"MatchType\":\"exact\"}]}], \"limit\": 1000}" }, { "routingKey": "aql-query-request", @@ -20,7 +21,7 @@ "sessionID": "test-session", "clientID": "test-client-id" }, - "data":"{\"DatabaseName\":\"test\",\"Return\":{\"Entities\":[0,1],\"Relations\":[0]},\"Entities\":[{\"Type\":\"airports\",\"Constraints\":[{\"Attribute\":\"city\",\"Value\":\"New York\",\"DataType\":\"text\",\"MatchType\":\"exact\"}]},{\"Type\":\"airports\",\"Constraints\":[{\"Attribute\":\"city\",\"Value\":\"San Francisco\",\"DataType\":\"text\",\"MatchType\":\"exact\"},{\"Attribute\":\"vip\",\"Value\":\"true\",\"DataType\":\"bool\",\"MatchType\":\"exact\"}]}],\"Relations\":[{\"Type\":\"flights\",\"Depth\":{\"min\":1,\"max\":1},\"EntityFrom\":0,\"EntityTo\":1,\"Constraints\":[{\"Attribute\":\"Month\",\"Value\":\"1\",\"DataType\":\"number\",\"MatchType\":\"exact\"},{\"Attribute\":\"Day\",\"Value\":\"15\",\"DataType\":\"number\",\"MatchType\":\"exact\"}]}], \"limit\": 1000}" + "data":"This is not a valid query" } ] } diff --git a/internal/entity/querystruct.go b/internal/entity/querystruct.go index 3c01d5f8bb6281474993a051fe641f6bbe24079b..868722fd4fd8edb648fe1a06b90e3622a4c802a7 100644 --- a/internal/entity/querystruct.go +++ b/internal/entity/querystruct.go @@ -6,15 +6,16 @@ type QueryParsedJSON struct { Return QueryReturnStruct Entities []QueryEntityStruct Relations []QueryRelationStruct - // Limit is for limiting the amount of paths AQL will return in a relation let statement - Limit int + Limit int + Modifiers []QueryModifierStruct } // QueryReturnStruct holds the indices of the entities and relations that need to be returned type QueryReturnStruct struct { Entities []int Relations []int + //Modifiers []int } // QueryEntityStruct encapsulates a single entity with its corresponding constraints @@ -32,6 +33,14 @@ type QueryRelationStruct struct { Constraints []QueryConstraintStruct } +// QueryModifierStruct encapsulates a single modifier with its corresponding constraints +type QueryModifierStruct struct { + Type string //SUM COUNT AVG + SelectedType string //node relation + ID int // ID of the enitity or relation + AttributeIndex int // = -1 if its the node or relation, = > -1 if an attribute is selected +} + // QuerySearchDepthStruct holds the range of traversals for the relation type QuerySearchDepthStruct struct { Min int diff --git a/internal/usecases/consume/consume_test.go b/internal/usecases/consume/consume_test.go index f39274305b7520e159b0dd1c17172a80535fca1b..cb8f06f2259d756d96ce9ab4983b8fd82c005902 100644 --- a/internal/usecases/consume/consume_test.go +++ b/internal/usecases/consume/consume_test.go @@ -86,7 +86,7 @@ func TestHandleCorrectMessage(t *testing.T) { var resultMessage map[string]interface{} json.Unmarshal(mockBroker.Messages[mockQueue][1].Body, &resultMessage) assert.Equal(t, "query_result", resultMessage["type"]) - assert.Empty(t, resultMessage["values"]) + assert.Equal(t, "test", resultMessage["values"]) } // Unit test message received with no session ID diff --git a/internal/usecases/consume/handlemessage.go b/internal/usecases/consume/handlemessage.go index b465eb51a77af781dfa4a4f183fd0a2d4d755ba5..53ee5bdef2bba604afc3b07ff8bf00830c39dcba 100644 --- a/internal/usecases/consume/handlemessage.go +++ b/internal/usecases/consume/handlemessage.go @@ -80,9 +80,11 @@ func (s *Service) HandleMessage(msg *brokeradapter.Message) { } // Add type indicator to result from database + var res interface{} + json.Unmarshal(*result, &res) resultMsgMap := make(map[string]interface{}) resultMsgMap["type"] = "query_result" - resultMsgMap["values"] = *result + resultMsgMap["values"] = res resultMsgByte, err := json.Marshal(resultMsgMap) if err != nil { errorhandler.LogError(err, "Marshalling query_result went wrong!") // TODO: send error message to client instead diff --git a/internal/usecases/convertquery/aql.go b/internal/usecases/convertquery/aql.go index 2c8781ec23404997f0c599718fe57173c73adb70..2478a9f46e1827da9e388d50aa428cfb4c357fe7 100644 --- a/internal/usecases/convertquery/aql.go +++ b/internal/usecases/convertquery/aql.go @@ -20,12 +20,15 @@ func (s *Service) ConvertQuery(jsonMsg *[]byte) (*string, *string, error) { fmt.Println(err) return nil, nil, err } + //fmt.Println("We made it past the initial error checking") // Check to make sure all indexes exist // How many entities are there numEntities := len(jsonStruct.Entities) - 1 // How many relations there are numRelations := len(jsonStruct.Relations) - 1 + // How many modifiers there are + //numModifiers := len(jsonStruct.Modifiers) - 1 // Make sure no entity should be returned that is outside the range of that list for _, e := range jsonStruct.Return.Entities { @@ -49,7 +52,15 @@ func (s *Service) ConvertQuery(jsonMsg *[]byte) (*string, *string, error) { } } + //Make sure no modifier should be returned that is outside the range of that list + // for _, m := range jsonStruct.Modifiers { + // // If this modifier references an modifier that is outside the range + // if m > numModifiers || m < 0 { + // return nil, nil, errors.New("non-existing modifier referenced") + // } + // } result := createQuery(jsonStruct) + //fmt.Print("test123" + *result) return result, &jsonStruct.DatabaseName, nil } @@ -74,11 +85,48 @@ Parameters: jsonQuery is a parsedJSON struct holding all the data needed to form Return: a string containing the corresponding AQL query and an error */ + +/* + +LET n0 = (FOR x IN airports FILTER x.city == "New York" RETURN x) +LET nodes = first(RETURN UNION_DISTINCT(n0,[],[])) +LET edges = first(RETURN UNION_DISTINCT([],[])) +RETURN {"vertices":nodes, "edges":edges } + + + +LET n0 = (FOR x IN airports FILTER x.city == "New York" RETURN x) +RETURN LENGTH(n0) + + + + +LET n0 = (FOR x IN airports FILTER x.city == "New York" RETURN x) +LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 8 +LIMIT 5000 RETURN DISTINCT p ) +LET nodes = first(RETURN UNION_DISTINCT(flatten(r0[**].vertices), [],[])) +LET edges = first(RETURN UNION_DISTINCT(flatten(r0[**].edges), [],[])) +RETURN {"vertices":nodes, "edges":edges } + + +LET n0 = (FOR x IN airports FILTER x.city == "New York" RETURN x) +LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 8 +RETURN DISTINCT p ) +RETURN COUNT(UNIQUE(r0[**].vertices[0])) + + +*/ + func createQuery(jsonQuery *entity.QueryParsedJSON) *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 receiver + // If a modifier is used, disable the limit + if len(jsonQuery.Modifiers) > 0 { + jsonQuery.Limit = -1 + } + var ( relationsToReturn []string nodesToReturn []string @@ -90,6 +138,7 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { ret := "" for i, relation := range jsonQuery.Relations { + relationName := fmt.Sprintf("r%v", i) if relation.EntityFrom >= 0 { @@ -137,26 +186,115 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { } } - // Create UNION statements that create unique lists of all the nodes and relations - // Thus removing all duplicates - nodeUnion = "\nLET nodes = first(RETURN UNION_DISTINCT(" - for _, relation := range relationsToReturn { - nodeUnion += fmt.Sprintf("flatten(%v[**].vertices), ", relation) - } + //If there are modifiers within the query, we run a different set of checks which focus on quantifiable aspects + if len(jsonQuery.Modifiers) > 0 { + modifier := jsonQuery.Modifiers[0] + // There is a distinction between (relations and entities) and (relations or entities) + if len(jsonQuery.Return.Relations) > 0 && len(jsonQuery.Return.Entities) > 0 { - for _, node := range nodesToReturn { - nodeUnion += fmt.Sprintf("%v,", node) - } - nodeUnion += "[],[]))\n" + var pathDistinction string // .vertices or .edges - relationUnion = "LET edges = first(RETURN UNION_DISTINCT(" - for _, relation := range relationsToReturn { - relationUnion += fmt.Sprintf("flatten(%v[**].edges), ", relation) - } - relationUnion += "[],[]))\n" + // Select the correct addition to the return of r0[**] + if modifier.SelectedType == "entity" { + // ASSUMING THERE IS ONLY 1 RELATION + if jsonQuery.Relations[0].EntityFrom == modifier.ID { + pathDistinction = fmt.Sprintf(".vertices[%v]", jsonQuery.Relations[0].Depth.Min-1) + + } else { + pathDistinction = fmt.Sprintf(".vertices[%v]", jsonQuery.Relations[0].Depth.Max) + + } + } else { + pathDistinction = ".edges[**]" + } + + // Getting the attribute if there is one + if modifier.AttributeIndex != -1 { + if modifier.SelectedType == "entity" { + pathDistinction += fmt.Sprintf(".%v", jsonQuery.Entities[modifier.ID].Constraints[modifier.AttributeIndex].Attribute) + + } else { + pathDistinction += fmt.Sprintf(".%v", jsonQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute) - ret += nodeUnion + relationUnion - ret += "RETURN {\"vertices\":nodes, \"edges\":edges }" + } + } + + // If count is used it has to be replaced with Length + unique else use the modifier type + if modifier.Type == "COUNT" { + ret += fmt.Sprintf("RETURN LENGTH (unique(r0[*]%v))", pathDistinction) + + } else { + ret += fmt.Sprintf("RETURN %v (r0[*]%v)", modifier.Type, pathDistinction) + + } + + } else { + // Check if the modifier is on an attribute + if modifier.AttributeIndex == -1 { + ret += fmt.Sprintf("RETURN LENGTH (n%v)", modifier.ID) + } else { + var attribute string + + // Selecting the right attribute from either the entity constraint or relation constraint + if modifier.SelectedType == "entity" { + attribute = jsonQuery.Entities[modifier.ID].Constraints[modifier.AttributeIndex].Attribute + + } else { + attribute = jsonQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute + + } + + // If count is used it has to be replaced with Length + unique else use the modifier type + if modifier.Type == "COUNT" { + ret += fmt.Sprintf("RETURN LENGTH (unique(n%v[*].%v))", modifier.ID, attribute) + + } else { + ret += fmt.Sprintf("RETURN %v (n%v[*].%v)", modifier.Type, modifier.ID, attribute) + + } + } + } + + // fmt.Println("Yes we do") + // switch modifier.Type { + // case "COUNT": + // if len(jsonQuery.Return.Relations) > 0 && modifier.SelectedType == "Entity" { + // ret += "RETURN COUNT(UNIQUE(r0[**].vertices[0]))" + // } else { + // ret += "RETURN LENGTH(n0)" + // } + // case "AVG": + // ret += "" + // case "SUM": + // ret += "" + // default: + // ret += "" + // } + + } else { + + // Create UNION statements that create unique lists of all the nodes and relations + // Thus removing all duplicates + nodeUnion = "\nLET nodes = first(RETURN UNION_DISTINCT(" + for _, relation := range relationsToReturn { + nodeUnion += fmt.Sprintf("flatten(%v[**].vertices), ", relation) + } + + for _, node := range nodesToReturn { + nodeUnion += fmt.Sprintf("%v,", node) + } + nodeUnion += "[],[]))\n" + + relationUnion = "LET edges = first(RETURN UNION_DISTINCT(" + for _, relation := range relationsToReturn { + relationUnion += fmt.Sprintf("flatten(%v[**].edges), ", relation) + } + relationUnion += "[],[]))\n" + + ret += nodeUnion + relationUnion + ret += "RETURN {\"vertices\":nodes, \"edges\":edges }" + + } return &ret } @@ -204,7 +342,13 @@ func createRelationLetWithFromEntity(relation *entity.QueryRelationStruct, name } relationFilterStmnt := *createConstraintStatements(&relation.Constraints, "p", true) - footer := fmt.Sprintf("\tLIMIT %v \nRETURN DISTINCT p )\n", limit) + + // Dont use a limit on quantifing queries + footer := "" + if limit != -1 { + footer += fmt.Sprintf("\tLIMIT %v \n", limit) + } + footer += "RETURN DISTINCT p )\n" ret := header + forStatement + optionStmtn + vFilterStmnt + relationFilterStmnt + footer return &ret @@ -226,7 +370,13 @@ func createRelationLetWithOnlyToEntity(relation *entity.QueryRelationStruct, nam optionStmtn := "\tOPTIONS { uniqueEdges: \"path\" }\n" relationFilterStmnt := *createConstraintStatements(&relation.Constraints, "p", true) - footer := fmt.Sprintf("\tLIMIT %v \nRETURN DISTINCT p )\n", limit) + + // Dont use a limit on quantifing queries + footer := "" + if limit != -1 { + footer += fmt.Sprintf("\tLIMIT %v \n", limit) + } + footer += "RETURN DISTINCT p )\n" ret := header + forStatement + optionStmtn + relationFilterStmnt + footer return &ret diff --git a/internal/usecases/convertquery/aql_test.go b/internal/usecases/convertquery/aql_test.go index 7e86fab69d89abdb2d4cf7be675d58512b66580c..a8515ebdb2ef8810c8548b0bedd0a7a324e5c12e 100644 --- a/internal/usecases/convertquery/aql_test.go +++ b/internal/usecases/convertquery/aql_test.go @@ -138,6 +138,239 @@ func TestRelationWithConstraint(t *testing.T) { assert.Equal(t, correctConvertedResult, cleanedResult) } +func TestModifierCountEntity(t *testing.T) { + // Setup for test + // Create query conversion service + service := NewService() + + query := []byte(`{ + "return": { + "entities": [ + 0 + ], + "relations": [] + }, + "entities": [ + { + "type": "airports", + "constraints": [ + { + "attribute": "state", + "value": "HI", + "dataType": "text", + "matchType": "exact" + } + ] + } + ], + "relations": [], + "limit": 5000, + "modifiers": [ + { + "type": "COUNT", + "selectedType": "entity", + "id": 0, + "attributeIndex": -1 + } + ] + }`) + + convertedResult, _, err := service.ConvertQuery(&query) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the result and the expected result are the same + correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)RETURN LENGTH (n0)` + cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "") + cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "") + assert.Equal(t, correctConvertedResult, cleanedResult) +} +func TestModifierCountEntityAttribute(t *testing.T) { + // Setup for test + // Create query conversion service + service := NewService() + + query := []byte(`{ + "return": { + "entities": [ + 0 + ], + "relations": [] + }, + "entities": [ + { + "type": "airports", + "constraints": [ + { + "attribute": "state", + "value": "HI", + "dataType": "text", + "matchType": "exact" + } + ] + } + ], + "relations": [], + "limit": 5000, + "modifiers": [ + { + "type": "SUM", + "selectedType": "entity", + "id": 0, + "attributeIndex": 0 + } + ] + }`) + + convertedResult, _, err := service.ConvertQuery(&query) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the result and the expected result are the same + correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)RETURN SUM (n0[*].state)` + cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "") + cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "") + assert.Equal(t, correctConvertedResult, cleanedResult) +} +func TestModifierCountRelation(t *testing.T) { + // Setup for test + // Create query conversion service + service := NewService() + + query := []byte(`{ + "return": { + "entities": [ + 0 + ], + "relations": [ + 0 + ] + }, + "entities": [ + { + "type": "airports", + "constraints": [ + { + "attribute": "state", + "value": "HI", + "dataType": "text", + "matchType": "exact" + } + ] + } + ], + "relations": [ + { + "type": "flights", + "depth": { + "min": 1, + "max": 1 + }, + "entityFrom": 0, + "entityTo": -1, + "constraints": [ + { + "attribute": "Day", + "value": "15", + "dataType": "number", + "matchType": "EQ" + } + ] + } + ], + "limit": 5000, + "modifiers": [ + { + "type": "COUNT", + "selectedType": "relation", + "id": 0, + "attributeIndex": -1 + } + ] + }`) + + convertedResult, _, err := service.ConvertQuery(&query) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the result and the expected result are the same + correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 15 RETURN DISTINCT p )RETURN LENGTH (unique(r0[*].edges[**]))` + cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "") + cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "") + assert.Equal(t, correctConvertedResult, cleanedResult) +} +func TestModifierCountRelationAttribute(t *testing.T) { + // Setup for test + // Create query conversion service + service := NewService() + + query := []byte(`{ + "return": { + "entities": [ + 0 + ], + "relations": [ + 0 + ] + }, + "entities": [ + { + "type": "airports", + "constraints": [ + { + "attribute": "state", + "value": "HI", + "dataType": "text", + "matchType": "exact" + } + ] + } + ], + "relations": [ + { + "type": "flights", + "depth": { + "min": 1, + "max": 1 + }, + "entityFrom": 0, + "entityTo": -1, + "constraints": [ + { + "attribute": "Day", + "value": "15", + "dataType": "number", + "matchType": "EQ" + } + ] + } + ], + "limit": 5000, + "modifiers": [ + { + "type": "AVG", + "selectedType": "relation", + "id": 0, + "attributeIndex": 0 + } + ] + }`) + + convertedResult, _, err := service.ConvertQuery(&query) + + // Assert that there is no error + assert.NoError(t, err) + + // Assert that the result and the expected result are the same + correctConvertedResult := `LET n0 = (FOR x IN airports FILTER x.state == "HI" RETURN x)LET r0 = (FOR x IN n0 FOR v, e, p IN 1..1 OUTBOUND x flights OPTIONS { uniqueEdges: "path" }FILTER p.edges[*].Day ALL == 15 RETURN DISTINCT p )RETURN AVG (r0[*].edges[**].Day)` + cleanedResult := strings.ReplaceAll(*convertedResult, "\n", "") + cleanedResult = strings.ReplaceAll(cleanedResult, "\t", "") + assert.Equal(t, correctConvertedResult, cleanedResult) +} + func TestRelationWithInOutConstraint(t *testing.T) { // Setup for test // Create query conversion service diff --git a/internal/usecases/request/interface.go b/internal/usecases/request/interface.go index 329f583dc245f533d90e9a4f6a5479bf86dd3dbe..e5c1f63dc2a8f60374c3ab3753277cfacef3e9d6 100644 --- a/internal/usecases/request/interface.go +++ b/internal/usecases/request/interface.go @@ -1,8 +1,6 @@ package request -import "query-service/internal/entity" - // UseCase is an interface describing the request usecases type UseCase interface { - SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*map[string][]entity.Document, error) + SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*[]byte, error) } diff --git a/internal/usecases/request/mock/mockrequest.go b/internal/usecases/request/mock/mockrequest.go index 3e9d9b386fac6be92db7b837fdd4c3ccdd851d69..7b52e4241c037e42224652b05df679d332ac56a8 100644 --- a/internal/usecases/request/mock/mockrequest.go +++ b/internal/usecases/request/mock/mockrequest.go @@ -1,8 +1,8 @@ package mockrequest import ( + "encoding/json" "errors" - "query-service/internal/entity" ) // A Service implements the request usecases (mock) @@ -18,8 +18,8 @@ func NewService() *Service { } // SendAQLQuery sends the query to arangoDB and parses the result (mock) -func (s *Service) SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*map[string][]entity.Document, error) { - mockResult := make(map[string][]entity.Document) +func (s *Service) SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*[]byte, error) { + mockResult, _ := json.Marshal("test") if !s.throwError { return &mockResult, nil diff --git a/internal/usecases/request/request.go b/internal/usecases/request/request.go index 9162f6f6858b852e5b3338a8d6b06af488ded687..fc9f971eeed1305c537d3ef4180008350fb0cf66 100644 --- a/internal/usecases/request/request.go +++ b/internal/usecases/request/request.go @@ -26,7 +26,9 @@ Parameters: AQLQuery is a string containing the query that will be send to the d Return: a map with two entries: "nodes" with a list of vertices/nodes and "edges" with a list of edges that will be returned to the frontend */ -func (s *Service) SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*map[string][]entity.Document, error) { +func (s *Service) SendAQLQuery(query string, username string, password string, hostname string, port int, database string) (*[]byte, error) { + + //(*map[string][]entity.Document, error) = old return var queryResult = make(map[string][]entity.Document) conn, err := http.NewConnection(http.ConnectionConfig{ Endpoints: []string{fmt.Sprintf("%s:%d", hostname, port)}, @@ -62,25 +64,49 @@ func (s *Service) SendAQLQuery(query string, username string, password string, h //Loop through the resulting documents listContainer := entity.ListContainer{} + num := -1.0 + isNum := false + for { - var doc map[string][]interface{} + //var doc map[string][]interface{} + var doc interface{} _, err := cursor.ReadDocument(ctx, &doc) if driver.IsNoMoreDocuments(err) { break } else if err != nil { // handle other errors + fmt.Println(err) return nil, err } - parseResult(doc, &listContainer) + // Switch on the type of return, to filter out the case of a single number + switch doc.(type) { + case float64: + num = doc.(float64) + isNum = true + break + case map[string]interface{}: + pdoc := doc.(map[string]interface{}) + parseResult(pdoc, &listContainer) + break + default: + fmt.Println("Incompatible result type") + break + } } - queryResult["nodes"] = listContainer.NodeList - queryResult["edges"] = listContainer.EdgeList + if !isNum { + // Return nodes and edges + queryResult["nodes"] = listContainer.NodeList + queryResult["edges"] = listContainer.EdgeList + + jsonQ, err := json.Marshal(queryResult) + return &jsonQ, err + } + // Return just a number + numQ, err := json.Marshal(num) + return &numQ, err - //writeJSON(queryResult) - //file, err := json.MarshalIndent(queryResult, "", " ") - return &queryResult, nil } /* parseResult takes the result of the query and translates this to two lists: a nodelist and an edgelist, stored in a listcontainer @@ -90,9 +116,11 @@ listContainer is a struct containing the nodelist and edgelist that will be retu Return: Nothing because the result is stored in the listContainer */ -func parseResult(doc map[string][]interface{}, listContainer *entity.ListContainer) { - vertices := doc["vertices"] - edges := doc["edges"] +func parseResult(doc map[string]interface{}, listContainer *entity.ListContainer) { + + //doc, ok := incomingDoc.(map[string][]interface{}) + vertices := doc["vertices"].([]interface{}) + edges := doc["edges"].([]interface{}) for _, vertex := range vertices { vertexDoc := vertex.(map[string]interface{})