Skip to content
Snippets Groups Projects
clustering.go 6.83 KiB
Newer Older
Joris's avatar
Joris committed
package cypher

import (
	"errors"
Joris's avatar
Joris committed
	"fmt"

	"git.science.uu.nl/graphpolaris/query-conversion/entity"
)

// 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) {

	// cluster is a set for all pills (entities e0 e1 e2, relations r0 .., groub by g0 ..)
	cluster := make(map[string]bool)
Joris's avatar
Joris committed
	if len(JSONQuery.Relations) > 0 {
		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
Joris's avatar
Joris committed
			to := fmt.Sprintf("%v%v", string(JSONQuery.Relations[0].ToType[0]), JSONQuery.Relations[0].ToID)
Joris's avatar
Joris committed
			cluster[to] = true
		}

		if JSONQuery.Relations[0].FromID != -1 {
Joris's avatar
Joris committed
			from := fmt.Sprintf("%v%v", string(JSONQuery.Relations[0].FromType[0]), JSONQuery.Relations[0].FromID)
Joris's avatar
Joris committed
			cluster[from] = true
		}

	} else if len(JSONQuery.GroupBys) > 0 {
		gb := fmt.Sprintf("g%v", JSONQuery.GroupBys[0].ID)
		cluster[gb] = true

Joris's avatar
Joris committed
		group := fmt.Sprintf("%v%v", string(JSONQuery.GroupBys[0].GroupType[0]), JSONQuery.GroupBys[0].GroupID)
Joris's avatar
Joris committed
		cluster[group] = true

Joris's avatar
Joris committed
		by := fmt.Sprintf("%v%v", string(JSONQuery.GroupBys[0].ByType[0]), JSONQuery.GroupBys[0].ByID)
Joris's avatar
Joris committed
		cluster[by] = true

Joris's avatar
Joris committed
	} else {
		// If there is no relation or groupby then there is no query cluster atm
		// Needs to change when the summary pill is introduced
Joris's avatar
Joris committed
		return nil, nil, false
Joris's avatar
Joris committed

	for i := 0; i < 100; i++ {
Joris's avatar
Joris committed
		stop := true

		// Iteratively check to see if something is connected to the cluster
		// It should have skips for when something has already been added to the cluster, but due to complex connections (like an IN or groupby attached to a relation)
		// It is easier to just try everything everytime (and its computationally insignificant)
		// The loop stops when nothing was added for a round
Joris's avatar
Joris committed

		for _, rel := range JSONQuery.Relations {

			rela := fmt.Sprintf("r%v", rel.ID)

			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 {
Joris's avatar
Joris committed
				to := fmt.Sprintf("%v%v", string(rel.ToType[0]), rel.ToID)
Joris's avatar
Joris committed

				if cluster[to] {
					partOfCluster = true
				}
			}

			if rel.FromID != -1 {
Joris's avatar
Joris committed
				from := fmt.Sprintf("%v%v", string(rel.FromType[0]), rel.FromID)
Joris's avatar
Joris committed

				if cluster[from] {
					partOfCluster = true
				}
			}

			if partOfCluster {
				if rel.ToID != -1 {
Joris's avatar
Joris committed
					to := fmt.Sprintf("%v%v", string(rel.ToType[0]), rel.ToID)
Joris's avatar
Joris committed
					cluster[to] = true
				}

				if rel.FromID != -1 {
Joris's avatar
Joris committed
					from := fmt.Sprintf("%v%v", string(rel.FromType[0]), rel.FromID)
Joris's avatar
Joris committed
					cluster[from] = true
				}

Joris's avatar
Joris committed
				cluster[rela] = true
Joris's avatar
Joris committed
				stop = false
			}
Joris's avatar
Joris committed
		// 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)
Joris's avatar
Joris committed

Joris's avatar
Joris committed
			for _, con := range ent.Constraints {
				if con.InID != -1 {
					in := fmt.Sprintf("%v%v", string(con.InType[0]), con.InID)
Joris's avatar
Joris committed

Joris's avatar
Joris committed
					if cluster[in] {
						cluster[self] = true
						stop = false
Joris's avatar
Joris committed
					}
				}
			}
Joris's avatar
Joris committed
		}

		// Now the same for Group by's
		for _, gb := range JSONQuery.GroupBys {
			gby := fmt.Sprintf("g%v", gb.ID)

			// 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)

Joris's avatar
Joris committed
			group := fmt.Sprintf("%v%v", string(gb.GroupType[0]), gb.GroupID)
			by := fmt.Sprintf("%v%v", string(gb.ByType[0]), gb.ByID)
Joris's avatar
Joris committed

			if cluster[group] || cluster[by] {
				cluster[gby] = true
				cluster[group] = true
				cluster[by] = true
				stop = false
			}

		}

		if stop {
			// No new entities were added to the cluster, thus it is finished
			break
		}
	}

	// Now walk through the JSON and divide it into the cluster and rest
	restJSON := entity.IncomingQueryJSON{DatabaseName: JSONQuery.DatabaseName, Limit: JSONQuery.Limit}
	clusterJSON := entity.IncomingQueryJSON{DatabaseName: JSONQuery.DatabaseName, Limit: JSONQuery.Limit}
	isRest := false

	// Loop through entities
	for _, ent := range JSONQuery.Entities {
		name := fmt.Sprintf("e%v", ent.ID)

		if cluster[name] {
			clusterJSON.Entities = append(clusterJSON.Entities, ent)
		} else {
			restJSON.Entities = append(restJSON.Entities, ent)
			isRest = true
		}
	}

	// Loop through relations
	for _, rel := range JSONQuery.Relations {
		name := fmt.Sprintf("r%v", rel.ID)

		if cluster[name] {
			clusterJSON.Relations = append(clusterJSON.Relations, rel)
		} else {
			restJSON.Relations = append(restJSON.Relations, rel)
			isRest = true
		}
	}

	// Loop through groupby's
	for _, gb := range JSONQuery.GroupBys {
		name := fmt.Sprintf("g%v", gb.ID)

		if cluster[name] {
			clusterJSON.GroupBys = append(clusterJSON.GroupBys, gb)
		} else {
			restJSON.GroupBys = append(restJSON.GroupBys, gb)
			isRest = true
		}
	}

	return &clusterJSON, &restJSON, isRest
}

