diff --git a/aql/convertQuery.go b/aql/convertQuery.go index 8ef1db00b794cfebb23e2a2aad61fd631bd2d823..0fe8f7484eeedcc37fdbc1f720848c14051e8df4 100644 --- a/aql/convertQuery.go +++ b/aql/convertQuery.go @@ -1,7 +1,6 @@ package aql import ( - "encoding/json" "errors" "fmt" @@ -9,64 +8,42 @@ import ( ) /* -ConvertQuery converts a json string to an AQL query - -Parameters: jsonMsg is the JSON file directly outputted by the drag and drop query builder in the frontend - -Return: a string containing the corresponding AQL query, a string containing the database name and an error */ -func (s *Service) ConvertQuery(jsonMsg *[]byte) (*string, *string, error) { - - jsonStruct, err := convertJSONToStruct(jsonMsg) - if err != nil { - return nil, nil, err - } +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 // How many entities are there - numEntities := len(jsonStruct.Entities) - 1 + numEntities := len(JSONQuery.Entities) - 1 // How many relations there are - numRelations := len(jsonStruct.Relations) - 1 + numRelations := len(JSONQuery.Relations) - 1 // Make sure no entity should be returned that is outside the range of that list - for _, e := range jsonStruct.Return.Entities { + for _, e := range JSONQuery.Return.Entities { // If this entity references an entity that is outside the range if e > numEntities || e < 0 { - return nil, nil, errors.New("non-existing entity referenced in return") + return nil, errors.New("non-existing entity referenced in return") } } // Make sure that no relation mentions a non-existing entity - for _, r := range jsonStruct.Relations { + for _, r := range JSONQuery.Relations { if r.EntityFrom > numEntities || r.EntityTo > numEntities { - return nil, nil, errors.New("non-exisiting entity referenced in relation") + return nil, errors.New("non-exisiting entity referenced in relation") } } // Make sure no non-existing relation is tried to be returned - for _, r := range jsonStruct.Return.Relations { + for _, r := range JSONQuery.Return.Relations { if r > numRelations || r < 0 { - return nil, nil, errors.New("non-existing relation referenced in return") + return nil, errors.New("non-existing relation referenced in return") } } - result := createQuery(jsonStruct) - return result, &jsonStruct.DatabaseName, nil -} - -/* convertJSONtoStruct reads a JSON file and sorts the data into the appropriate structs -Parameters: jsonMsg is the JSON file directly outputted by the drag and drop query builder in the frontend - -Return: parsedJSON is a struct with the same structure and holding the same data as jsonMsg -*/ -func convertJSONToStruct(jsonMsg *[]byte) (*entity.QueryParsedJSON, error) { - jsonStruct := entity.QueryParsedJSON{} - err := json.Unmarshal(*jsonMsg, &jsonStruct) - - if err != nil { - return nil, err - } - - return &jsonStruct, nil + result := createQuery(JSONQuery) + return result, nil } /* createQuery generates a query based on the json file provided @@ -74,12 +51,12 @@ Parameters: jsonQuery is a parsedJSON struct holding all the data needed to form Return: a string containing the corresponding AQL query and an error */ -func createQuery(jsonQuery *entity.QueryParsedJSON) *string { +func createQuery(JSONQuery *entity.IncomingQueryJSON) *string { // Note: Case #4, where there is an edge only query (without any entity), is not supported by frontend // If a modifier is used, disable the limit - if len(jsonQuery.Modifiers) > 0 { - jsonQuery.Limit = -1 + if len(JSONQuery.Modifiers) > 0 { + JSONQuery.Limit = -1 } var ( @@ -92,7 +69,7 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { // Loop over all relations ret := "" - for i, relation := range jsonQuery.Relations { + for i, relation := range JSONQuery.Relations { relationName := fmt.Sprintf("r%v", i) @@ -101,16 +78,16 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { // create the let for this node fromName := fmt.Sprintf("n%v", relation.EntityFrom) - ret += *createNodeLet(&jsonQuery.Entities[relation.EntityFrom], &fromName) + ret += *createNodeLet(&JSONQuery.Entities[relation.EntityFrom], &fromName) - ret += *createRelationLetWithFromEntity(&relation, relationName, &jsonQuery.Entities, jsonQuery.Limit) + ret += *createRelationLetWithFromEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit) } else if relation.EntityTo >= 0 { // if there is only a to-node toName := fmt.Sprintf("n%v", relation.EntityTo) - ret += *createNodeLet(&jsonQuery.Entities[relation.EntityTo], &toName) + ret += *createNodeLet(&JSONQuery.Entities[relation.EntityTo], &toName) - ret += *createRelationLetWithOnlyToEntity(&relation, relationName, &jsonQuery.Entities, jsonQuery.Limit) + ret += *createRelationLetWithOnlyToEntity(&relation, relationName, &JSONQuery.Entities, JSONQuery.Limit) // Add this relation to the list } else { fmt.Println("Relation-only queries are currently not supported") @@ -124,17 +101,17 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { // Add node let statements for nodes that are not yet returned // Create a set from all the entity-from's and entity-to's, to check if they are returned nodeSet := make(map[int]bool) - for _, relation := range jsonQuery.Relations { + for _, relation := range JSONQuery.Relations { nodeSet[relation.EntityFrom] = true nodeSet[relation.EntityTo] = true } // Check if the entities to return are already returned - for _, entityIndex := range jsonQuery.Return.Entities { + for _, entityIndex := range JSONQuery.Return.Entities { if !nodeSet[entityIndex] { // If not, return this node name := fmt.Sprintf("n%v", entityIndex) - ret += *createNodeLet(&jsonQuery.Entities[entityIndex], &name) + ret += *createNodeLet(&JSONQuery.Entities[entityIndex], &name) // Add this node to the list nodesToReturn = append(nodesToReturn, name) @@ -142,21 +119,21 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { } //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] + 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 { + if len(JSONQuery.Return.Relations) > 0 && len(JSONQuery.Return.Entities) > 0 { var pathDistinction string // .vertices or .edges // 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) + 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) + pathDistinction = fmt.Sprintf(".vertices[%v]", JSONQuery.Relations[0].Depth.Max) } } else { @@ -166,10 +143,10 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *string { // 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) + 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) + pathDistinction += fmt.Sprintf(".%v", JSONQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute) } } @@ -192,10 +169,10 @@ func createQuery(jsonQuery *entity.QueryParsedJSON) *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 + attribute = JSONQuery.Entities[modifier.ID].Constraints[modifier.AttributeIndex].Attribute } else { - attribute = jsonQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute + attribute = JSONQuery.Relations[modifier.ID].Constraints[modifier.AttributeIndex].Attribute } diff --git a/aql/convertQueryBenchmark_test.go b/aql/convertQueryBenchmark_test.go index 2d579810d472c7a75cd7757b8c4c8d7dd0dd5aa2..ed4aef11a496ec326ebe8264534c52a671e6864b 100644 --- a/aql/convertQueryBenchmark_test.go +++ b/aql/convertQueryBenchmark_test.go @@ -1,6 +1,11 @@ package aql -import "testing" +import ( + "encoding/json" + "testing" + + "git.science.uu.nl/datastrophe/query-conversion/aql/entity" +) func BenchmarkConvertEmptyQuery(b *testing.B) { // Setup for test @@ -17,10 +22,14 @@ func BenchmarkConvertEmptyQuery(b *testing.B) { "limit": 5000 }`) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + b.ResetTimer() for i := 0; i < b.N; i++ { - service.ConvertQuery(&query) + service.ConvertQuery(&JSONQuery) } } @@ -53,10 +62,14 @@ func BenchmarkConvertOneAttributeQuery(b *testing.B) { "limit": 5000 }`) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + b.ResetTimer() for i := 0; i < b.N; i++ { - service.ConvertQuery(&query) + service.ConvertQuery(&JSONQuery) } } @@ -144,9 +157,13 @@ func BenchmarkConvertTwoRelationQuery(b *testing.B) { "limit": 5000 }`) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + b.ResetTimer() for i := 0; i < b.N; i++ { - service.ConvertQuery(&query) + service.ConvertQuery(&JSONQuery) } } diff --git a/aql/convertQuery_test.go b/aql/convertQuery_test.go index 171c4ceb5b4ecd844a31e329505f25128c16d99e..bf8dcbe908ea1f33c84d789d1c5f93b32fad389d 100644 --- a/aql/convertQuery_test.go +++ b/aql/convertQuery_test.go @@ -1,10 +1,12 @@ package aql import ( + "encoding/json" "errors" "strings" "testing" + "git.science.uu.nl/datastrophe/query-conversion/aql/entity" "github.com/stretchr/testify/assert" ) @@ -23,7 +25,11 @@ func TestEmptyQueryConversion(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -65,7 +71,11 @@ func TestEntityOneAttributeQuery(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -126,7 +136,11 @@ func TestRelationWithConstraint(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -175,7 +189,11 @@ func TestModifierCountEntity(t *testing.T) { ] }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -223,7 +241,11 @@ func TestModifierCountEntityAttribute(t *testing.T) { ] }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -291,7 +313,11 @@ func TestModifierCountRelation(t *testing.T) { ] }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -359,7 +385,11 @@ func TestModifierCountRelationAttribute(t *testing.T) { ] }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -432,7 +462,11 @@ func TestRelationWithInOutConstraint(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -528,7 +562,11 @@ func TestTwoRelations(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -582,7 +620,11 @@ func TestRelationWithOnlyToNode(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -638,7 +680,11 @@ func TestTooManyReturnEntities(t *testing.T) { "limit": 5000 }`) - _, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + _, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.Equal(t, errors.New("non-existing entity referenced in return"), err) @@ -688,7 +734,11 @@ func TestTooManyReturnRelations(t *testing.T) { "limit": 5000 }`) - _, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + _, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.Equal(t, errors.New("non-existing relation referenced in return"), err) @@ -739,7 +789,11 @@ func TestNegativeReturnEntities(t *testing.T) { "limit": 5000 }`) - _, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + _, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.Equal(t, errors.New("non-existing entity referenced in return"), err) @@ -772,7 +826,11 @@ func TestNoRelationsField(t *testing.T) { "limit": 5000 }`) - convertedResult, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + convertedResult, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) @@ -826,7 +884,11 @@ func TestEntityFromLowerThanNegativeOneInRelation(t *testing.T) { "limit": 5000 }`) - _, _, err := service.ConvertQuery(&query) + // Unmarshall the incoming message into an IncomingJSONQuery object + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + _, err := service.ConvertQuery(&JSONQuery) // Assert that there is no error assert.NoError(t, err) diff --git a/aql/entity/queryStruct.go b/aql/entity/queryStruct.go index 14ecebe354f5bca3e54998917e697f9419ca9cf2..ec04a6e7e28e15a806060d4ab5cabf54def159f2 100644 --- a/aql/entity/queryStruct.go +++ b/aql/entity/queryStruct.go @@ -1,7 +1,7 @@ package entity -// QueryParsedJSON is used for JSON conversion of the incoming byte array -type QueryParsedJSON struct { +// IncomingQueryJSON describes the query coming into the service in JSON format +type IncomingQueryJSON struct { DatabaseName string Return QueryReturnStruct Entities []QueryEntityStruct diff --git a/interface.go b/interface.go index b7dbbb5dfc0352e30503d274468d72a9a7c62512..5818f30299af54613a915835df8de332963e2239 100644 --- a/interface.go +++ b/interface.go @@ -1,6 +1,8 @@ package query +import "git.science.uu.nl/datastrophe/query-conversion/aql/entity" + // A Converter converts an incoming message in our JSON format to a format like AQL or Cypher type Converter interface { - ConvertQuery(jsonMsg *[]byte) (*string, *string, error) + ConvertQuery(JSONQuery *entity.IncomingQueryJSON) (*string, error) }