Skip to content

Commit

Permalink
Added GraphModel (#67)
Browse files Browse the repository at this point in the history
* Added GraphModel

Generally lifted from OrigoDB:

https://raw.githubusercontent.com/DevrexLabs/OrigoDB/dev/src/OrigoDB.Core/Modeling/GraphModel.cs

Added tests including end-to-end smoke tests covering basic usage and proving journaling/replaying of graph.

* PR-67 Resolves issues in graph model
  • Loading branch information
Simcon authored and rofr committed Sep 28, 2018
1 parent f906fbf commit 0b4aa0f
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/Memstate.Core/Models/Graph/Edge.cs
@@ -0,0 +1,13 @@
namespace Memstate.Models.Graph
{
public partial class GraphModel
{
public class Edge : Item
{
public Edge(long id, string label) : base(id, label) { }

public Node From;
public Node To;
}
}
}
114 changes: 114 additions & 0 deletions src/Memstate.Core/Models/Graph/GraphModel.cs
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Memstate.Models.Graph
{
public partial class GraphModel
{
/// <summary>
/// Unique id generator, shared by nodes and edges
/// </summary>
private long _lastId;

//Nodes and edges
private readonly SortedDictionary<long, Node> _nodesById;
private readonly SortedDictionary<long, Edge> _edgesById;

private SortedDictionary<string, SortedSet<Node>> _nodesByLabel;
private SortedDictionary<string, SortedSet<Edge>> _edgesByLabel;


public IEnumerable<Node> Nodes
{
get { return _nodesById.Values; }
}

public IEnumerable<Edge> Edges
{
get { return _edgesById.Values; }
}

public GraphModel()
{
var ignoreCase = StringComparer.OrdinalIgnoreCase;
_edgesById = new SortedDictionary<long, Edge>();
_edgesByLabel = new SortedDictionary<string, SortedSet<Edge>>(ignoreCase);
_nodesById = new SortedDictionary<long, Node>();
_nodesByLabel = new SortedDictionary<string, SortedSet<Node>>(ignoreCase);
}

public long CreateNode(string label)
{
var id = ++_lastId;
var node = new Node(id, label);
_nodesById[id] = node;
AddByLabel(_nodesByLabel, node, label);
return id;
}

public long CreateEdge(long fromId, long toId, string label)
{
Node from = NodeById(fromId);
Node to = NodeById(toId);
var id = ++_lastId;
var edge = new Edge(id, label) { From = from, To = to };
_edgesById[id] = edge;
AddByLabel(_edgesByLabel, edge, label);
from.Out.Add(edge);
to.In.Add(edge);
return id;
}

public void RemoveEdge(long id)
{
var edge = EdgeById(id);
_edgesById.Remove(id);
edge.From.Out.Remove(edge);
edge.To.In.Remove(edge);
_edgesByLabel[edge.Label].Remove(edge);
}

public void RemoveNode(long id)
{
var node = NodeById(id);
foreach (var edge in node.Out) RemoveEdge(edge.Id);
foreach (var edge in node.In) RemoveEdge(edge.Id);
_nodesById.Remove(id);
_nodesByLabel[node.Label].Remove(node);
}

public T Query<T>(Expression<Func<GraphModel, T>> query)
{
return query.Compile().Invoke(this);
}

private Node NodeById(long id)
{
return GetById(_nodesById, id);
}

private Edge EdgeById(long id)
{
return GetById(_edgesById, id);
}

private T GetById<T>(IDictionary<long, T> items, long id)
{
if (items.TryGetValue(id, out var item)) return item;
throw new ArgumentException("No such node: " + id);
}

private static void AddByLabel<T>(IDictionary<string, SortedSet<T>> index, T item, string label)
{
SortedSet<T> set;
if (!index.TryGetValue(label, out set))
{
set = new SortedSet<T>();
index[label] = set;
}
set.Add(item);
}

}
}
53 changes: 53 additions & 0 deletions src/Memstate.Core/Models/Graph/Item.cs
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;

namespace Memstate.Models.Graph
{
public partial class GraphModel
{
[Serializable]
public abstract class Item : IComparable<Item>
{
public readonly long Id;
public readonly string Label;
public readonly SortedDictionary<string, object> Props;

protected Item(long id, string label)
{
Id = id;
Label = label;
Props = new SortedDictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}

public object Get(string key)
{
object result;
Props.TryGetValue(key, out result);
return result;
}

public void Set(string key, object value)
{
Props[key] = value;
}

public int CompareTo(Item other)
{
return Math.Sign(Id - other.Id);
}

public override bool Equals(object obj)
{
return obj != null
&& obj.GetType() == GetType() //Edge == Node should always be false
&& ((Item)obj).Id == Id;
}

public override int GetHashCode()
{
return Id.GetHashCode();
}
}

}
}
15 changes: 15 additions & 0 deletions src/Memstate.Core/Models/Graph/Node.cs
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace Memstate.Models.Graph
{
public partial class GraphModel
{
public class Node : Item
{
public Node(long id, string label) : base(id, label) { }

public ISet<Edge> Out = new SortedSet<Edge>();
public ISet<Edge> In = new SortedSet<Edge>();
}
}
}
@@ -0,0 +1,26 @@
using Memstate.Models.Graph;
using System.Linq;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Commands
{
public class CreateEdge : Command<GraphModel, Edge>
{
public long From { get; private set; }
public long To { get; private set; }
public string Label { get; private set; }

public CreateEdge(long from, long to, string label)
{
From = from;
To = to;
Label = label;
}

public override Edge Execute(GraphModel model)
{
var edgeId = model.CreateEdge(From, To, Label);
return model.Edges.Single(e => e.Id == edgeId);
}
}
}
@@ -0,0 +1,22 @@
using Memstate.Models.Graph;
using System.Linq;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Commands
{
public class CreateNode : Command<GraphModel, Node>
{
public string Label { get; private set; }

public CreateNode(string label)
{
Label = label;
}

public override Node Execute(GraphModel model)
{
var nodeId = model.CreateNode(Label);
return model.Nodes.Single(n => n.Id == nodeId);
}
}
}
@@ -0,0 +1,27 @@
using Memstate.Models.Graph;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Commands
{
public class IncrementLikes : Command<GraphModel, Node>
{
public long TweetId { get; private set; }

public IncrementLikes(long id)
{
TweetId = id;
}

public override Node Execute(GraphModel model)
{
var tweet = model.Nodes.SingleOrDefault(n => n.Id == TweetId);
object likes = tweet.Get("likes") ?? (long)0;
tweet.Set("likes", (long)likes + 1);
return tweet;
}
}
}
@@ -0,0 +1,14 @@
using Memstate.Models.Graph;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Queries
{
public class GetEdges : Query<GraphModel, IEnumerable<Edge>>
{
public override IEnumerable<Edge> Execute(GraphModel db) => new List<Edge>(db.Edges);
}
}
@@ -0,0 +1,11 @@
using Memstate.Models.Graph;
using System.Collections.Generic;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Queries
{
public class GetNodes : Query<GraphModel, IEnumerable<Node>>
{
public override IEnumerable<Node> Execute(GraphModel db) => new List<Node>(db.Nodes);
}
}
@@ -0,0 +1,18 @@
using Memstate.Models.Graph;
using System.Collections.Generic;
using System.Linq;
using static Memstate.Models.Graph.GraphModel;

namespace Memstate.Docs.GettingStarted.QuickStartGraph.Queries
{
public class GetUsersWithTweets : Query<GraphModel, IEnumerable<Node>>
{
public override IEnumerable<Node> Execute(GraphModel db)
{
var users = db.Nodes.Where(n => n.Label == "user");
var tweetEdges = users.SelectMany(e => e.Out.Where(r => r.Label == "tweeted"));
var tweets = tweetEdges.Select(e => e.To);
return tweets;
}
}
}

0 comments on commit 0b4aa0f

Please sign in to comment.