diff --git a/Divan.sln b/Divan.sln index 25cde46..904a469 100644 --- a/Divan.sln +++ b/Divan.sln @@ -1,26 +1,30 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Divan", "src\Divan.csproj", "{37AC0B66-5340-4B81-BC62-3EE80233A011}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trivial", "samples\Trivial\Trivial.csproj", "{CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {37AC0B66-5340-4B81-BC62-3EE80233A011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {37AC0B66-5340-4B81-BC62-3EE80233A011}.Debug|Any CPU.Build.0 = Debug|Any CPU - {37AC0B66-5340-4B81-BC62-3EE80233A011}.Release|Any CPU.ActiveCfg = Release|Any CPU - {37AC0B66-5340-4B81-BC62-3EE80233A011}.Release|Any CPU.Build.0 = Release|Any CPU - {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Divan", "src\Divan.csproj", "{37AC0B66-5340-4B81-BC62-3EE80233A011}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trivial", "samples\Trivial\Trivial.csproj", "{CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {37AC0B66-5340-4B81-BC62-3EE80233A011}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37AC0B66-5340-4B81-BC62-3EE80233A011}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37AC0B66-5340-4B81-BC62-3EE80233A011}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37AC0B66-5340-4B81-BC62-3EE80233A011}.Release|Any CPU.Build.0 = Release|Any CPU + {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDCC7924-F227-46DC-B2E6-2BBE06B84AF2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + version = 0.1 + StartupItem = src\Divan.csproj + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/Trivial/Makefile b/samples/Trivial/Makefile new file mode 100644 index 0000000..0b2f3fe --- /dev/null +++ b/samples/Trivial/Makefile @@ -0,0 +1,154 @@ + +EXTRA_DIST = Makefile + +# Warning: This is an automatically generated file, do not edit! + +srcdir=. +top_srcdir=../.. + +include $(top_srcdir)/config.make + +ifeq ($(CONFIG),DEBUG) +ASSEMBLY_COMPILER_COMMAND = gmcs +ASSEMBLY_COMPILER_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize- -debug "-define:DEBUG;TRACE" "-main:Trivial.Program" +ASSEMBLY = bin/Debug/Trivial.exe +ASSEMBLY_MDB = $(ASSEMBLY).mdb +COMPILE_TARGET = exe +PROJECT_REFERENCES = \ + ../../src/bin/Debug/Divan.dll +BUILD_DIR = bin/Debug/ + +NEWTONSOFT_JSON_DLL_SOURCE=../../../../../Divan/bin/Debug/Newtonsoft.Json.dll +NEWTONSOFT_JSON_DLL_MDB_SOURCE=../../../../../Divan/bin/Debug/Newtonsoft.Json.dll.mdb +DIVAN_DLL_MDB_SOURCE=../../src/bin/Debug/Divan.dll.mdb +DIVAN_DLL_MDB=$(BUILD_DIR)/Divan.dll.mdb +DIVAN_DLL_SOURCE=../../src/bin/Debug/Divan.dll +TRIVIAL_EXE_MDB_SOURCE=bin/Debug/Trivial.exe.mdb +TRIVIAL_EXE_MDB=$(BUILD_DIR)/Trivial.exe.mdb + +endif + +ifeq ($(CONFIG),RELEASE) +ASSEMBLY_COMPILER_COMMAND = gmcs +ASSEMBLY_COMPILER_FLAGS = -noconfig -codepage:utf8 -warn:4 -optimize+ "-define:TRACE" "-main:Trivial.Program" +ASSEMBLY = bin/Release/Trivial.exe +ASSEMBLY_MDB = +COMPILE_TARGET = exe +PROJECT_REFERENCES = \ + ../../src/bin/Release/Divan.dll +BUILD_DIR = bin/Release/ + +NEWTONSOFT_JSON_DLL_SOURCE=../../../../../Divan/bin/Debug/Newtonsoft.Json.dll +NEWTONSOFT_JSON_DLL_MDB_SOURCE=../../../../../Divan/bin/Debug/Newtonsoft.Json.dll.mdb +DIVAN_DLL_MDB= +DIVAN_DLL_SOURCE=../../src/bin/Release/Divan.dll +TRIVIAL_EXE_MDB= + +endif + +AL=al2 +SATELLITE_ASSEMBLY_NAME=$(notdir $(basename $(ASSEMBLY))).resources.dll + +PROGRAMFILES = \ + $(NEWTONSOFT_JSON_DLL) \ + $(NEWTONSOFT_JSON_DLL_MDB) \ + $(DIVAN_DLL_MDB) \ + $(DIVAN_DLL) \ + $(TRIVIAL_EXE_MDB) + +BINARIES = \ + $(TRIVIAL) + + +RESGEN=resgen2 + +TRIVIAL = $(BUILD_DIR)/trivial +NEWTONSOFT_JSON_DLL = $(BUILD_DIR)/Newtonsoft.Json.dll +NEWTONSOFT_JSON_DLL_MDB = $(BUILD_DIR)/Newtonsoft.Json.dll.mdb +DIVAN_DLL = $(BUILD_DIR)/Divan.dll + +FILES = \ + Program.cs \ + Properties/AssemblyInfo.cs + +DATA_FILES = + +RESOURCES = + +EXTRAS = \ + trivial.in + +REFERENCES = \ + System \ + System.Core \ + System.Xml.Linq \ + System.Data.DataSetExtensions \ + System.Data \ + System.Xml + +DLL_REFERENCES = \ + ../../../../../Divan/bin/Debug/Newtonsoft.Json.dll + +CLEANFILES = $(PROGRAMFILES) $(BINARIES) + +#Targets +all: $(ASSEMBLY) $(PROGRAMFILES) $(BINARIES) $(top_srcdir)/config.make + +include $(top_srcdir)/Makefile.include +#include $(srcdir)/custom-hooks.make + + + +$(eval $(call emit-deploy-wrapper,TRIVIAL,trivial,x)) +$(eval $(call emit-deploy-target,NEWTONSOFT_JSON_DLL)) +$(eval $(call emit-deploy-target,NEWTONSOFT_JSON_DLL_MDB)) +$(eval $(call emit-deploy-target,DIVAN_DLL_MDB)) +$(eval $(call emit-deploy-target,DIVAN_DLL)) + + +$(eval $(call emit_resgen_targets)) +$(build_xamlg_list): %.xaml.g.cs: %.xaml + xamlg '$<' + + +$(ASSEMBLY) $(ASSEMBLY_MDB): $(build_sources) $(build_resources) $(build_datafiles) $(DLL_REFERENCES) $(PROJECT_REFERENCES) $(build_xamlg_list) $(build_satellite_assembly_list) + make pre-all-local-hook prefix=$(prefix) + mkdir -p $(shell dirname $(ASSEMBLY)) + make $(CONFIG)_BeforeBuild + $(ASSEMBLY_COMPILER_COMMAND) $(ASSEMBLY_COMPILER_FLAGS) -out:$(ASSEMBLY) -target:$(COMPILE_TARGET) $(build_sources_embed) $(build_resources_embed) $(build_references_ref) + make $(CONFIG)_AfterBuild + make post-all-local-hook prefix=$(prefix) + +install-local: $(ASSEMBLY) $(ASSEMBLY_MDB) + make pre-install-local-hook prefix=$(prefix) + make install-satellite-assemblies prefix=$(prefix) + mkdir -p '$(DESTDIR)$(libdir)/$(PACKAGE)' + $(call cp,$(ASSEMBLY),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call cp,$(ASSEMBLY_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + mkdir -p '$(DESTDIR)$(bindir)' + $(call cp,$(TRIVIAL),$(DESTDIR)$(bindir)) + $(call cp,$(NEWTONSOFT_JSON_DLL),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call cp,$(NEWTONSOFT_JSON_DLL_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call cp,$(DIVAN_DLL_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call cp,$(DIVAN_DLL),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call cp,$(TRIVIAL_EXE_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + make post-install-local-hook prefix=$(prefix) + +uninstall-local: $(ASSEMBLY) $(ASSEMBLY_MDB) + make pre-uninstall-local-hook prefix=$(prefix) + make uninstall-satellite-assemblies prefix=$(prefix) + $(call rm,$(ASSEMBLY),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(ASSEMBLY_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(TRIVIAL),$(DESTDIR)$(bindir)) + $(call rm,$(NEWTONSOFT_JSON_DLL),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(NEWTONSOFT_JSON_DLL_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(DIVAN_DLL_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(DIVAN_DLL),$(DESTDIR)$(libdir)/$(PACKAGE)) + $(call rm,$(TRIVIAL_EXE_MDB),$(DESTDIR)$(libdir)/$(PACKAGE)) + make post-uninstall-local-hook prefix=$(prefix) + +install: install-local +uninstall: uninstall-local +clean: clean-local + +include $(top_srcdir)/rules.make diff --git a/samples/Trivial/Program.cs b/samples/Trivial/Program.cs index 6837cc3..9f610ed 100644 --- a/samples/Trivial/Program.cs +++ b/samples/Trivial/Program.cs @@ -95,9 +95,8 @@ class Program 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(linqProvider); + var tempView = db.NewTempView("test", "test", "if (doc.docType && doc.docType == 'car') emit(doc.Hps, doc);"); + var linqCars = tempView.LinqQuery(); var fastCars = from c in linqCars where c.HorsePowers >= 175 select c;//.Make + " " + c.Model; foreach (var fastCar in fastCars) @@ -113,7 +112,7 @@ class Program Console.WriteLine(twoCar); // cleanup for later - db.DeleteDocument(tempQuery.Doc); + db.DeleteDocument(tempView.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 @@ -138,15 +137,6 @@ class Program 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; - } - /// /// The simplest way to deal with domain objects is to subclass CouchDocument /// and inherit members Id and Rev. You will need to implement WriteJson/ReadJson. diff --git a/samples/Trivial/Trivial.csproj b/samples/Trivial/Trivial.csproj index 54b823d..f8ed2b5 100644 --- a/samples/Trivial/Trivial.csproj +++ b/samples/Trivial/Trivial.csproj @@ -1,5 +1,5 @@ - - + + Debug AnyCPU @@ -31,7 +31,7 @@ 4 - + False ..\..\lib\Newtonsoft.Json.dll diff --git a/samples/Trivial/trivial b/samples/Trivial/trivial new file mode 100644 index 0000000..a342f68 --- /dev/null +++ b/samples/Trivial/trivial @@ -0,0 +1,3 @@ +#!/bin/sh + +exec mono "/usr/local/lib/divan/Trivial.exe" "$@" diff --git a/samples/Trivial/trivial.in b/samples/Trivial/trivial.in new file mode 100644 index 0000000..c41a472 --- /dev/null +++ b/samples/Trivial/trivial.in @@ -0,0 +1,3 @@ +#!/bin/sh + +exec mono "@expanded_libdir@/@PACKAGE@/Trivial.exe" "$@" diff --git a/src/CouchBulkDeleteDocuments.cs b/src/CouchBulkDeleteDocuments.cs index a6d6ae3..efe7d4f 100644 --- a/src/CouchBulkDeleteDocuments.cs +++ b/src/CouchBulkDeleteDocuments.cs @@ -34,4 +34,4 @@ public override void ReadJson(JObject obj) throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/src/CouchDatabase.cs b/src/CouchDatabase.cs index a60bce8..c2f9ff1 100644 --- a/src/CouchDatabase.cs +++ b/src/CouchDatabase.cs @@ -55,6 +55,17 @@ public CouchDesignDocument NewDesignDocument(string aName) return newDoc; } + /// + /// Only to be used when developing. + /// + public CouchViewDefinition NewTempView(string designDoc, string viewName, string mapText) + { + var doc = NewDesignDocument(designDoc); + var view = doc.AddView(viewName, "function (doc) {" + mapText + "}"); + doc.Synch(); + return view; + } + /// /// Currently the logic is that the code is always the master. /// And we also do not remove design documents in the database that @@ -539,6 +550,11 @@ public CouchQuery Query(CouchViewDefinition view) return new CouchQuery(view); } + public CouchLuceneQuery Query(CouchLuceneViewDefinition view) + { + return new CouchLuceneQuery(view); + } + public CouchQuery QueryAllDocuments() { return Query(null, "_all_docs"); @@ -651,8 +667,7 @@ public bool HasDocument(string documentId) { try { - Request(documentId).Get().Send(); -// NOTE: Should use HEAD Request(documentId).Head().Send(); + Request(documentId).Head().Send(); return true; } catch (WebException) @@ -665,8 +680,7 @@ public bool HasAttachment(string documentId) { try { - Request(documentId + "/attachment").Get().Send(); -// NOTE: Should use HEAD Request(documentId + "/attachment").Head().Send(); + Request(documentId + "/attachment").Head().Send(); return true; } catch (WebException) diff --git a/src/CouchDesignDocument.cs b/src/CouchDesignDocument.cs index 656399d..eb530a5 100644 --- a/src/CouchDesignDocument.cs +++ b/src/CouchDesignDocument.cs @@ -7,11 +7,15 @@ namespace Divan { /// - /// A named design document in CouchDB. Holds CouchViewDefinitions. + /// A named design document in CouchDB. Holds CouchViewDefinitions and CouchLuceneViewDefinitions (if you use Couchdb-Lucene). /// public class CouchDesignDocument : CouchDocument, IEquatable { public IList Definitions = new List(); + + // This List is only used if you also have Couchdb-Lucene installed + public IList LuceneDefinitions = new List(); + public string Language = "javascript"; public CouchDatabase Owner; @@ -67,6 +71,70 @@ public void RemoveView(CouchViewDefinition view) Definitions.Remove(view); } + /// + /// Add Lucene fulltext view. + /// + /// Name of view + /// Index function + /// + public CouchLuceneViewDefinition AddLuceneView(string name, string index) + { + var def = new CouchLuceneViewDefinition(name, index, this); + LuceneDefinitions.Add(def); + return def; + } + + /// + /// Add a Lucene view with a predefined index function that will index EVERYTHING. + /// + /// + public CouchLuceneViewDefinition AddLuceneViewIndexEverything(string name) + { + return AddLuceneView(name, + @"function(doc) { + var ret = new Document(); + + function idx(obj) { + for (var key in obj) { + switch (typeof obj[key]) { + case 'object': + idx(obj[key]); + break; + case 'function': + break; + default: + ret.add(obj[key]); + break; + } + } + }; + + idx(doc); + + if (doc._attachments) { + for (var i in doc._attachments) { + ret.attachment(""attachment"", i); + } + }}"); + } + + // All these three methods duplicated for Lucene views, perhaps we should hold them all in one List? + public void RemoveLuceneViewNamed(string viewName) + { + RemoveLuceneView(FindLuceneView(viewName)); + } + + private CouchLuceneViewDefinition FindLuceneView(string name) + { + return LuceneDefinitions.Where(x => x.Name == name).First(); + } + + public void RemoveLuceneView(CouchLuceneViewDefinition view) + { + view.Doc = null; + LuceneDefinitions.Remove(view); + } + /// /// If this design document is missing in the database, /// or if it is different - then we save it overwriting the one in the db. @@ -98,6 +166,18 @@ public override void WriteJson(JsonWriter writer) definition.WriteJson(writer); } writer.WriteEndObject(); + + // If we have Lucene definitions we write them too + if (LuceneDefinitions.Count > 0) + { + writer.WritePropertyName("fulltext"); + writer.WriteStartObject(); + foreach (var definition in LuceneDefinitions) + { + definition.WriteJson(writer); + } + writer.WriteEndObject(); + } } public override void ReadJson(JObject obj) @@ -113,6 +193,18 @@ public override void ReadJson(JObject obj) v.ReadJson((JObject)views[property.Name]); Definitions.Add(v); } + + var fulltext = (JObject)obj["fulltext"]; + // If we have Lucene definitions we read them too + if (fulltext != null) + { + foreach (var property in fulltext.Properties()) + { + var v = new CouchLuceneViewDefinition(property.Name, this); + v.ReadJson((JObject) views[property.Name]); + LuceneDefinitions.Add(v); + } + } } public bool Equals(CouchDesignDocument other) diff --git a/src/CouchRequest.cs b/src/CouchRequest.cs index 9ea4fe1..045c5c7 100644 --- a/src/CouchRequest.cs +++ b/src/CouchRequest.cs @@ -86,7 +86,7 @@ public CouchRequest QueryOptions(ICollection> optio // HEAD requests seem to be problematic under Mono. public CouchRequest Head() { - method = "HEAD"; + method = "GET"; // Should be "HEAD" return this; } @@ -176,7 +176,6 @@ public CouchRequest Check(string message) private HttpWebRequest GetRequest() { Uri requestUri = new UriBuilder("http", server.Host, server.Port, ((db != null) ? db.Name + "/" : "") + path, query).Uri; - var request = WebRequest.Create(requestUri) as HttpWebRequest; if (request == null) { @@ -188,11 +187,11 @@ private HttpWebRequest GetRequest() if (mimeType != null) { request.ContentType = mimeType; - } - + } + if (postData != null) { - byte[] bytes = Encoding.UTF8.GetBytes(postData); + byte[] bytes = Encoding.UTF8.GetBytes(postData); request.ContentLength = bytes.Length; using (Stream ps = request.GetRequestStream()) { @@ -212,30 +211,31 @@ public JObject Parse() public T Parse() where T : JToken { + //var timer = new Stopwatch(); + //timer.Start(); using (WebResponse response = GetResponse()) { - PickETag(response); - if (etagToCheck != null) - { - if (IsETagValid()) - { - return null; - } - } - if (method == "HEAD") { - return null; - } using (Stream stream = response.GetResponseStream()) { using (var reader = new StreamReader(stream)) { using (var textReader = new JsonTextReader(reader)) { + PickETag(response); + if (etagToCheck != null) + { + if (IsETagValid()) + { + return null; + } + } result = JToken.ReadFrom(textReader); // We know it is a top level JSON JObject. } } } } + //timer.Stop(); + //Trace.WriteLine("Time for Couch HTTP & JSON PARSE: " + timer.ElapsedMilliseconds); return (T) result; } @@ -255,20 +255,16 @@ public string String() { using (WebResponse response = GetResponse()) { - PickETag(response); - if (etagToCheck != null) + using (var reader = new StreamReader(response.GetResponseStream())) { + PickETag(response); + if (etagToCheck != null) + { if (IsETagValid()) { return null; } - } - if (method == "HEAD") { - return null; - } - using (var reader = new StreamReader(response.GetResponseStream())) - { - + } return reader.ReadToEnd(); } } @@ -276,7 +272,7 @@ public string String() private WebResponse GetResponse() { - return GetRequest().GetResponse(); + return GetRequest().GetResponse(); } public CouchRequest Send() @@ -293,4 +289,4 @@ public bool IsETagValid() return etagToCheck == etag; } } -} +} \ No newline at end of file diff --git a/src/CouchServer.cs b/src/CouchServer.cs index 26d044a..fa028fb 100644 --- a/src/CouchServer.cs +++ b/src/CouchServer.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.IO; using System.Net; -using System; using System.Text.RegularExpressions; using Newtonsoft.Json; @@ -16,7 +15,7 @@ namespace Divan /// public class CouchServer { - private const string DefaultHost = "192.168.9.205"; + private const string DefaultHost = "localhost"; private const int DefaultPort = 5984; private readonly JsonSerializer serializer = new JsonSerializer(); @@ -58,7 +57,7 @@ public CouchRequest Request() /// public void Debug(string message) { - Console.WriteLine(message); + Trace.WriteLine(message); } public bool HasDatabase(string name) @@ -66,10 +65,9 @@ public bool HasDatabase(string name) //return GetDatabaseNames().Contains(name); // This is too slow when we have thousands of dbs!!! try { - // NOTE: HEAD requests seem to be problematic in Mono... - //Request().Path(name).Head().Send(); - Request().Path(name).Get().Send(); - return true; + // HEAD requests seem to be problematic in Mono... + Request().Path(name).Head().Send(); + return true; } catch (WebException) { diff --git a/src/CouchViewDefinition.cs b/src/CouchViewDefinition.cs index 1f26cef..91a5f8c 100644 --- a/src/CouchViewDefinition.cs +++ b/src/CouchViewDefinition.cs @@ -2,25 +2,22 @@ using System.Collections.Generic; using Newtonsoft.Json; using Newtonsoft.Json.Linq; - +using Divan.Linq; + namespace Divan { /// /// A definition of a CouchDB view with a name, a map and a reduce function and a reference to the /// owning CouchDesignDocument. /// - public class CouchViewDefinition : IEquatable + public class CouchViewDefinition : CouchViewDefinitionBase, IEquatable { /// - /// Constructor used to create "on the fly" definitions, like for example for "_all_docs". + /// Basic constructor used in ReadJson() etc. /// /// View name used in URI. /// A design doc, can also be created on the fly. - public CouchViewDefinition(string name, CouchDesignDocument doc) - { - Doc = doc; - Name = name; - } + public CouchViewDefinition(string name, CouchDesignDocument doc) : base(name, doc) {} /// /// Constructor used for permanent views, see CouchDesignDocument. @@ -29,29 +26,30 @@ public CouchViewDefinition(string name, CouchDesignDocument doc) /// Map function. /// Optional reduce function. /// Parent document. - public CouchViewDefinition(string name, string map, string reduce, CouchDesignDocument doc) + public CouchViewDefinition(string name, string map, string reduce, CouchDesignDocument doc): base(name, doc) { - Doc = doc; - Name = name; Map = map; Reduce = reduce; } - public CouchDesignDocument Doc { get; set; } - public string Name { get; set; } public string Map { get; set; } public string Reduce { get; set; } - public CouchRequest Request() + public void Touch() { - return Doc.Owner.Request(Path()); + Query().Limit(0).GetResult(); } - public CouchDatabase Db() + public CouchQuery Query() { - return Doc.Owner; + return Doc.Owner.Query(this); } + public CouchLinqQuery LinqQuery() { + var linqProvider = new CouchQueryProvider(Db(), this); + return new CouchLinqQuery(linqProvider); + } + public void WriteJson(JsonWriter writer) { writer.WritePropertyName(Name); @@ -75,25 +73,6 @@ public void ReadJson(JObject obj) } } - public CouchQuery Query() - { - return Doc.Owner.Query(this); - } - - public void Touch() - { - Query().Limit(0).GetResult(); - } - - public string Path() - { - if (Doc.Id == "_design/") - { - return Name; - } - return Doc.Id + "/_view/" + Name; - } - /// /// Utility methods to make queries shorter. /// diff --git a/src/CouchViewDefinitionBase.cs b/src/CouchViewDefinitionBase.cs new file mode 100644 index 0000000..8b198c9 --- /dev/null +++ b/src/CouchViewDefinitionBase.cs @@ -0,0 +1,33 @@ +namespace Divan +{ + public abstract class CouchViewDefinitionBase + { + public CouchDesignDocument Doc { get; set; } + public string Name { get; set; } + + protected CouchViewDefinitionBase(string name, CouchDesignDocument doc) + { + Doc = doc; + Name = name; + } + + public CouchDatabase Db() + { + return Doc.Owner; + } + + public CouchRequest Request() + { + return Db().Request(Path()); + } + + public virtual string Path() + { + if (Doc.Id == "_design/") + { + return Name; + } + return Doc.Id + "/_view/" + Name; + } + } +} \ No newline at end of file diff --git a/src/CustomDictionary.xml b/src/CustomDictionary.xml index 9b13063..7f5bd8c 100644 --- a/src/CustomDictionary.xml +++ b/src/CustomDictionary.xml @@ -1,468 +1,468 @@ - - - - - - cb - ch - csc - elem - gt - idx - img - lg - multi - num - ps - pw - scp - si - sig - tk - tw - val - - - - json - accessor - accessors - acos - aes - aptca - arg - args - asin - asm - aspx - async - atan - baml - bcl - bindable - bitrate - blittable - blog - bool - bootstrapper - bootstrappers - browsable - cacheability - callee - callees - canonicalize - cdecl - cdo - chtml - cim - cloneable - clr - clr's - cls - clsid - clsids - cmd - cmdlet - cmdlets - comparand - concat - config - contravariant - cookieless - cos - crm - css - cyclomatic - debuggable - decommission - deformatter - delegator - dequeue - dereferenced - des - deserialization - deserialize - deserialized - deserializing - dhcp - discardable - dll - dns - documentable - dsig - dtd - em - email - emails - emf - encodable - endian - enqueue - enum - enums - expando - finalizer - finalizers - fixup - fixups - formattable - func - guid - guids - hashtable - hashtables - hashtable's - hdc - hijri - href - iis - il - ime - initializer - initializers - int - interop - intrinsics - ipv - iterator - iterators - jit - ldap - linq - localhost - loopback - loopbacks - mapper - mappers - marshaler - marshalers - mdi - mergable - misc - miscased - monitorable - oks - mscoree - mscorlib - msh - multiline - multipanel - multipanels - multiview - multiviews - mutator - mutators - mutex - mutexes - ndpsec - nls - nop - ntfs - ntlm - nullable - obj - odbc - overridable - pageable - parameterless - pdb - persistable - playlist - pragma - prepend - prog - ptr - queryable - ras - rect - rects - recurse - refactor - reg - regex - remoted - remoting - representable - res - resolver - resolvers - rethrow - rethrows - rijndael - rpc - rtc - rva - sdl - searchspace - searchspaces - seekable - seq - serializable - serializer - serializers - smtp - specifier - specifiers - spline - sql - ssl - sta - stickies - struct - structs - subaddress - subaddresses - subclass - subclasses - subdirectories - subdirectory - subexpression - subexpressions - subitem - subitems - subkey - subkeys - submenu - submenus - subpath - subpaths - subsegment - subsegments - subtree - subtrees - tcp - templated - thunk - thunks - tlb - tuple - tuples - udp - udt - unboxing - uncategorize - unindent - uninitialize - uninitialized - uninstantiated - unmaintainable - unmarshal - unregister - unregistering - unregisters - unregistration - unrepresentable - unterminated - untrusted - uri - uris - url - urls - utc - utf - validator - vsa - weblog - wiki - wcf - wmf - wmi - wml - wpf - wql - wsdl - xaml - xhtml - xmlns - xor - xrml - xsd - xsi - xsl - xslt - - - - complus - cancelled - indices - login - logout - signon - signoff - writeable - cant - arent - dont - doesnt - didnt - couldnt - wouldnt - shouldnt - wont - havent - hasnt - hadnt - isnt - wasnt - werent - flag - flags - - - - datastore - datastores - dataset - datasets - textbox - textboxes - codepage - codepages - checkbox - checkboxes - pushbutton - pushbuttons - dropdown - dropdowns - toolbar - toolbars - scrollbar - scrollbars - bitflag - bitflags - filename - filenames - fileserver - fileservers - username - usernames - hostname - hostnames - fieldname - fieldnames - pathname - pathnames - whitespace - whitespaces - logon - logons - logoff - logoffs - signin - signins - signout - signouts - frontend - frontends - backend - backends - sitemap - sitemaps - datatype - datatypes - designtime - designtimes - readonly - truetype - netbios - autodetect - autodetects - autoscroll - autoscrolls - autocomplete - autocompletes - autosave - autosaves - javascript - jscript - voiceview - appletalk - mapinfo - newline - newlines - qword - qwords - keyset - keysets - - - - onset - inset - byname - setout - countertype - editor - longtime - drawstring - hookup - cleanup - breakout - setline - maybe - nods - classis - gettable - inform - beset - settable - standalone - threadlike - infield - infields - meantime - mackey - jscript - ipv - tooltip - tooltips - indispose - - - - - Pi - Na - NESW - NWSE - Json - - - + + + + + + cb + ch + csc + elem + gt + idx + img + lg + multi + num + ps + pw + scp + si + sig + tk + tw + val + + + + json + accessor + accessors + acos + aes + aptca + arg + args + asin + asm + aspx + async + atan + baml + bcl + bindable + bitrate + blittable + blog + bool + bootstrapper + bootstrappers + browsable + cacheability + callee + callees + canonicalize + cdecl + cdo + chtml + cim + cloneable + clr + clr's + cls + clsid + clsids + cmd + cmdlet + cmdlets + comparand + concat + config + contravariant + cookieless + cos + crm + css + cyclomatic + debuggable + decommission + deformatter + delegator + dequeue + dereferenced + des + deserialization + deserialize + deserialized + deserializing + dhcp + discardable + dll + dns + documentable + dsig + dtd + em + email + emails + emf + encodable + endian + enqueue + enum + enums + expando + finalizer + finalizers + fixup + fixups + formattable + func + guid + guids + hashtable + hashtables + hashtable's + hdc + hijri + href + iis + il + ime + initializer + initializers + int + interop + intrinsics + ipv + iterator + iterators + jit + ldap + linq + localhost + loopback + loopbacks + mapper + mappers + marshaler + marshalers + mdi + mergable + misc + miscased + monitorable + oks + mscoree + mscorlib + msh + multiline + multipanel + multipanels + multiview + multiviews + mutator + mutators + mutex + mutexes + ndpsec + nls + nop + ntfs + ntlm + nullable + obj + odbc + overridable + pageable + parameterless + pdb + persistable + playlist + pragma + prepend + prog + ptr + queryable + ras + rect + rects + recurse + refactor + reg + regex + remoted + remoting + representable + res + resolver + resolvers + rethrow + rethrows + rijndael + rpc + rtc + rva + sdl + searchspace + searchspaces + seekable + seq + serializable + serializer + serializers + smtp + specifier + specifiers + spline + sql + ssl + sta + stickies + struct + structs + subaddress + subaddresses + subclass + subclasses + subdirectories + subdirectory + subexpression + subexpressions + subitem + subitems + subkey + subkeys + submenu + submenus + subpath + subpaths + subsegment + subsegments + subtree + subtrees + tcp + templated + thunk + thunks + tlb + tuple + tuples + udp + udt + unboxing + uncategorize + unindent + uninitialize + uninitialized + uninstantiated + unmaintainable + unmarshal + unregister + unregistering + unregisters + unregistration + unrepresentable + unterminated + untrusted + uri + uris + url + urls + utc + utf + validator + vsa + weblog + wiki + wcf + wmf + wmi + wml + wpf + wql + wsdl + xaml + xhtml + xmlns + xor + xrml + xsd + xsi + xsl + xslt + + + + complus + cancelled + indices + login + logout + signon + signoff + writeable + cant + arent + dont + doesnt + didnt + couldnt + wouldnt + shouldnt + wont + havent + hasnt + hadnt + isnt + wasnt + werent + flag + flags + + + + datastore + datastores + dataset + datasets + textbox + textboxes + codepage + codepages + checkbox + checkboxes + pushbutton + pushbuttons + dropdown + dropdowns + toolbar + toolbars + scrollbar + scrollbars + bitflag + bitflags + filename + filenames + fileserver + fileservers + username + usernames + hostname + hostnames + fieldname + fieldnames + pathname + pathnames + whitespace + whitespaces + logon + logons + logoff + logoffs + signin + signins + signout + signouts + frontend + frontends + backend + backends + sitemap + sitemaps + datatype + datatypes + designtime + designtimes + readonly + truetype + netbios + autodetect + autodetects + autoscroll + autoscrolls + autocomplete + autocompletes + autosave + autosaves + javascript + jscript + voiceview + appletalk + mapinfo + newline + newlines + qword + qwords + keyset + keysets + + + + onset + inset + byname + setout + countertype + editor + longtime + drawstring + hookup + cleanup + breakout + setline + maybe + nods + classis + gettable + inform + beset + settable + standalone + threadlike + infield + infields + meantime + mackey + jscript + ipv + tooltip + tooltips + indispose + + + + + Pi + Na + NESW + NWSE + Json + + + diff --git a/src/Divan.FxCop b/src/Divan.FxCop index a3875f0..1453357 100644 --- a/src/Divan.FxCop +++ b/src/Divan.FxCop @@ -1,307 +1,307 @@ - - - - True - c:\sandbox\monitor2\thirdparty\tools\fxcop\Xml\FxCopReport.xsl - - - - - - True - True - True - 10 - 1 - - False - - False - 120 - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'Divan.dll' - - - - - - - - - - - - - 'CouchDatabase.GetAllDocuments()' - - - - - - - - - 'CouchDatabase.GetDocument<T>(string)' - 'T' - - - - - - - - - 'CouchDatabase.GetResultWithOptions<T>(string, string, Dictionary<string, string>)' - 'T' - - - - - - - - - 'CouchDatabase.GetView<T>(string, string)' - 'T' - - - - - - - - - 'CouchDatabase.GetView<T>(string, string, string)' - 'T' - - - - - - - - - 'CouchDatabase.GetView<T>(string, string, string, string)' - 'T' - - - - - - - - - - - - - 'CouchGenericViewResult.Document<T>()' - 'T' - - - - - - - - - 'CouchGenericViewResult.Documents<T>()' - 'T' - - - - - - - - - 'CouchGenericViewResult.RetrieveDocument<T>(string)' - 'T' - - - - - - - - - 'CouchGenericViewResult.RetrieveDocuments<T>(string)' - 'T' - - - - - - - - - 'CouchGenericViewResult.ValueDocument<T>()' - 'T' - - - - - - - - - 'CouchGenericViewResult.ValueDocuments<T>()' - 'T' - - - - - - - - - - - - - 'CouchQuery.GetResult<T>()' - 'T' - - - - - - - - - - - - - 'CouchServer.GetDatabaseNames()' - - - - - - - - - 'url' - 'CouchServer.Request(CouchDatabase, string, string)' - - - - - - - - - 'url' - 'CouchServer.Request(CouchDatabase, string, string, string, string)' - - - - - - - - - 'url' - 'CouchServer.RequestStream(CouchDatabase, string, string, string, string)' - - - - - - - - - - - - - SetUp - 'CouchTest.SetUp()' - Setup - - - - - - - - - - - - - - TearDown - 'CouchTest.TearDown()' - Teardown - - - - - - - - - - - - - - - - - - - - - NUnit - NUnit - - - - - Sign {0} with a strong name key. - - - The compound word '{0}' in member name {1} exists as a discrete term. If your usage is intended to be single word, case it as '{2}' or strip the first token entirely if it represents any sort of Hungarian notation. - - - Consider a design where {0} doesn't require explicit type parameter {1} in any call to it. - - - Change the type of parameter {0} of method {1} from string to System.Uri, or provide an overload of {1}, that allows {0} to be passed as a System.Uri object. - - - Change {0} to a property if appropriate. - - - - + + + + True + c:\sandbox\monitor2\thirdparty\tools\fxcop\Xml\FxCopReport.xsl + + + + + + True + True + True + 10 + 1 + + False + + False + 120 + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'Divan.dll' + + + + + + + + + + + + + 'CouchDatabase.GetAllDocuments()' + + + + + + + + + 'CouchDatabase.GetDocument<T>(string)' + 'T' + + + + + + + + + 'CouchDatabase.GetResultWithOptions<T>(string, string, Dictionary<string, string>)' + 'T' + + + + + + + + + 'CouchDatabase.GetView<T>(string, string)' + 'T' + + + + + + + + + 'CouchDatabase.GetView<T>(string, string, string)' + 'T' + + + + + + + + + 'CouchDatabase.GetView<T>(string, string, string, string)' + 'T' + + + + + + + + + + + + + 'CouchGenericViewResult.Document<T>()' + 'T' + + + + + + + + + 'CouchGenericViewResult.Documents<T>()' + 'T' + + + + + + + + + 'CouchGenericViewResult.RetrieveDocument<T>(string)' + 'T' + + + + + + + + + 'CouchGenericViewResult.RetrieveDocuments<T>(string)' + 'T' + + + + + + + + + 'CouchGenericViewResult.ValueDocument<T>()' + 'T' + + + + + + + + + 'CouchGenericViewResult.ValueDocuments<T>()' + 'T' + + + + + + + + + + + + + 'CouchQuery.GetResult<T>()' + 'T' + + + + + + + + + + + + + 'CouchServer.GetDatabaseNames()' + + + + + + + + + 'url' + 'CouchServer.Request(CouchDatabase, string, string)' + + + + + + + + + 'url' + 'CouchServer.Request(CouchDatabase, string, string, string, string)' + + + + + + + + + 'url' + 'CouchServer.RequestStream(CouchDatabase, string, string, string, string)' + + + + + + + + + + + + + SetUp + 'CouchTest.SetUp()' + Setup + + + + + + + + + + + + + + TearDown + 'CouchTest.TearDown()' + Teardown + + + + + + + + + + + + + + + + + + + + + NUnit + NUnit + + + + + Sign {0} with a strong name key. + + + The compound word '{0}' in member name {1} exists as a discrete term. If your usage is intended to be single word, case it as '{2}' or strip the first token entirely if it represents any sort of Hungarian notation. + + + Consider a design where {0} doesn't require explicit type parameter {1} in any call to it. + + + Change the type of parameter {0} of method {1} from string to System.Uri, or provide an overload of {1}, that allows {0} to be passed as a System.Uri object. + + + Change {0} to a property if appropriate. + + + + diff --git a/src/Divan.csproj b/src/Divan.csproj index b02117a..62c0326 100644 --- a/src/Divan.csproj +++ b/src/Divan.csproj @@ -1,84 +1,92 @@ - - - - Debug - AnyCPU - 9.0.30729 - 2.0 - {37AC0B66-5340-4B81-BC62-3EE80233A011} - Library - Properties - Divan - Divan - v3.5 - 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - ..\lib\Newtonsoft.Json.dll - - - False - ..\lib\nunit.framework.dll - - - - 3.5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {37AC0B66-5340-4B81-BC62-3EE80233A011} + Library + Properties + Divan + Divan + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\lib\Newtonsoft.Json.dll + + + False + ..\lib\nunit.framework.dll + + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Lucene/CouchLuceneQuery.cs b/src/Lucene/CouchLuceneQuery.cs new file mode 100644 index 0000000..c3c5193 --- /dev/null +++ b/src/Lucene/CouchLuceneQuery.cs @@ -0,0 +1,233 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Newtonsoft.Json.Linq; + +namespace Divan +{ + /// + /// A Lucene query with all its options. This class overlaps with CouchQuery but I could not find + /// a nice way to use inheritance and still keep a fluent style interface without going into generics HELL. + /// + /// You can perform all types of queries using Lucene's default query syntax: + /// http://lucene.apache.org/java/2_4_0/queryparsersyntax.html + /// + /// The _body field is searched by default which will include the extracted text from all attachments. + /// + public class CouchLuceneQuery + { + public readonly CouchLuceneViewDefinition View; + + // Special options + public bool checkETagUsingHead; + public Dictionary Options = new Dictionary(); + + public string postData; + public CouchLuceneViewResult Result; + + public CouchLuceneQuery(CouchLuceneViewDefinition view) + { + View = view; + } + + public void ClearOptions() + { + Options = new Dictionary(); + } + + /// + /// The analyzer used to convert the query string into a query object. + /// + public CouchLuceneQuery Analyzer(string value) + { + Options["analyzer"] = value; + return this; + } + + + /// + /// Specify a JSONP callback wrapper. The full JSON result will be prepended + /// with this parameter and also placed with parentheses. + /// + public CouchLuceneQuery Callback(string value) + { + Options["callback"] = value; + return this; + } + + /// + /// Setting this to true disables response caching (the query is executed every time) + /// and indents the JSON response for readability. + /// + public CouchLuceneQuery Debug() + { + Options["debug"] = "true"; + return this; + } + + + /// + /// Usually couchdb-lucene determines the Content-Type of its response based on the + /// presence of the Accept header. If Accept contains "application/json", you get + /// "application/json" in the response, otherwise you get "text/plain;charset=utf8". + /// Some tools, like JSONView for FireFox, do not send the Accept header but do render + /// "application/json" responses if received. Setting force_json=true forces all response + /// to "application/json" regardless of the Accept header. + /// + public CouchLuceneQuery ForceJson() + { + Options["force_json"] = "true"; + return this; + } + + public CouchLuceneQuery IncludeDocuments() + { + Options["include_docs"] = "true"; + return this; + } + + public CouchLuceneQuery Limit(int value) + { + Options["limit"] = value.ToString(); + return this; + } + + /// + /// The query to run (e.g, subject:hello). If not specified, the default field is searched. + /// + public CouchLuceneQuery Q(string value) + { + Options["q"] = value; + return this; + } + + /// + /// (EXPERT) if true, returns a json response with a rewritten query and term frequencies. + /// This allows correct distributed scoring when combining the results from multiple nodes. + /// + public CouchLuceneQuery Rewrite() + { + Options["rewrite"] = "true"; + return this; + } + + public CouchLuceneQuery Skip(int value) + { + Options["skip"] = value.ToString(); + return this; + } + + /// + /// The fields to sort on. Prefix with / for ascending order + /// and \ for descending order (ascending is the default if not specified). + /// + public CouchLuceneQuery Sort(params object[] value) + { + if (value != null) + { + Options["sort"] = JToken.FromObject(value).ToString(); + } + return this; + } + + /// + /// If you set the stale option to ok, couchdb-lucene may not perform any + /// refreshing on the index. Searches may be faster as Lucene caches important + /// data (especially for sorting). A query without stale=ok will use the latest + /// data committed to the index. + /// + public CouchLuceneQuery Stale() + { + Options["stale"] = "ok"; + return this; + } + + /// + /// Tell this query to do a HEAD request first to see + /// if ETag has changed and only then do the full request. + /// This is only interesting if you are reusing this query object. + /// + public CouchLuceneQuery CheckETagUsingHead() + { + checkETagUsingHead = true; + return this; + } + + public CouchLuceneViewResult GetResult() + { + try + { + return GetResult(); + } + catch (WebException e) + { + throw CouchException.Create("Query failed", e); + } + } + + public bool IsCachedAndValid() + { + // If we do not have a result it is not cached + if (Result == null) + { + return false; + } + CouchRequest req = View.Request().QueryOptions(Options); + req.Etag(Result.etag); + return req.Head().Send().IsETagValid(); + } + + public string String() + { + return Request().String(); + } + + + public CouchRequest Request() + { + var req = View.Request().QueryOptions(Options); + if (postData != null) + { + req.Data(postData).Post(); + } + return req; + } + + public T GetResult() where T : CouchLuceneViewResult, new() + { + if (Options["q"] == null) + { + throw CouchException.Create("Lucene query failed, you need to specify Q()."); + } + var req = Request(); + + if (Result == null) + { + Result = new T(); + } + else + { + // Tell the request what we already have + req.Etag(Result.etag); + if (checkETagUsingHead) + { + // Make a HEAD request to avoid transfer of data + if (req.Head().Send().IsETagValid()) + { + return (T) Result; + } + // Set back to GET before proceeding below + req.Get(); + } + } + + JObject json = req.Parse(); + if (json != null) // ETag did not match, view has changed + { + Result.Result(json, View); + Result.etag = req.Etag(); + } + return (T) Result; + } + } +} \ No newline at end of file diff --git a/src/Lucene/CouchLuceneTest.cs b/src/Lucene/CouchLuceneTest.cs new file mode 100644 index 0000000..bee76fe --- /dev/null +++ b/src/Lucene/CouchLuceneTest.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Newtonsoft.Json.Linq; +using NUnit.Framework; +using NUnit.Framework.SyntaxHelpers; + +namespace Divan +{ + /// + /// Unit tests for the Lucene part in Divan. Operates in a separate CouchDB database called divan_lucene_unit_tests. + /// Requires a working Couchdb-Lucene installation according to Couchdb-Lucene's documentation. + /// + [TestFixture] + public class CouchLuceneTest + { + #region Setup/Teardown + + [SetUp] + public void SetUp() + { + server = new CouchServer(); + db = server.GetNewDatabase(DbName); + } + + [TearDown] + public void TearDown() + { + //db.Delete(); + } + + #endregion + + private CouchServer server; + private CouchDatabase db; + private const string DbName = "divan_lucene_unit_tests"; + + [Test] + public void ShouldHandleTrivialQuery() + { + var design = db.NewDesignDocument("test"); + var view = design.AddLuceneView("simple", @"function (doc) { var ret = new Document(); ret.add(doc.text); return ret;}"); + db.SynchDesignDocuments(); + + db.CreateDocument("{\"text\": \"one two three four\"}"); + + var result = view.Query().Q("one").GetResult(); +// var docs = result.GetDocuments(); +// var doc = docs.First(); + Assert.That(result.Count(), Is.EqualTo(1)); +// Assert.That(doc.Obj["text"].Value(), Is.EqualTo("one two three four")); + } + +/* [Test] + public void ShouldHandleEmptyIndex() + { + var design = db.NewDesignDocument("test"); + var view = design.AddLuceneView("noindex", + @"function(doc) { + return null; + } + "); + db.WriteDocument(design); + + CouchJsonDocument doc1 = db.CreateDocument("{\"CPU\": \"Intel\"}"); + db.CreateDocument("{\"CPU\": \"AMD\"}"); + db.CreateDocument("{\"CPU\": \"Via\"}"); + db.CreateDocument("{\"CPU\": \"Sparq\"}"); + + var query = view.Query().Q("Via").GetResult(); + + + } +*/ + + } +} \ No newline at end of file diff --git a/src/Lucene/CouchLuceneViewDefinition.cs b/src/Lucene/CouchLuceneViewDefinition.cs new file mode 100644 index 0000000..7a86415 --- /dev/null +++ b/src/Lucene/CouchLuceneViewDefinition.cs @@ -0,0 +1,170 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Divan +{ + /// + /// A definition of a CouchDB Lucene view with a name, an index function and some options, see below. + /// + public class CouchLuceneViewDefinition : CouchViewDefinitionBase, IEquatable + { + /// + /// Basic constructor used in ReadJson() etc. + /// + /// View name used in URI. + /// A design doc, can also be created on the fly. + public CouchLuceneViewDefinition(string name, CouchDesignDocument doc) : base(name, doc) { } + + + /// + /// Constructor used for permanent views, see CouchDesignDocument. + /// + /// View name. + /// Index function. + /// Parent document. + public CouchLuceneViewDefinition(string name, string index, CouchDesignDocument doc) : base(name, doc) + { + Index = index; + } + + /// + /// Copied from http://github.com/rnewson/couchdb-lucene/tree/v0.4/README.md + /// + /// You must supply a index function in order to enable couchdb-lucene as, by default, + /// nothing will be indexed. To suppress a document from the index, return null. + /// It's more typical to return a single Document object which contains everything + /// you'd like to query and retrieve. You may also return an array of Document + /// objects if you wish. + /// + /// You may add any number of index views in any number of design documents. + /// All searches will be constrained to documents emitted by the index functions. + /// + /// Example function: "function(doc) { var ret=new Document(); ret.add(doc.subject); return ret }" + /// + public string Index { get; set; } + + /// + /// Copied from http://github.com/rnewson/couchdb-lucene/tree/v0.4/README.md + /// + /// Lucene has numerous ways of converting free-form text into tokens, these classes are called Analyzer's. + /// By default, the StandardAnalyzer is used which lower-cases all text, drops common English words + /// ("the", "and", and so on), among other things. This processing might not always suit you, so you can + /// choose from several others by setting the "analyzer" field to one of the following values; + /// + /// brazilian, chinese, cjk, czech, dutch, english, french, german, keyword, porter, + /// russian, simple, standard, thai + /// + public string Analyzer { get; set; } + + /// + /// Copied from http://github.com/rnewson/couchdb-lucene/tree/v0.4/README.md + /// + /// The defaults for numerous indexing options can be overridden here. A full list of options follows. + /// + /// Name: Description: Available options: Default: + /// + /// field the field name to index under user-defined default + /// store whether the data is stored. + /// Value will be returned in the search result. yes, no no + /// + /// index whether (and how) the data is indexed analyzed, + /// analyzed_no_norms, + /// no, not_analyzed, + /// not_analyzed_no_norms analyzed + /// + /// In Divan we flattened this so that this object has all these three settings instead of a Dictionary: + /// + public string Field { get; set; } + public bool Store { get; set; } + public string IndexHow { get; set; } + + + public void WriteJson(JsonWriter writer) + { + writer.WritePropertyName(Name); + writer.WriteStartObject(); + if (Analyzer != null) + { + writer.WritePropertyName("analyzer"); + writer.WriteValue(Analyzer); + } + // Bah, if this gets out of hand we should use a Dictionary instead :) + if (Field != null || Store || IndexHow != null) + { + writer.WritePropertyName("defaults"); + writer.WriteStartObject(); + if (Field != null) + { + writer.WritePropertyName("field"); + writer.WriteValue(Field); + } + if (Store) + { + writer.WritePropertyName("store"); + writer.WriteValue("yes"); + } + if (IndexHow != null) + { + writer.WritePropertyName("index"); + writer.WriteValue(IndexHow); + } + writer.WriteEndObject(); + } + writer.WritePropertyName("index"); + writer.WriteValue(Index); + writer.WriteEndObject(); + } + + public void ReadJson(JObject obj) + { + if (obj["analyzer"] != null) + { + Analyzer = obj["reduce"].Value(); + } + if (obj["defaults"] != null) + { + var defaults = obj["defaults"]; + if (defaults["field"] != null) + { + Field = defaults["field"].Value(); + } + if (defaults["store"] != null) + { + Store = (defaults["store"].Value()).Equals("yes"); + } + if (defaults["index"] != null) + { + IndexHow = defaults["index"].Value(); + } + } + Index = obj["index"].Value(); + } + + public CouchLuceneQuery Query() + { + return Db().Query(this); + } + + public override string Path() + { + // Todo: "_fti" is hardcoded here. + // Also, for some odd reason Couchdb-Lucene does not want the "_design/"-prefix in query URLs. + + return "_fti/" + WithoutDesignPart(Doc.Id) + "/" + Name; + } + + static private string WithoutDesignPart(string name) { + if (name.StartsWith("_design/")) { + return name.Substring(8); + } + return name; + } + + public bool Equals(CouchLuceneViewDefinition other) + { + return Name.Equals(other.Name) && Index.Equals(other.Index) && Analyzer.Equals(other.Analyzer) && + Store.Equals(other.Store) && Field.Equals(other.Field) && IndexHow.Equals(other.IndexHow); + } + } +} \ No newline at end of file diff --git a/src/Lucene/CouchLuceneViewResult.cs b/src/Lucene/CouchLuceneViewResult.cs new file mode 100644 index 0000000..10239ec --- /dev/null +++ b/src/Lucene/CouchLuceneViewResult.cs @@ -0,0 +1,95 @@ +using Newtonsoft.Json.Linq; +using System.Collections.Generic; + +namespace Divan +{ + /// + /// This is a view result from a CouchLuceneQuery. The result is returned as JSON + /// from CouchDB and parsed into a JObject by Newtonsoft.Json. Basically + /// the result is a JSON array of document key/score pairs. + /// + public class CouchLuceneViewResult + { + private CouchLuceneViewDefinition view; + public string etag; + public JObject result; + + public void Result(JObject obj, CouchLuceneViewDefinition aView) + { + result = obj; + view = aView; + } + + public string ETag() + { + return result["etag"].Value(); + } + + /// + /// The total number of hits for the query. Not all may be returned due to Limit(), see Count(). + /// + public int TotalCount() + { + return result["total_rows"].Value(); + } + + /// + /// Maximum number of hits that CouchDB was allowed to return. + /// + public int Limit() + { + return result["limit"].Value(); + } + + /// + /// Number of milliseconds spent fetching CouchDB documents. + /// + public int FetchDuration() + { + return result["fetch_duration"].Value(); + } + + /// + /// Number of milliseconds spent executing the actual search in Lucene. + /// + public int SearchDuration() + { + return result["search_duration"].Value(); + } + + /// + /// Number of skipped results in this query. Renamed to Offset() to match API in CouchDB more closely. + /// + public int Offset() + { + return result["skip"].Value(); + } + + public JEnumerable Rows() + { + return result["rows"].Children(); + } + + /// + /// Perform a bulk retrieval of the documents that was returned by this + /// query. Note that this may be a subset of TotalCount(). + /// + public virtual IList GetDocuments() where T : ICouchDocument, new() + { + var ids = new List(); + foreach (JObject row in Rows()) + { + ids.Add(row["id"].Value()); + } + return view.Db().GetDocuments(ids); + } + + /// + /// Returns number of documents returned in this result. See TotalCount() for the total number of hits. + /// + public int Count() + { + return result["rows"].Value().Count; + } + } +} \ No newline at end of file