| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 | /* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */package graphimport (	"bytes"	"encoding/binary"	"encoding/gob"	"fmt"	"strings"	"devt.de/krotik/common/stringutil"	"devt.de/krotik/eliasdb/graph/data"	"devt.de/krotik/eliasdb/graph/util"	"devt.de/krotik/eliasdb/hash"	"devt.de/krotik/eliasdb/storage")// Helper functions for GraphManager// =================================/*checkPartitionName checks if a given partition name is valid.*/func (gm *Manager) checkPartitionName(part string) error {	if !stringutil.IsAlphaNumeric(part) {		return &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("Partition name %v is not alphanumeric - can only contain [a-zA-Z0-9_]", part),		}	}	return nil}/*checkNode checks if a given node can be written to the datastore.*/func (gm *Manager) checkNode(node data.Node) error {	return gm.checkItemGeneral(node, "Node")}/*checkItemGeneral checks the general properties of a given graph item.*/func (gm *Manager) checkItemGeneral(node data.Node, name string) error {	if node.Key() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: name + " is missing a key value"}	}	if node.Kind() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: name + " is missing a kind value"}	}	if !stringutil.IsAlphaNumeric(node.Kind()) {		return &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("%v kind %v is not alphanumeric - can only contain [a-zA-Z0-9_]", name, node.Kind()),		}	}	for attr := range node.Data() {		if attr == "" {			return &util.GraphError{Type: util.ErrInvalidData, Detail: name + " contains empty string attribute name"}		}	}	return nil}/*checkEdge checks if a given edge can be written to the datastore.*/func (gm *Manager) checkEdge(edge data.Edge) error {	if err := gm.checkItemGeneral(edge, "Edge"); err != nil {		return err	}	if edge.End1Key() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a key value for end1"}	}	if edge.End1Kind() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a kind value for end1"}	}	if edge.End1Role() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a role value for end1"}	} else if !stringutil.IsAlphaNumeric(edge.End1Role()) {		return &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("Edge role %v is not alphanumeric - can only contain [a-zA-Z0-9_]", edge.End1Role()),		}	}	if _, ok := edge.Attr(data.EdgeEnd1Cascading).(bool); !ok {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a cascading value for end1"}	}	if edge.End2Key() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a key value for end2"}	}	if edge.End2Kind() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a kind value for end2"}	}	if edge.End2Role() == "" {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a role value for end2"}	} else if !stringutil.IsAlphaNumeric(edge.End2Role()) {		return &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("Edge role %v is not alphanumeric - can only contain [a-zA-Z0-9_]", edge.End2Role()),		}	}	if _, ok := edge.Attr(data.EdgeEnd2Cascading).(bool); !ok {		return &util.GraphError{Type: util.ErrInvalidData, Detail: "Edge is missing a cascading value for end2"}	}	return nil}/*writeNodeCount writes a new node count for a specific kind to the datastore.*/func (gm *Manager) writeNodeCount(kind string, count uint64, flush bool) error {	numstr := make([]byte, 8)	binary.LittleEndian.PutUint64(numstr, count)	gm.gs.MainDB()[MainDBNodeCount+kind] = string(numstr)	if flush {		return gm.gs.FlushMain()	}	return nil}/*writeEdgeCount writes a new edge count for a specific kind to the datastore.*/func (gm *Manager) writeEdgeCount(kind string, count uint64, flush bool) error {	numstr := make([]byte, 8)	binary.LittleEndian.PutUint64(numstr, count)	gm.gs.MainDB()[MainDBEdgeCount+kind] = string(numstr)	if flush {		return gm.gs.FlushMain()	}	return nil}/*getNodeStorageHTree gets two HTree instances which can be used to store nodes.This function ensures that depending entries in other datastructures do exist.*/func (gm *Manager) getNodeStorageHTree(part string, kind string,	create bool) (*hash.HTree, *hash.HTree, error) {	gm.storageMutex.Lock()	defer gm.storageMutex.Unlock()	// Check if the partition name is valid	if err := gm.checkPartitionName(part); err != nil {		return nil, nil, err	}	// Check if the node kind is valid	if !stringutil.IsAlphaNumeric(kind) {		return nil, nil, &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("Node kind %v is not alphanumeric - can only contain [a-zA-Z0-9_]", kind),		}	}	// Make sure all required lookup maps are there	if gm.getMainDBMap(MainDBNodeKinds) == nil {		gm.storeMainDBMap(MainDBNodeKinds, make(map[string]string))	}	if gm.getMainDBMap(MainDBParts) == nil {		gm.storeMainDBMap(MainDBParts, make(map[string]string))	}	if gm.getMainDBMap(MainDBNodeAttrs+kind) == nil {		gm.storeMainDBMap(MainDBNodeAttrs+kind, make(map[string]string))	}	if gm.getMainDBMap(MainDBNodeEdges+kind) == nil {		gm.storeMainDBMap(MainDBNodeEdges+kind, make(map[string]string))	}	if _, ok := gm.gs.MainDB()[MainDBNodeCount+kind]; !ok {		gm.gs.MainDB()[MainDBNodeCount+kind] = string(make([]byte, 8, 8))	}	// Return the actual storage	gs := gm.gs.StorageManager(part+kind+StorageSuffixNodes, create)	if gs == nil {		return nil, nil, nil	}	attrTree, err := gm.getHTree(gs, RootIDNodeHTree)	if err != nil {		return nil, nil, err	}	valTree, err := gm.getHTree(gs, RootIDNodeHTreeSecond)	if err != nil {		return nil, nil, err	}	return attrTree, valTree, nil}/*getEdgeStorageHTree gets a HTree which can be used to store edges. This function ensures that dependingentries in other datastructures do exist.*/func (gm *Manager) getEdgeStorageHTree(part string, kind string, create bool) (*hash.HTree, error) {	gm.storageMutex.Lock()	defer gm.storageMutex.Unlock()	// Check if the partition name is valid	if err := gm.checkPartitionName(part); err != nil {		return nil, err	}	// Check if the edge kind is valid	if !stringutil.IsAlphaNumeric(kind) {		return nil, &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("Edge kind %v is not alphanumeric - can only contain [a-zA-Z0-9_]", kind),		}	}	// Make sure all required lookup maps are there	if gm.getMainDBMap(MainDBEdgeKinds) == nil {		gm.storeMainDBMap(MainDBEdgeKinds, make(map[string]string))	}	if gm.getMainDBMap(MainDBEdgeAttrs+kind) == nil {		gm.storeMainDBMap(MainDBEdgeAttrs+kind, make(map[string]string))	}	if _, ok := gm.gs.MainDB()[MainDBEdgeCount+kind]; !ok {		gm.gs.MainDB()[MainDBEdgeCount+kind] = string(make([]byte, 8, 8))	}	// Return the actual storage	gs := gm.gs.StorageManager(part+kind+StorageSuffixEdges, create)	if gs == nil {		return nil, nil	}	return gm.getHTree(gs, RootIDNodeHTree)}/*getNodeIndexHTree gets a HTree which can be used to index nodes.*/func (gm *Manager) getNodeIndexHTree(part string, kind string, create bool) (*hash.HTree, error) {	return gm.getIndexHTree(part, kind, create, "Node", StorageSuffixNodesIndex)}/*getEdgeIndexHTree gets a HTree which can be used to index edges.*/func (gm *Manager) getEdgeIndexHTree(part string, kind string, create bool) (*hash.HTree, error) {	return gm.getIndexHTree(part, kind, create, "Edge", StorageSuffixEdgesIndex)}/*getIndexHTree gets a HTree which can be used to index items.*/func (gm *Manager) getIndexHTree(part string, kind string, create bool, name string, suffix string) (*hash.HTree, error) {	gm.storageMutex.Lock()	defer gm.storageMutex.Unlock()	// Check if the partition name is valid	if err := gm.checkPartitionName(part); err != nil {		return nil, err	}	// Check if the kind is valid	if !stringutil.IsAlphaNumeric(kind) {		return nil, &util.GraphError{			Type:   util.ErrInvalidData,			Detail: fmt.Sprintf("%v kind %v is not alphanumeric - can only contain [a-zA-Z0-9_]", name, kind),		}	}	gs := gm.gs.StorageManager(part+kind+suffix, create)	if gs == nil {		return nil, nil	}	return gm.getHTree(gs, RootIDNodeHTree)}/*flushNodeStorage flushes a node storage.*/func (gm *Manager) flushNodeStorage(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixNodes, false); sm != nil {		if err := sm.Flush(); err != nil {			return &util.GraphError{Type: util.ErrFlushing, Detail: err.Error()}		}	}	return nil}/*flushNodeIndex flushes a node index.*/func (gm *Manager) flushNodeIndex(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixNodesIndex, false); sm != nil {		if err := sm.Flush(); err != nil {			return &util.GraphError{Type: util.ErrFlushing, Detail: err.Error()}		}	}	return nil}/*flushEdgeStorage flushes an edge storage.*/func (gm *Manager) flushEdgeStorage(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixEdges, false); sm != nil {		if err := sm.Flush(); err != nil {			return &util.GraphError{Type: util.ErrFlushing, Detail: err.Error()}		}	}	return nil}/*flushEdgeIndex flushes an edge index.*/func (gm *Manager) flushEdgeIndex(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixEdgesIndex, false); sm != nil {		if err := sm.Flush(); err != nil {			return &util.GraphError{Type: util.ErrFlushing, Detail: err.Error()}		}	}	return nil}/*rollbackNodeStorage rollbacks a node storage.*/func (gm *Manager) rollbackNodeStorage(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixNodes, false); sm != nil {		if err := sm.Rollback(); err != nil {			return &util.GraphError{Type: util.ErrRollback, Detail: err.Error()}		}	}	return nil}/*rollbackNodeIndex rollbacks a node index.*/func (gm *Manager) rollbackNodeIndex(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixNodesIndex, false); sm != nil {		if err := sm.Rollback(); err != nil {			return &util.GraphError{Type: util.ErrRollback, Detail: err.Error()}		}	}	return nil}/*rollbackEdgeStorage rollbacks an edge storage.*/func (gm *Manager) rollbackEdgeStorage(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixEdges, false); sm != nil {		if err := sm.Rollback(); err != nil {			return &util.GraphError{Type: util.ErrRollback, Detail: err.Error()}		}	}	return nil}/*rollbackEdgeIndex rollbacks an edge index.*/func (gm *Manager) rollbackEdgeIndex(part string, kind string) error {	if sm := gm.gs.StorageManager(part+kind+StorageSuffixEdgesIndex, false); sm != nil {		if err := sm.Rollback(); err != nil {			return &util.GraphError{Type: util.ErrRollback, Detail: err.Error()}		}	}	return nil}/*getHTree creates or loads a HTree from a given StorageManager. HTrees are not cachedsince the creation shouldn't have too much overhead.*/func (gm *Manager) getHTree(sm storage.Manager, slot int) (*hash.HTree, error) {	var htree *hash.HTree	var err error	loc := sm.Root(slot)	if loc == 0 {		// Create a new HTree and store its location		htree, err = hash.NewHTree(sm)		if err != nil {			err = &util.GraphError{Type: util.ErrAccessComponent, Detail: err.Error()}		} else {			sm.SetRoot(slot, htree.Location())		}	} else {		// Load existing HTree		htree, err = hash.LoadHTree(sm, loc)		if err != nil {			err = &util.GraphError{Type: util.ErrAccessComponent, Detail: err.Error()}		}	}	return htree, err}/*getMainDBMap gets a map from the main database.*/func (gm *Manager) getMainDBMap(key string) map[string]string {	// First try to cache	mapval, ok := gm.mapCache[key]	if ok {		return mapval	}	// Lookup map and decode it	val, ok := gm.gs.MainDB()[key]	if ok {		mapval = stringToMap(val)		gm.mapCache[key] = mapval	}	return mapval}/*storeMainDBMap stores a map in the main database. The map is stored as a gob byte slice.Once it has been decoded it is cached for read operations.*/func (gm *Manager) storeMainDBMap(key string, mapval map[string]string) {	gm.mapCache[key] = mapval	gm.gs.MainDB()[key] = mapToString(mapval)}// Static helper functions// =======================/*IsFullSpec is a function to determine if a given spec is a fully specified spec(i.e. all spec components are specified)*/func IsFullSpec(spec string) bool {	sspec := strings.Split(spec, ":")	if len(sspec) != 4 || sspec[0] == "" || sspec[1] == "" || sspec[2] == "" || sspec[3] == "" {		return false	}	return true}/*mapToString turns a map of strings into a single string.*/func mapToString(stringmap map[string]string) string {	bb := &bytes.Buffer{}	gob.NewEncoder(bb).Encode(stringmap)	return string(bb.Bytes())}/*stringToMap turns a string into a map of strings.*/func stringToMap(mapString string) map[string]string {	var stringmap map[string]string	if err := gob.NewDecoder(bytes.NewBufferString(mapString)).Decode(&stringmap); err != nil {		panic(fmt.Sprint("Cannot decode:", mapString, err))	}	return stringmap}
 |