From 61ce693511f0015398ff267cdff8a641172c12cd Mon Sep 17 00:00:00 2001 From: Joris <joris.l@hotmail.com> Date: Tue, 16 Nov 2021 15:56:45 +0100 Subject: [PATCH] Query converter appears to work Tests are needed however --- cypher/convertQueryNew.go | 442 ++++++++++++++++++++++++++----- cypher/convertQueryNew_test.go | 470 ++++++++++++++++++++++++++++++++- cypher/healthChecks.go | 95 +++---- entity/queryStruct.go | 58 ++-- 4 files changed, 908 insertions(+), 157 deletions(-) diff --git a/cypher/convertQueryNew.go b/cypher/convertQueryNew.go index 734a93e..3ffcd57 100644 --- a/cypher/convertQueryNew.go +++ b/cypher/convertQueryNew.go @@ -3,75 +3,79 @@ package cypher import ( "errors" "fmt" + "strings" "git.science.uu.nl/graphpolaris/query-conversion/entity" ) +// ConvertQuery2 func (s *Service) ConvertQuery2(totalJSONQuery *entity.IncomingQueryJSON) (*string, error) { - - ok, err := performBasicHealthCheck(totalJSONQuery) - if !ok { - // Kind of a placeholder for better error handling - return nil, err - } - - finalCypher := make([]string, 0) + var finalCypher *string // ** CHECK THAT THEY ARE NOT EQUAL IF RECURSED OTHERWISE FINAL RETURN WONT WORK queryJSON := totalJSONQuery // Flattened out recursion on the JSON - for { - query, rest, isRest := checkForQueryCluster(queryJSON) - ok, err := checkQueryValidity(query) - if ok { - finalCypher = append(finalCypher, *createCypher(query)) - } else { - // do something with the error - fmt.Println(err) - } + // for { - if !isRest { - break - } else { - queryJSON = rest + // Okay: er zou eigenlijk per cluster een aparte query gedaan moeten worden, maar voor nu pakt ie gewoon ff de eerste + query, rest, isRest := checkForQueryCluster(queryJSON) + + //fmt.Println(query) + if isRest { + fmt.Println("Rest:") + fmt.Println(rest) + } + + ok, err := checkEnoughReturn(query) + if ok { + finalCypher, err = createCypher(query) + if err != nil { + return nil, err } + } else { + // do something with the error + fmt.Println(err) } - // Okay nadenktijd: Kan deze wel achteraan, want wil je de dingen die boven een groupby staan wel gereturned hebben? - // Zo nee, dan moet je dus eerst weten wat er uberhaupt gereturned moet worden, aka een hierarchy functie - finalCypher = append(finalCypher, *createReturnStatement(totalJSONQuery)) + // if !isRest { + // break + // } else { + // queryJSON = rest + // } + // } - // Code that checks if all pills are connected - // if so: build the query + //fmt.Println(*finalCypher) - // else: - // Code that checks to see if the disconnected pieces are valid queries - // code that builds the queries - return nil, nil + return finalCypher, nil } // createCypher creates queries without the return statement, due to the possibility of multiple disconnected queries -func createCypher(JSONQuery *entity.IncomingQueryJSON) *string { - //queryHierarchy := magicHierarchyFunction(JSONQuery) - - /* - Match (deel 1) - Constraints op entities - Unwind as r0 - With * - Constraints op r0 - - Dan weer door - */ - return nil -} +func createCypher(JSONQuery *entity.IncomingQueryJSON) (*string, error) { -// NOTE MOET MISSCHIEN ANDERS -// createReturnStatement creates the final return statement, connecting all previous cypher together -func createReturnStatement(JSONQuery *entity.IncomingQueryJSON) *string { - // Hier dus weer de vraag of dingen boven een GROUP BY gereturned dienen te worden Lijkt mij niet - return nil + // ** NOTE: wat als er 2 clusters aan queries zijn, maar de een returned een tabel (want eindigt op een group by) en de ander is nodes en edges? + + hierarchy, err := createQueryHierarchy(JSONQuery) + if err != nil { + //TODO + return nil, errors.New("") + } + + cypher, err := formQuery(JSONQuery, hierarchy) + if err != nil { + return nil, errors.New("") + //TODO + } + + returnStatement, err := createReturnStatement(JSONQuery, hierarchy) + if err != nil { + return nil, errors.New("") + //TODO + } + + finalCypher := *cypher + *returnStatement + + return &finalCypher, nil } type queryPart struct { @@ -84,14 +88,78 @@ type queryPart struct { type query []queryPart func (q query) find(qID int, qType string) *queryPart { - for _, part := range q { - if part.qID == qID && part.qType == qType { - return &part + for i := range q { + if q[i].qID == qID && q[i].qType == qType { + return &q[i] } } return nil } +// NOTE MOET MISSCHIEN ANDERS +// createReturnStatement creates the final return statement, connecting all previous cypher together +func createReturnStatement(JSONQuery *entity.IncomingQueryJSON, parts query) (*string, error) { + // Hier dus weer de vraag of dingen boven een GROUP BY gereturned dienen te worden Lijkt mij niet + var retStatement string + + // First check to see if the return is a table (due to a groupby at the end) or if it is nodelink data + numOfParts := len(parts) + if parts[numOfParts-1].qType == "groupBy" { + // Return is a table + groupBy := JSONQuery.FindG(parts[numOfParts-1].qID) + + gName := fmt.Sprintf("%v_%v", groupBy.AppliedModifier, groupBy.GroupAttribute) + by := fmt.Sprintf("%v%v.%v", string(groupBy.ByType[0]), groupBy.ByID, groupBy.ByAttribute) + byName := strings.Replace(by, ".", "_", 1) + + retStatement = fmt.Sprintf("RETURN %v, %v", byName, gName) + } else { + // Return is nodelink + // Loop through the parts of the query from back to front + retStatement = "RETURN " + lineStart := "" + for i := numOfParts - 1; i >= 0; i-- { + part := parts[i] + if part.qType == "relation" { + rel := JSONQuery.FindR(part.qID) + retStatement += fmt.Sprintf("%v r%v", lineStart, rel.ID) + lineStart = "," + + if rel.FromID != -1 { + if rel.FromType == "entity" { + + retStatement += fmt.Sprintf("%v e%v", lineStart, rel.FromID) + } else { + id := JSONQuery.FindG(rel.FromID).ByID + retStatement += fmt.Sprintf("%v eg%v", lineStart, id) + } + } + + if rel.ToID != -1 { + if rel.ToType == "entity" { + + retStatement += fmt.Sprintf("%v e%v", lineStart, rel.ToID) + } else { + id := JSONQuery.FindG(rel.ToID).ByID + retStatement += fmt.Sprintf("%v eg%v", lineStart, id) + } + } + } else if part.qType == "entity" { + // TODO + + // Probably ends with a break to, since a single entity is always connected via an in? (maybe not in case of ONLY having an entity as the entire query) + } else { + // Then it is a groupby which must not be returned, thus the returns are done. + break + } + } + } + + retStatement = retStatement + "\n" + fmt.Sprintf("LIMIT %v", JSONQuery.Limit) + + return &retStatement, nil +} + func (q query) selectByID(ID int) *queryPart { for _, part := range q { if part.partID == ID { @@ -151,7 +219,7 @@ func createQueryHierarchy(JSONQuery *entity.IncomingQueryJSON) (query, error) { continue } - if rel.FromID == rela.ToID && rel.FromType == "relation" { + if rel.FromID == rela.ToID && rel.FromType == rela.ToType { part := parts.find(rel.ID, "relation") part.dependencies = append(part.dependencies, parts.find(rela.ID, "relation").partID) } @@ -166,7 +234,8 @@ func createQueryHierarchy(JSONQuery *entity.IncomingQueryJSON) (query, error) { for _, gb := range JSONQuery.GroupBys { if (rel.FromID == gb.ID && rel.FromType == "groupBy") || (rel.ToID == gb.ID && rel.ToType == "groupBy") { part := parts.find(rel.ID, "relation") - part.dependencies = append(part.dependencies, parts.find(gb.ID, "groupBy").partID) + gbID := parts.find(gb.ID, "groupBy").partID + part.dependencies = append(part.dependencies, gbID) } } } @@ -177,8 +246,10 @@ func createQueryHierarchy(JSONQuery *entity.IncomingQueryJSON) (query, error) { // Check if the gb is connected to the relation if (gb.ByID == rela.ID && gb.ByType == "relation") || // Is the By connected to a relation (gb.GroupID == rela.ID && gb.GroupType == "relation") || // is the Group connected to a relation - ((gb.ByID == rela.FromID || gb.ByID == rela.ToID) && gb.ByType == "entity") || // Is the by connected to an entity connected to the relation - ((gb.GroupID == rela.FromID || gb.GroupID == rela.ToID) && gb.GroupType == "entity") { // Is the Group connected to an entity connected to the relation + (gb.ByID == rela.FromID && gb.ByType == rela.FromType) || // Is the by connected to an entity connected to the relation + (gb.ByID == rela.ToID && gb.ByType == rela.ToType) || // Is the by connected to an entity connected to the relation + (gb.GroupID == rela.FromID && gb.GroupType == rela.FromType) || // Is the by connected to an entity connected to the relation + (gb.GroupID == rela.ToID && gb.GroupType == rela.ToType) { // Is the by connected to an entity connected to the relation part := parts.find(gb.ID, "groupBy") part.dependencies = append(part.dependencies, parts.find(rela.ID, "relation").partID) } @@ -251,10 +322,15 @@ func createQueryHierarchy(JSONQuery *entity.IncomingQueryJSON) (query, error) { return nil, errors.New("Cyclic query detected") } - return sortedQuery, nil + //Reverse the list + retQuery := make([]queryPart, len(sortedQuery)) + for i := 0; i < len(sortedQuery); i++ { + retQuery[i] = sortedQuery[len(sortedQuery)-i-1] + } + return retQuery, nil - // ** HOUD NOG GEEN REKENING MET INS, MOET NOG WEL - // maar is wat lastiger omdat dat wat extra checks vergt + // ** HOUD NOG GEEN REKENING MET INS (van entities naar groupbys), MOET NOG WEL + // maar is wat lastiger omdat dat wat extra checks vergt, alle code voor ins moet ook in de volgende functies worden gestopt // Maak van alle rels en gb's een query part // Loop door alle rels heen en kijk of hun FROM een TO is van een andere rel --> dependency @@ -266,3 +342,251 @@ func createQueryHierarchy(JSONQuery *entity.IncomingQueryJSON) (query, error) { // Returned I guess een list van query parts met de dependencies naar beneden zodat je van boven naar beneden de lijst kan doorlopen // om de query te maken } + +func formQuery(JSONQuery *entity.IncomingQueryJSON, hierarchy query) (*string, error) { + + // ** NOTE: this does not create a return statement, that has yet to be made (which will also probably use the hierarchy) + + // Traverse through the hierarchy and for every entry create a part like: + // Match (deel 1) + // Constraints op entities + // Unwind as r0 + // With * + // Constraints op r0 + totalQuery := "" + + for _, entry := range hierarchy { + var cypher *string + var err error + + switch entry.qType { + case "relation": + cypher, err = createRelationCypher(JSONQuery, entry) + if err != nil { + return nil, err + } + break + case "groupBy": + cypher, err = createGroupByCypher(JSONQuery, entry) + if err != nil { + return nil, err + } + + break + case "entity": + // This would be in case of an IN or if there was only 1 entity in the query builder + break + default: + // Should never be reached + return nil, errors.New("Invalid query pill type detected") + } + + totalQuery += *cypher + } + + return &totalQuery, nil +} + +// createRelationCypher takes the json and a query part, finds the necessary entities and converts it into cypher +func createRelationCypher(JSONQuery *entity.IncomingQueryJSON, part queryPart) (*string, error) { + + rel := JSONQuery.FindR(part.qID) + + if (rel.FromID == -1) && (rel.ToID == -1) { + // Now there is only a relation, which we do not allow + return nil, errors.New("Relation only queries are not supported") + } + + var match, eConstraints, unwind, rConstraints string + + // There is some duplicate code here below that could be omitted with extra if-statements, but that is something to do + // for a later time. Since this way it is easier to understand the flow of the code + if rel.ToID == -1 { + // There is no To, only a From + var eName string + var ent *entity.QueryEntityStruct + + if rel.FromType == "entity" { + + ent = JSONQuery.FindE(rel.ToID) + eName = fmt.Sprintf("e%v", ent.ID) + + } else if rel.FromType == "groupBy" { + gb := JSONQuery.FindG(rel.FromID) + if gb.ByType == "relation" { + return nil, errors.New("Invalid query: cannot connect a relation to a group by that groups by another relation") + } + + ent = JSONQuery.FindE(gb.ByID) + // this is a sort of dummy variable, since it is not directly visible in the query, but it is definitely needed + eName = fmt.Sprintf("e%v", ent.ID) + } else { + // Should never be reachable + return nil, errors.New("Invalid connection type to relation") + } + + match = fmt.Sprintf("MATCH p%v = (%v:%v)-[:%v*%v..%v]-()\n", part.partID, eName, ent.Name, rel.Name, rel.Depth.Min, rel.Depth.Max) + + eConstraints = "" + newLineStatement := "\tWHERE" + for _, v := range ent.Constraints { + eConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, eName)) + newLineStatement = "\tAND" + } + + // Add an IN clause, connecting the relation to the output of the groupby + if rel.FromType == "groupBy" { + gb := JSONQuery.FindG(rel.FromID) + inConstraint := fmt.Sprintf("%v %v.%v IN %v_%v \n", newLineStatement, eName, gb.ByAttribute, gb.AppliedModifier, gb.ByAttribute) + eConstraints += inConstraint + } + + } else if rel.FromID == -1 { + var eName string + var ent *entity.QueryEntityStruct + + if rel.ToType == "entity" { + ent = JSONQuery.FindE(rel.ToID) + eName = fmt.Sprintf("e%v", ent.ID) + + } else if rel.ToType == "groupBy" { + gb := JSONQuery.FindG(rel.ToID) + if gb.ByType == "relation" { + return nil, errors.New("Invalid query: cannot connect a relation to a group by that groups by another relation") + } + + ent = JSONQuery.FindE(gb.ByID) + // this is a sort of dummy variable, since it is not directly visible in the query, but it is definitely needed + eName = fmt.Sprintf("e%v", ent.ID) + } else { + // Should never be reachable + return nil, errors.New("Invalid connection type to relation") + } + + match = fmt.Sprintf("MATCH p%v = ()-[:%v*%v..%v]-(%v:%v)\n", part.partID, rel.Name, rel.Depth.Min, rel.Depth.Max, eName, ent.Name) + + eConstraints = "" + newLineStatement := "\tWHERE" + for _, v := range ent.Constraints { + eConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, eName)) + newLineStatement = "\tAND" + } + + // Add an IN clause, connecting the relation to the output of the groupby + if rel.ToType == "groupBy" { + gb := JSONQuery.FindG(rel.ToID) + inConstraint := fmt.Sprintf("%v %v.%v IN %v_%v \n", newLineStatement, eName, gb.ByAttribute, gb.AppliedModifier, gb.ByAttribute) + eConstraints += inConstraint + } + + } else { + var eTName string + var entFrom *entity.QueryEntityStruct + var eFName string + var entTo *entity.QueryEntityStruct + + // Check of what type the To is + if rel.ToType == "entity" { + entTo = JSONQuery.FindE(rel.ToID) + eTName = fmt.Sprintf("e%v", entTo.ID) + + } else if rel.ToType == "groupBy" { + gb := JSONQuery.FindG(rel.ToID) + if gb.ByType == "relation" { + return nil, errors.New("Invalid query: cannot connect a relation to a group by that groups by another relation") + } + + entTo = JSONQuery.FindE(gb.ByID) + // this is a sort of dummy variable, since it is not directly visible in the query, but it is definitely needed + eTName = fmt.Sprintf("e%v", entTo.ID) + } else { + // Should never be reachable + return nil, errors.New("Invalid connection type to relation") + } + + // Check of what type the From is + if rel.FromType == "entity" { + + entFrom = JSONQuery.FindE(rel.FromID) + eFName = fmt.Sprintf("e%v", entFrom.ID) + + } else if rel.FromType == "groupBy" { + gb := JSONQuery.FindG(rel.FromID) + if gb.ByType == "relation" { + return nil, errors.New("Invalid query: cannot connect a relation to a group by that groups by another relation") + } + + entFrom = JSONQuery.FindE(gb.ByID) + // this is a sort of dummy variable, since it is not directly visible in the query, but it is definitely needed + eFName = fmt.Sprintf("eg%v", entFrom.ID) + } else { + // Should never be reachable + return nil, errors.New("Invalid connection type to relation") + } + + match = fmt.Sprintf("MATCH p%v = (%v:%v)-[:%v*%v..%v]-(%v:%v)\n", part.partID, eFName, entFrom.Name, rel.Name, rel.Depth.Min, rel.Depth.Max, eTName, entTo.Name) + + eConstraints = "" + newLineStatement := "\tWHERE" + for _, v := range entFrom.Constraints { + eConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, eFName)) + newLineStatement = "\tAND" + } + for _, v := range entTo.Constraints { + eConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, eTName)) + newLineStatement = "\tAND" + } + + // Add an IN clause, connecting the relation to the output of the groupby + if rel.ToType == "groupBy" { + gb := JSONQuery.FindG(rel.ToID) + inConstraint := fmt.Sprintf("%v %v.%v IN %v_%v \n", newLineStatement, eTName, gb.ByAttribute, strings.Replace(eFName, "g", "", 1), gb.ByAttribute) + eConstraints += inConstraint + newLineStatement = "\tAND" + } + + if rel.FromType == "groupBy" { + gb := JSONQuery.FindG(rel.FromID) + inConstraint := fmt.Sprintf("%v %v.%v IN %v_%v \n", newLineStatement, eFName, gb.ByAttribute, strings.Replace(eFName, "g", "", 1), gb.ByAttribute) + eConstraints += inConstraint + } + } + + rName := fmt.Sprintf("r%v", part.qID) + unwind = fmt.Sprintf("UNWIND relationships(p%v) as %v \nWITH *\n", part.partID, rName) + + rConstraints = "" + newLineStatement := "\tWHERE" + for _, v := range rel.Constraints { + rConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, rName)) + newLineStatement = "\tAND" + } + + retString := match + eConstraints + unwind + rConstraints + return &retString, nil + +} + +// createGroupByCypher takes the json and a query part, finds the group by and converts it into cypher +func createGroupByCypher(JSONQuery *entity.IncomingQueryJSON, part queryPart) (*string, error) { + groupBy := JSONQuery.FindG(part.qID) + + gName := fmt.Sprintf("%v_%v", groupBy.AppliedModifier, groupBy.GroupAttribute) + by := fmt.Sprintf("%v%v.%v", string(groupBy.ByType[0]), groupBy.ByID, groupBy.ByAttribute) + byName := strings.Replace(by, ".", "_", 1) + group := fmt.Sprintf("%v%v.%v", string(groupBy.GroupType[0]), groupBy.GroupID, groupBy.GroupAttribute) + + // If you do not use a *, then everything needs to be aliased + with := fmt.Sprintf("WITH %v AS %v, %v(%v) AS %v \n", by, byName, groupBy.AppliedModifier, group, gName) + + // ** HOW TO ADRESS THE AGGREGATED VALUE? + gConstraints := "" + newLineStatement := "\tWHERE" + for _, v := range groupBy.Constraints { + gConstraints += fmt.Sprintf("%v %v \n", newLineStatement, *createConstraintBoolExpression(&v, gName)) + newLineStatement = "\tAND" + } + + retString := with + gConstraints + return &retString, nil +} diff --git a/cypher/convertQueryNew_test.go b/cypher/convertQueryNew_test.go index f58479e..c856096 100644 --- a/cypher/convertQueryNew_test.go +++ b/cypher/convertQueryNew_test.go @@ -9,6 +9,7 @@ import ( ) func Test1(t *testing.T) { + // Works, but, the AVG function is applied to a string, so that doesnt work, but the translation does :D query := []byte(`{ "databaseName": "Movies3", "return": { @@ -28,13 +29,13 @@ func Test1(t *testing.T) { "entities": [ { "id": 0, - "type": "Person", + "name": "Person", "constraints": [ { "attribute": "name", "value": "Raymond Campbell", "dataType": "string", - "matchType": "EQ", + "matchType": "NEQ", "inID": -1, "inType": "" } @@ -42,12 +43,12 @@ func Test1(t *testing.T) { }, { "id": 1, - "type": "Movie", + "name": "Movie", "constraints": [] }, { "id": 2, - "type": "Genre", + "name": "Genre", "constraints": [] } ], @@ -84,10 +85,10 @@ func Test1(t *testing.T) { "id": 0, "groupType": "entity", "groupID": 0, - "groupAttribute": "age??????", + "groupAttribute": "bornIn", "byType": "entity", "byID": 1, - "byAttribute": "ID????????", + "byAttribute": "imdbId", "appliedModifier": "AVG", "relationID": 0, "constraints": [] @@ -98,14 +99,465 @@ func Test1(t *testing.T) { }`) var JSONQuery entity.IncomingQueryJSON - err := json.Unmarshal(query, &JSONQuery) + json.Unmarshal(query, &JSONQuery) - hierarchy, err := createQueryHierarchy(&JSONQuery) + // hierarchy, err := createQueryHierarchy(&JSONQuery) + // if err != nil { + // fmt.Println(err) + // } + + // fmt.Println(hierarchy) + + s := NewService() + cypher, err := s.ConvertQuery2(&JSONQuery) + if err != nil { + fmt.Println(err) + } + + fmt.Println(*cypher) + + t.Fail() + +} +func Test2(t *testing.T) { + // Works, but, the AVG function is applied to a string, so that doesnt work, but the translation does :D + query := []byte(`{ + "databaseName": "TweedeKamer", + "return": { + "entities": [ + 0, + 1, + 2 + ], + "relations": [ + 0, + 1 + ] + }, + "entities": [ + { + "name": "parliament", + "ID": 0, + "constraints": [ + { + "attribute": "name", + "value": "Geert", + "dataType": "string", + "matchType": "contains" + } + ] + }, + { + "name": "parties", + "ID": 1, + "constraints": [] + }, + { + "name": "resolutions", + "ID": 2, + "constraints": [] + } + ], + "relations": [ + { + "ID": 0, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 0, + "toType": "entity", + "toID": 1, + "constraints": [] + }, + { + "ID": 1, + "name": "submits", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 0, + "toType": "entity", + "toID": 2, + "constraints": [] + } + ], + "groupBys": [], + "machineLearning": [], + "limit": 5000 + } + `) + + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + s := NewService() + cypher, err := s.ConvertQuery2(&JSONQuery) + if err != nil { + fmt.Println(err) + } + + fmt.Println(*cypher) + + t.Fail() + +} +func Test3(t *testing.T) { + // Works, but, the AVG function is applied to a string, so that doesnt work, but the translation does :D + query := []byte(`{ + "databaseName": "TweedeKamer", + "return": { + "entities": [ + 0, + 1, + 2, + 3, + 4 + ], + "relations": [ + 0, + 1, + 2, + 3 + ] + }, + "entities": [ + { + "name": "parliament", + "ID": 0, + "constraints": [ + { + "attribute": "name", + "value": "A", + "dataType": "string", + "matchType": "contains" + } + ] + }, + { + "name": "parties", + "ID": 1, + "constraints": [ + { + "attribute": "seats", + "value": "10", + "dataType": "int", + "matchType": "LT" + } + ] + }, + { + "name": "resolutions", + "ID": 2, + "constraints": [ + { + "attribute": "date", + "value": "mei", + "dataType": "string", + "matchType": "contains" + } + ] + }, + { + "name": "parliament", + "ID": 3, + "constraints": [] + }, + { + "name": "parties", + "ID": 4, + "constraints": [ + { + "attribute": "name", + "value": "Volkspartij voor Vrijheid en Democratie", + "dataType": "string", + "matchType": "==" + } + ] + } + ], + "relations": [ + { + "ID": 0, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 0, + "toType": "entity", + "toID": 1, + "constraints": [] + }, + { + "ID": 1, + "name": "submits", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 0, + "toType": "entity", + "toID": 2, + "constraints": [] + }, + { + "ID": 2, + "name": "submits", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 3, + "toType": "entity", + "toID": 2, + "constraints": [] + }, + { + "ID": 3, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromID": 3, + "toType": "entity", + "toID": 4, + "constraints": [] + } + ], + "groupBys": [], + "machineLearning": [], + "limit": 5000 + } + + `) + + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + s := NewService() + cypher, err := s.ConvertQuery2(&JSONQuery) + if err != nil { + fmt.Println(err) + } + + fmt.Println(*cypher) + + t.Fail() + +} + +func Test4(t *testing.T) { + // Works, but, the AVG function is applied to a string, so that doesnt work, but the translation does :D + query := []byte(`{ + "return": { + "entities": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "relations": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + }, + "entities": [ + { + "name": "parliament", + "ID": 0, + "constraints": [ + { + "attribute": "name", + "value": "Geert", + "dataType": "string", + "matchType": "contains" + } + ] + }, + { + "name": "commissions", + "ID": 1, + "constraints": [] + }, + { + "name": "parliament", + "ID": 2, + "constraints": [] + }, + { + "name": "parties", + "ID": 3, + "constraints": [ + { + "attribute": "seats", + "value": "10", + "dataType": "int", + "matchType": "LT" + } + ] + }, + { + "name": "resolutions", + "ID": 4, + "constraints": [ + { + "attribute": "date", + "value": "mei", + "dataType": "string", + "matchType": "contains" + } + ] + }, + { + "name": "resolutions", + "ID": 5, + "constraints": [] + }, + { + "name": "parties", + "ID": 6, + "constraints": [] + } + , + { + "name": "parliament", + "ID": 7, + "constraints": [] + } + + ], + "groupBys": [], + "relations": [ + { + "ID": 0, + "name": "part_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 0, + "toType": "entity", + "toID": 1, + "constraints": [] + }, + { + "ID": 1, + "name": "part_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 2, + "toType": "entity", + "toID": 1, + "constraints": [] + }, + { + "ID": 2, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 2, + "toType": "entity", + "toID": 3, + "constraints": [] + }, + { + "ID": 3, + "name": "submits", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 2, + "toType": "entity", + "toID": 4, + "constraints": [] + }, + { + "ID": 4, + "name": "submits", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 0, + "toType": "entity", + "toID": 5, + "constraints": [] + }, + { + "ID": 5, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 0, + "toType": "entity", + "toID": 6, + "constraints": [] + } + , + { + "ID": 6, + "name": "member_of", + "depth": { + "min": 1, + "max": 1 + }, + "fromType": "entity", + "fromId": 7, + "toType": "entity", + "toID": 6, + "constraints": [] + } + ], + "machineLearning": [], + "limit": 5000 + } + + + `) + + var JSONQuery entity.IncomingQueryJSON + json.Unmarshal(query, &JSONQuery) + + s := NewService() + cypher, err := s.ConvertQuery2(&JSONQuery) if err != nil { fmt.Println(err) } - fmt.Println(hierarchy) + fmt.Println(*cypher) + t.Fail() } diff --git a/cypher/healthChecks.go b/cypher/healthChecks.go index 1502b53..4c781cb 100644 --- a/cypher/healthChecks.go +++ b/cypher/healthChecks.go @@ -7,39 +7,6 @@ import ( "git.science.uu.nl/graphpolaris/query-conversion/entity" ) -func performBasicHealthCheck(JSONQuery *entity.IncomingQueryJSON) (bool, error) { - - // Check to make sure all indexes exist - // How many entities are there - numEntities := len(JSONQuery.Entities) - 1 - // How many relations there are - numRelations := 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 > numEntities || e < 0 { - return false, 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 > numEntities || r.ToID > numEntities { - return false, 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 > numRelations || r < 0 { - return false, errors.New("non-existing relation referenced in return") - } - } - - return true, nil -} - // checkForQueryCluster will detect (and separate?) if there are multiple queries in the query panel and will try to sepate the queries. // Maybe also delete floating pills that have no connection (but that is a different function) func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.IncomingQueryJSON, *entity.IncomingQueryJSON, bool) { @@ -51,20 +18,20 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming // Dit is het startpunt van de cluster, vrij veel if elses ivm half afgemaakte queries // Lots of existance checks + if len(JSONQuery.Relations) > 0 { - fmt.Println("he") rel := fmt.Sprintf("r%v", JSONQuery.Relations[0].ID) cluster[rel] = true if JSONQuery.Relations[0].ToID != -1 { // Take the first letter: entities with ID 0 -> e0 - to := fmt.Sprintf("%v%v", JSONQuery.Relations[0].ToType[0], JSONQuery.Relations[0].ToID) + to := fmt.Sprintf("%v%v", string(JSONQuery.Relations[0].ToType[0]), JSONQuery.Relations[0].ToID) cluster[to] = true } if JSONQuery.Relations[0].FromID != -1 { - from := fmt.Sprintf("%v%v", JSONQuery.Relations[0].FromType[0], JSONQuery.Relations[0].FromID) + from := fmt.Sprintf("%v%v", string(JSONQuery.Relations[0].FromType[0]), JSONQuery.Relations[0].FromID) cluster[from] = true } @@ -73,17 +40,20 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming cluster[gb] = true // TODO: Wat te doen als de groupby niet goed is aangesloten, want dat crasht ie nogal atm - group := fmt.Sprintf("%v%v", JSONQuery.GroupBys[0].GroupType[0], JSONQuery.GroupBys[0].GroupID) + group := fmt.Sprintf("%v%v", string(JSONQuery.GroupBys[0].GroupType[0]), JSONQuery.GroupBys[0].GroupID) cluster[group] = true - by := fmt.Sprintf("%v%v", JSONQuery.GroupBys[0].ByType[0], JSONQuery.GroupBys[0].ByID) + by := fmt.Sprintf("%v%v", string(JSONQuery.GroupBys[0].ByType[0]), JSONQuery.GroupBys[0].ByID) cluster[by] = true + } else { + return nil, nil, false } // Relation toevoegen aan de map // Is er geen relation doe dan groupby // is die er ook niet dan rip - for { + + for i := 0; i < 100; i++ { stop := true // kijk langs alle relations en group bys of ie verbonden is aan de cluster en nog niet in de set zit @@ -102,7 +72,7 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming partOfCluster := false // Now comes the check to see if one of its endpoints is in the cluster, meaning everything is in the cluster if rel.ToID != -1 { - to := fmt.Sprintf("%v%v", rel.ToType[0], rel.ToID) + to := fmt.Sprintf("%v%v", string(rel.ToType[0]), rel.ToID) if cluster[to] { partOfCluster = true @@ -110,8 +80,7 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming } if rel.FromID != -1 { - from := fmt.Sprintf("%v%v", rel.FromType[0], rel.FromID) - cluster[from] = true + from := fmt.Sprintf("%v%v", string(rel.FromType[0]), rel.FromID) if cluster[from] { partOfCluster = true @@ -120,37 +89,37 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming if partOfCluster { if rel.ToID != -1 { - to := fmt.Sprintf("%v%v", rel.ToType[0], rel.ToID) + to := fmt.Sprintf("%v%v", string(rel.ToType[0]), rel.ToID) cluster[to] = true } if rel.FromID != -1 { - from := fmt.Sprintf("%v%v", rel.FromType[0], rel.FromID) + from := fmt.Sprintf("%v%v", string(rel.FromType[0]), rel.FromID) cluster[from] = true } + cluster[rela] = true stop = false } + } - // Check to see if an entity is connected to the cluster via an 'IN' - for _, ent := range JSONQuery.Entities { - self := fmt.Sprintf("e%v", ent.ID) - if cluster[self] { - continue - } + // Check to see if an entity is connected to the cluster via an 'IN' + for _, ent := range JSONQuery.Entities { + self := fmt.Sprintf("e%v", ent.ID) + if cluster[self] { + continue + } - for _, con := range ent.Constraints { - if con.InID != -1 { - in := fmt.Sprintf("%v%v", con.InType[0], con.InID) + for _, con := range ent.Constraints { + if con.InID != -1 { + in := fmt.Sprintf("%v%v", string(con.InType[0]), con.InID) - if cluster[in] { - cluster[self] = true - stop = false - } + if cluster[in] { + cluster[self] = true + stop = false } } } - } // Now the same for Group by's @@ -163,8 +132,8 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming // It should have been checked that the connections of the group by are valid, since a group by must have all connections filled (in contrary of a relation) - group := fmt.Sprintf("%v%v", gb.GroupType[0], gb.GroupID) - by := fmt.Sprintf("%v%v", gb.ByType[0], gb.ByID) + group := fmt.Sprintf("%v%v", string(gb.GroupType[0]), gb.GroupID) + by := fmt.Sprintf("%v%v", string(gb.ByType[0]), gb.ByID) if cluster[group] || cluster[by] { cluster[gby] = true @@ -242,12 +211,12 @@ func checkForQueryCluster(JSONQuery *entity.IncomingQueryJSON) (*entity.Incoming } // ** MOGELIJK OBSOLETE, hangt af van de hierarchiefunctie -/* checkQueryValidity performs checks to see if the query is valid. +/* checkEnoughReturn performs checks to see if the query is valid. Returns a boolean indicating if the query is valid, the error will containt a custom message saying what is wrong with the query. It is obviously possible the query is still invalid, but that is for the database to find out. */ -func checkQueryValidity(JSONQuery *entity.IncomingQueryJSON) (bool, error) { +func checkEnoughReturn(JSONQuery *entity.IncomingQueryJSON) (bool, error) { // The first test is to see if there are at least 2 returns, since only one return is not possible (we do not allow relation only queries) ret := JSONQuery.Return @@ -257,4 +226,6 @@ func checkQueryValidity(JSONQuery *entity.IncomingQueryJSON) (bool, error) { } return true, nil + + // Wel nog hier de notitie dat als er bijv 1 entity is, dat er dan een zieke summary query moet komen } diff --git a/entity/queryStruct.go b/entity/queryStruct.go index 62afc65..acffc45 100644 --- a/entity/queryStruct.go +++ b/entity/queryStruct.go @@ -30,14 +30,14 @@ type QueryEntityStruct struct { // QueryRelationStruct encapsulates a single relation with its corresponding constraints type QueryRelationStruct struct { - ID int `json:"id"` - Name string `json:"name"` - Depth QuerySearchDepthStruct `json:"depth"` - FromType string `json:"fromType"` - FromID int `json:"fromID"` - ToType string `json:"toType"` - ToID int `json:"toID"` - QueryConstraintStruct []QueryConstraintStruct `json:"constraints"` + ID int `json:"id"` + Name string `json:"name"` + Depth QuerySearchDepthStruct `json:"depth"` + FromType string `json:"fromType"` + FromID int `json:"fromID"` + ToType string `json:"toType"` + ToID int `json:"toID"` + Constraints []QueryConstraintStruct `json:"constraints"` } type QueryGroupByStruct struct { @@ -86,28 +86,32 @@ type QuerySearchDepthStruct struct { Max int `json:"max"` } -// Moet misschien nog ff anders of opgesplitst worden, want interface is vervelend -func (JSONQuery IncomingQueryJSON) find(qID int, qType string) interface{} { - - if qType == "entity" { - for _, part := range JSONQuery.Entities { - if part.ID == qID { - return &part - } +// FindE finds the entity with a specified ID in an IncomingQueryJSON struct +func (JSONQuery IncomingQueryJSON) FindE(qID int) *QueryEntityStruct { + for _, part := range JSONQuery.Entities { + if part.ID == qID { + return &part } - } else if qType == "relation" { - for _, part := range JSONQuery.Relations { - if part.ID == qID { - return &part - } - } - } else if qType == "groupBy" { - for _, part := range JSONQuery.GroupBys { - if part.ID == qID { - return &part - } + } + return nil +} + +// FindR finds the relation with a specified ID in an IncomingQueryJSON struct +func (JSONQuery IncomingQueryJSON) FindR(qID int) *QueryRelationStruct { + for _, part := range JSONQuery.Relations { + if part.ID == qID { + return &part } } + return nil +} +// FindG finds the groupBy with a specified ID in an IncomingQueryJSON struct +func (JSONQuery IncomingQueryJSON) FindG(qID int) *QueryGroupByStruct { + for _, part := range JSONQuery.GroupBys { + if part.ID == qID { + return &part + } + } return nil } -- GitLab