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