Skip to content

Commit

Permalink
basic implementation complete. still need to work on a select clause …
Browse files Browse the repository at this point in the history
…that's mroe complex than just "select obj"
  • Loading branch information
kolosy committed Sep 15, 2009
1 parent b478c3f commit 6fd7e61
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 53 deletions.
32 changes: 29 additions & 3 deletions samples/Trivial/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Divan;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Divan.Linq;

namespace Trivial
{
Expand All @@ -20,7 +21,7 @@ namespace Trivial
class Program
{
static void Main(string[] args) {
string host = "localhost";
string host = "172.16.10.78";
int port = 5984;

// Lets you see all HTTP requests made by Divan
Expand All @@ -29,7 +30,7 @@ static void Main(string[] args) {
// Trivial parse of args to get host and port
switch (args.Length) {
case 0:
Console.WriteLine("Using localhost:5984");
Console.WriteLine("Using " + host + ":" + port);
break;
case 1:
Console.WriteLine("Using " + args[0] + ":5984");
Expand Down Expand Up @@ -89,6 +90,18 @@ static void Main(string[] args) {
var cars = db.GetAllDocuments<Car>();
Console.WriteLine("Loaded all Cars: " + cars.Count);

// Now try some linq
var tempQuery = CreateTempView("if (doc.docType && doc.docType == 'car') emit(doc.Hps, doc);", db);
var linqProvider = new CouchQueryProvider(db, tempQuery);
var linqCars = new CouchLinqQuery<Car>(linqProvider);

var fastCars = from c in linqCars where c.HorsePowers >= 175 select c;//.Make + " " + c.Model;
foreach (var fastCar in fastCars)
Console.WriteLine(fastCar);

// cleanup for later
db.DeleteDocument(tempQuery.Doc);

// Delete some Cars one by one. CouchDB is an MVCC database which means that for every operation that modifies a document
// we need to supply not only its document id, but also the revision that we are aware of. This means that we must supply id/rev
// for each document we want to delete.
Expand All @@ -107,7 +120,18 @@ static void Main(string[] args) {

// Delete the db itself
db.Delete();
Console.WriteLine("Deleted database");
Console.WriteLine("Deleted database\r\n\r\n. Press enter to close. ");

Console.ReadLine();
}

private static CouchViewDefinition CreateTempView(string mapText, CouchDatabase db)
{
var designDoc = new CouchDesignDocument("test", db);
var def = designDoc.AddView("test", "function (doc) {" + mapText + "}");
designDoc.Synch();

return def;
}

/// <summary>
Expand Down Expand Up @@ -138,6 +162,8 @@ public override void WriteJson(JsonWriter writer)
// This will write id and rev
base.WriteJson(writer);

writer.WritePropertyName("docType");
writer.WriteValue("car");
writer.WritePropertyName("Make");
writer.WriteValue(Make);
writer.WritePropertyName("Model");
Expand Down
86 changes: 55 additions & 31 deletions src/Linq/CouchLinqQuery.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
using System;
// adapted from sample code at http://linqinaction.net/files/folders/linqinaction/entry1952.aspx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections;
using System.Reflection;

namespace Divan.Linq
{
Expand All @@ -17,60 +21,80 @@ public CouchLinqQuery(Expression expression, CouchQueryProvider provider)
this.provider = provider;
}

#region IEnumerable<T> Members

public IEnumerator<T> GetEnumerator()
public CouchLinqQuery(CouchQueryProvider provider)
{
throw new NotImplementedException();
this.expression = Expression.Constant(this);
this.provider = provider;
}

#endregion

#region IEnumerable Members

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
Expression IQueryable.Expression
{
throw new NotImplementedException();
get { return this.expression; }
}

#endregion

#region IQueryable Members

public Type ElementType
Type IQueryable.ElementType
{
get { throw new NotImplementedException(); }
get { return typeof(T); }
}

public System.Linq.Expressions.Expression Expression
IQueryProvider IQueryable.Provider
{
get { throw new NotImplementedException(); }
get { return this.provider; }
}

public IQueryProvider Provider
class TransformingEnumerator<T> : IEnumerator<T>
{
get { throw new NotImplementedException(); }
}
private IEnumerator<T> e;
private MethodInfo transformer;

#endregion
public TransformingEnumerator(IEnumerator<T> e, MethodInfo transformer)
{
this.e = e;
this.transformer = transformer;
}

#region IQueryable Members
public T Current { get { return (T)transformer.Invoke(e.Current, null); } }
object IEnumerator.Current { get { return transformer.Invoke(e.Current, null); } }

Type IQueryable.ElementType
public void Dispose() { e.Dispose(); }
public bool MoveNext() { return e.MoveNext(); }
public void Reset() { e.Reset(); }
}

protected virtual IEnumerator<T> DoGetEnumerator<T>()
{
get { throw new NotImplementedException(); }
var expVisitor = this.provider.Prepare(expression);
var viewResult = (CouchGenericViewResult)expVisitor.Query.GetResult();

var dynamicResult =
viewResult
.GetType()
.GetMethods()
.First(m => m.Name == "ValueDocuments" && m.IsGenericMethodDefinition)
.MakeGenericMethod(new Type[] { typeof(T) })
.Invoke(viewResult, null);

if (expVisitor.SelectExpression == null)
return ((IEnumerable<T>)dynamicResult).GetEnumerator();

return
new TransformingEnumerator<T>(
((IEnumerable<T>)dynamicResult).GetEnumerator(),
((MethodCallExpression)expVisitor.SelectExpression).Method);
}

System.Linq.Expressions.Expression IQueryable.Expression
public IEnumerator<T> GetEnumerator()
{
get { throw new NotImplementedException(); }
return DoGetEnumerator<T>();
}

IQueryProvider IQueryable.Provider
IEnumerator IEnumerable.GetEnumerator()
{
get { throw new NotImplementedException(); }
return DoGetEnumerator<CouchDocument>();
}

#endregion
public override string ToString() { return this.provider.GetQueryText(this.expression); }
public override bool Equals(object obj) { return obj == null ? false : ToString().Equals(obj.ToString()); }
public override int GetHashCode() { return ToString().GetHashCode(); }
}
}
37 changes: 32 additions & 5 deletions src/Linq/CouchQueryProvider.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
// adapted from sample code at http://linqinaction.net/files/folders/linqinaction/entry1952.aspx

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
Expand All @@ -13,13 +15,21 @@ public class CouchQueryProvider: IQueryProvider
string view;
string design;

CouchViewDefinition definition;

public CouchQueryProvider(CouchDatabase db, string design, string view)
{
this.db = db;
this.view = view;
this.design = design;
}

public CouchQueryProvider(CouchDatabase db, CouchViewDefinition definition)
{
this.db = db;
this.definition = definition;
}

#region IQueryProvider Members

public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
Expand All @@ -42,19 +52,36 @@ public IQueryable CreateQuery(Expression expression)
}
}

protected virtual CouchQuery Prepare(Expression expression)
public virtual ExpressionVisitor Prepare(Expression expression)
{
return new ExpressionVisitor().ProcessExpression(expression, db, design, view);
return
definition == null ?
new ExpressionVisitor().ProcessExpression(expression, db, design, view) :
new ExpressionVisitor().ProcessExpression(expression, db, definition);
}

public TResult Execute<TResult>(Expression expression)
{
return (TResult)Execute(expression);// return Prepare(expression).GetResult<TResult>();
if (!typeof(CouchViewResult).IsAssignableFrom(typeof(TResult)))
throw new InvalidCastException("Only subclasses of CouchViewResult are supported. " + typeof(TResult).ToString() + " is not a valid result type.");

return (TResult)Execute(expression);
}

public object Execute(Expression expression)
{
return Prepare(expression).GetResult();
var expVisitor = Prepare(expression);
var result = expVisitor.Query.GetResult().RowDocuments().First();

if (expVisitor.SelectExpression == null)
return result;

return ((MethodCallExpression)expVisitor.SelectExpression).Method.Invoke(result, null);
}

public string GetQueryText(Expression expression)
{
return Prepare(expression).ToString();
}

#endregion
Expand Down
41 changes: 28 additions & 13 deletions src/Linq/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
// adapted from sample code at http://linqinaction.net/files/folders/linqinaction/entry1952.aspx

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -7,15 +9,23 @@

namespace Divan.Linq
{
internal class ExpressionVisitor
public class ExpressionVisitor
{
CouchQuery query;
public virtual CouchQuery Query { get; protected set; }
public virtual MethodCallExpression SelectExpression { get; protected set; }

public ExpressionVisitor ProcessExpression(Expression expression, CouchDatabase db, string design, string view)
{
Query = db.Query(design, view);
VisitExpression(expression);
return this;
}

public CouchQuery ProcessExpression(Expression expression, CouchDatabase db, string design, string view)
public ExpressionVisitor ProcessExpression(Expression expression, CouchDatabase db, CouchViewDefinition definition)
{
query = db.Query(design, view);
Query = db.Query(definition);
VisitExpression(expression);
return query;
return this;
}

private void VisitExpression(Expression expression)
Expand All @@ -26,13 +36,13 @@ private void VisitExpression(Expression expression)
VisitAndAlso((BinaryExpression)expression);
break;
case ExpressionType.Equal:
CallIfPresent((BinaryExpression)expression, (val) => query.Key(val));
CallIfPresent((BinaryExpression)expression, (val) => Query.Key(val));
break;
case ExpressionType.LessThanOrEqual:
CallIfPresent((BinaryExpression)expression, (val) => query.EndKey(val));
CallIfPresent((BinaryExpression)expression, (val) => Query.EndKey(val));
break;
case ExpressionType.GreaterThanOrEqual:
CallIfPresent((BinaryExpression)expression, (val) => query.StartKey(val));
CallIfPresent((BinaryExpression)expression, (val) => Query.StartKey(val));
break;
default:
if (expression is MethodCallExpression)
Expand All @@ -51,9 +61,12 @@ private void VisitAndAlso(BinaryExpression andAlso)

private void VisitMethodCall(MethodCallExpression expression)
{
if ((expression.Method.DeclaringType == typeof(Queryable)) &&
if ((expression.Method.DeclaringType == typeof(Queryable)) &&
(expression.Method.Name == "Where"))
VisitExpression(((UnaryExpression)expression.Arguments[1]).Operand);
else if ((expression.Method.DeclaringType == typeof(Queryable)) &&
(expression.Method.Name == "Select"))
SelectExpression = expression;
else
throw new NotSupportedException("Method not supported: " + expression.Method.Name);
}
Expand All @@ -69,8 +82,10 @@ private void CallIfPresent(Expression expression, Action<object> callback)

private object GetRightExpressionValue(BinaryExpression expression)
{
if ((expression.Left.NodeType != ExpressionType.MemberAccess) ||
(((MemberExpression)expression.Left).Member.Name != "Key"))
// since all things will have to translate to "key", and the only
// place where we know what "key" really is is within the view,
// just check that it's a member access expression
if ((expression.Left.NodeType != ExpressionType.MemberAccess))
return null;

object key;
Expand All @@ -79,7 +94,7 @@ private object GetRightExpressionValue(BinaryExpression expression)
else if (expression.Right.NodeType == ExpressionType.MemberAccess)
key = GetMemberValue((MemberExpression)expression.Right);
else
throw new NotSupportedException("Expression type not supported for publisher: " + expression.Right.NodeType.ToString());
throw new NotSupportedException("Expression type not supported: " + expression.Right.NodeType.ToString());

return key;
}
Expand Down
4 changes: 3 additions & 1 deletion src/Linq/TypeSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// The code in this file comes from Matt Warren's series of blog posts on how to build a LINQ provider
// adapted from sample code at http://linqinaction.net/files/folders/linqinaction/entry1952.aspx

// The code in this file comes from Matt Warren's series of blog posts on how to build a LINQ provider
// http://blogs.msdn.com/mattwar/archive/2007/08/09/linq-building-an-iqueryable-provider-part-i.aspx

using System;
Expand Down

0 comments on commit 6fd7e61

Please sign in to comment.