// checkNoDeadEnds checks to see if al from's and to's exist
func checkNoDeadEnds(JSONQuery *entity.IncomingQueryJSON) (bool, error) {

	// Check for all the connections of a relation
	for _, rel := range JSONQuery.Relations {
		if rel.FromID != -1 {
			if rel.FromType == "entity" {
				ent := JSONQuery.FindE(rel.FromID)
				if ent == nil {
					return false, errors.New("Invalid query")
				}
			} else if rel.FromType == "groupBy" {
				gb := JSONQuery.FindG(rel.FromID)
				if gb == nil {
					return false, errors.New("Invalid query")
				}
			}
		}

		if rel.ToID != -1 {
			if rel.ToType == "entity" {
				ent := JSONQuery.FindE(rel.ToID)
				if ent == nil {
					return false, errors.New("Invalid query")
				}
			} else if rel.ToType == "groupBy" {
				gb := JSONQuery.FindG(rel.ToID)
				if gb == nil {
					return false, errors.New("Invalid query")
				}
			}
		}
	}

	// Check for all the connections of a group by
	for _, gb := range JSONQuery.GroupBys {
		if gb.GroupType == "entity" {
			ent := JSONQuery.FindE(gb.GroupID)
			if ent == nil {
				return false, errors.New("Invalid query")
			}
		}

		if gb.GroupType == "relation" {
			rel := JSONQuery.FindE(gb.GroupID)
			if rel == nil {
				return false, errors.New("Invalid query")
			}
		}

		if gb.ByType == "entity" {
			ent := JSONQuery.FindE(gb.ByID)
			if ent == nil {
				return false, errors.New("Invalid query")
			}
		}

		if gb.ByType == "relation" {
			rel := JSONQuery.FindE(gb.ByID)
			if rel == nil {
				return false, errors.New("Invalid query")
			}
		}
	}

	// Check all the connections of IN-statements
	for _, ent := range JSONQuery.Entities {
		if len(ent.Constraints) == 0 {
			continue
		}

		for _, cons := range ent.Constraints {
			if cons.InID == -1 {
				continue
			}

			gb := JSONQuery.FindG(cons.InID)
			if gb == nil {
				return false, errors.New("Invalid query")
			}
		}
	}

	return true, nil
}