Click here to Skip to main content
15,868,141 members
Articles / Database Development / NoSQL

RavenDB - An Introduction

,
Rate me:
Please Sign up or sign in to vote.
4.87/5 (38 votes)
28 Apr 2010CPOL7 min read 260.5K   2.7K   112  
An introduction to RavenDB - a new open source .NET document database using .NET 4.0 and VS 2010
using System;
using Newtonsoft.Json.Linq;
using Raven.Database.Exceptions;

namespace Raven.Database.Json
{
    public class JsonPatcher
    {
        private readonly JObject document;

        public JsonPatcher(JObject document)
        {
            this.document = document;
        }

        public JObject Apply(JArray patch)
        {
            foreach (JObject patchCmd in patch)
            {
                Apply(patchCmd);
            }
            return document;
        }

        private void Apply(JObject patchCmd)
        {
            if (patchCmd["type"] == null)
                throw new InvalidOperationException("Patch property must have a type property");
            if (patchCmd["name"] == null)
                throw new InvalidOperationException("Patch property must have a name property");
            var propName = patchCmd["type"].Value<string>();
            switch (propName)
            {
                case "set":
                    AddProperty(patchCmd, patchCmd["name"].Value<string>());
                    break;
                case "unset":
                    RemoveProperty(patchCmd, patchCmd["name"].Value<string>());
                    break;
                case "add":
                    AddValue(patchCmd, patchCmd["name"].Value<string>());
                    break;
                case "insert":
                    InsertValue(patchCmd, patchCmd["name"].Value<string>());
                    break;
                case "remove":
                    RemoveValue(patchCmd, patchCmd["name"].Value<string>());
                    break;
                case "modify":
                    ModifyValue(patchCmd, patchCmd["name"].Value<string>());
                    break;
                default:
                    throw new ArgumentException("Cannot understand command: " + propName);
            }
        }

        private void ModifyValue(JObject patchCmd, string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property == null)
                throw new InvalidOperationException("Cannot modify value from  '" + propName + "' because it was not found");

            var val = patchCmd["value"];
            if (val == null || val.Type != JsonTokenType.Array)
                throw new InvalidOperationException("Cannot understand modified value from  '" + propName +
                                                    "' because it was not found or not an array of commands");
            
            switch (property.Value.Type)
            {
                case JsonTokenType.Object:
                    foreach (JToken cmd in val.Value<JArray>())
                    {
                        var nestedDoc = property.Value.Value<JObject>();
                        new JsonPatcher(nestedDoc)
                            .Apply(cmd.Value<JObject>());
                    }
                    break;
                case JsonTokenType.Array:
                     var positionToken = patchCmd["position"];
                     if (positionToken == null || positionToken.Type != JsonTokenType.Integer)
                         throw new InvalidOperationException("Cannot modify value from  '" + propName +
                                                             "' because position element does not exists or not an integer");
                    var position = positionToken.Value<int>();
                    var value = property.Value.Value<JArray>()[position];
                    foreach (JToken cmd in val.Value<JArray>())
                    {
                        new JsonPatcher(value.Value<JObject>())
                            .Apply(cmd.Value<JObject>());
                    }
                    break;
                default:
                    throw new InvalidOperationException("Can't understand how to deal with: " + property.Value.Type);
            }
        }

        private void RemoveValue(JObject patchCmd, string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property == null)
            {
                property = new JProperty(propName, new JArray());
                document.Add(property);
            }
            var array = property.Value as JArray;
            if (array == null)
                throw new InvalidOperationException("Cannot remove value from  '" + propName + "' because it is not an array");
            var positionToken = patchCmd["position"];
            if (positionToken == null || positionToken.Type != JsonTokenType.Integer)
                throw new InvalidOperationException("Cannot remove value from  '" + propName + "' because position element does not exists or not an integer");
            var position = positionToken.Value<int>();
            if (position < 0 || position >= array.Count)
                throw new IndexOutOfRangeException("Cannot remove value from  '" + propName +
                                                   "' because position element is out of bound bounds");
            array.RemoveAt(position);
        }

        private void InsertValue(JObject patchCmd, string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property == null)
            {
                property = new JProperty(propName, new JArray());
                document.Add(property);
            }
            var array = property.Value as JArray;
            if (array == null)
                throw new InvalidOperationException("Cannot remove value from  '" + propName + "' because it is not an array");
            var positionToken = patchCmd["position"];
            if (positionToken == null || positionToken.Type != JsonTokenType.Integer)
                throw new InvalidOperationException("Cannot remove value from  '" + propName + "' because position element does not exists or not an integer");
            var position = positionToken.Value<int>();
            if (position < 0 || position >= array.Count)
                throw new IndexOutOfRangeException("Cannot remove value from  '" + propName +
                                                   "' because position element is out of bound bounds");
            array.Insert(position, patchCmd["value"]);
        }

        private void AddValue(JObject patchCmd, string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property == null)
            {
                property = new JProperty(propName, new JArray());
                document.Add(property);
            }
            var array = property.Value as JArray;
            if (array == null)
                throw new InvalidOperationException("Cannot insert value to '" + propName + "' because it is not an array");

            array.Add(patchCmd["value"]);
        }

        private void RemoveProperty(JObject patchCmd,string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property != null)
                property.Remove();
        }

        private void AddProperty(JObject patchCmd, string propName)
        {
            var property = document.Property(propName);
            EnsurePreviousValueMatchCurrentValue(patchCmd, property);
            if (property == null)
            {
                property = new JProperty(propName);
                document.Add(property);
            }
            property.Value = patchCmd["value"];
        }

        private static void EnsurePreviousValueMatchCurrentValue(JObject patchCmd, JProperty property)
        {
            var prevVal = patchCmd["prevVal"];
            if (prevVal == null)
                return;
            switch (prevVal.Type)
            {
                case JsonTokenType.Undefined:
                    if (property != null)
                        throw new ConcurrencyException();
                    break;
                default:
                    if(property == null)
                        throw new ConcurrencyException();
                    var equalityComparer = new JTokenEqualityComparer();
                    if (equalityComparer.Equals(property.Value, prevVal) == false)
                        throw new ConcurrencyException();
                    break;
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I've been a software developer since 1996 and have enjoyed C# since 2003. I have a Bachelor's degree in Computer Science and for some reason, a Master's degree in Business Administration. I currently do software development contracting/consulting.

Written By
Israel Israel
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions