-
Notifications
You must be signed in to change notification settings - Fork 50
/
CouchDatabase.cs
325 lines (306 loc) · 14 KB
/
CouchDatabase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Web;
using LoveSeat.Interfaces;
using LoveSeat.Support;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace LoveSeat
{
public class CouchDatabase : CouchBase, IDocumentDatabase
{
private readonly string databaseBaseUri;
private string defaultDesignDoc = null;
internal CouchDatabase(string baseUri, string databaseName, string username, string password)
: base(username, password)
{
this.baseUri = baseUri;
this.databaseBaseUri = baseUri + databaseName;
}
/// <summary>
/// Creates a document using the json provided.
/// No validation or smarts attempted here by design for simplicities sake
/// </summary>
/// <param name="id">Id of Document</param>
/// <param name="jsonForDocument"></param>
/// <returns></returns>
public Document CreateDocument(string id, string jsonForDocument)
{
var jobj = JObject.Parse(jsonForDocument);
if (jobj.Value<object>("_rev") == null)
jobj.Remove("_rev");
var resp = GetRequest(databaseBaseUri + "/" + id)
.Put().Form()
.Data(jobj.ToString(Formatting.None))
.GetResponse();
return
resp.GetCouchDocument();
}
public Document CreateDocument(Document doc)
{
return CreateDocument(doc.Id, doc.ToString());
}
/// <summary>
/// Creates a document when you intend for Couch to generate the id for you.
/// </summary>
/// <param name="jsonForDocument">Json for creating the document</param>
/// <returns></returns>
public Document CreateDocument(string jsonForDocument)
{
var json = JObject.Parse(jsonForDocument);
var jobj =
GetRequest(databaseBaseUri + "/").Post().Json().Data(jsonForDocument).GetResponse().GetJObject();
json["_id"] = jobj["id"];
json["_rev"] = jobj["rev"];
return new Document(json);
}
public JObject DeleteDocument(string id, string rev)
{
return GetRequest(databaseBaseUri + "/" + id + "?rev=" + rev).Delete().Form().GetResponse().GetJObject();
}
/// <summary>
/// Returns null if document is not found
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Document GetDocument(string id)
{
var resp = GetRequest(databaseBaseUri + "/" + id).Get().Json().GetResponse();
if (resp.StatusCode==HttpStatusCode.NotFound) return null;
return resp.GetCouchDocument();
}
public T GetDocument<T>(Guid id , IObjectSerializer<T> objectSerializer)
{
return GetDocument(id.ToString(), objectSerializer);
}
public T GetDocument<T>(Guid id)
{
return GetDocument<T>(id.ToString());
}
public T GetDocument<T>(string id)
{
return GetDocument(id, new ObjectSerializer<T>());
}
public T GetDocument<T>(string id, IObjectSerializer<T> objectSerializer)
{
var resp = GetRequest(databaseBaseUri + "/" + id).Get().Json().GetResponse();
if (resp.StatusCode == HttpStatusCode.NotFound) return default(T);
return objectSerializer.Deserialize(resp.GetResponseString());
}
/// <summary>
/// Adds an attachment to a document. If revision is not specified then the most recent will be fetched and used. Warning: if you need document update conflicts to occur please use the method that specifies the revision
/// </summary>
/// <param name="id">id of the couch Document</param>
/// <param name="attachment">byte[] of of the attachment. Use File.ReadAllBytes()</param>
/// <param name="contentType">Content Type must be specifed</param>
public JObject AddAttachment(string id, byte[] attachment, string filename, string contentType)
{
var doc = GetDocument(id);
return AddAttachment(id, doc.Rev, attachment, filename, contentType);
}
/// <summary>
/// Adds an attachment to the documnet. Rev must be specified on this signature. If you want to attach no matter what then use the method without the rev param
/// </summary>
/// <param name="id">id of the couch Document</param>
/// <param name="rev">revision _rev of the Couch Document</param>
/// <param name="attachment">byte[] of of the attachment. Use File.ReadAllBytes()</param>
/// <param name="filename">filename of the attachment</param>
/// <param name="contentType">Content Type must be specifed</param>
/// <returns></returns>
public JObject AddAttachment(string id, string rev, byte[] attachment, string filename, string contentType)
{
return
GetRequest(databaseBaseUri + "/" + id + "/" + filename + "?rev=" + rev).Put().ContentType(contentType).Data(attachment).GetResponse().GetJObject();
}
public Stream GetAttachmentStream(Document doc, string attachmentName)
{
return GetAttachmentStream(doc.Id, doc.Rev, attachmentName);
}
public Stream GetAttachmentStream(string docId, string rev, string attachmentName)
{
return GetRequest(databaseBaseUri + "/" + docId + "/" + HttpUtility.UrlEncode(attachmentName)).Get().GetResponse().GetResponseStream();
}
public Stream GetAttachmentStream(string docId, string attachmentName)
{
var doc = GetDocument(docId);
if (doc == null) return null;
return GetAttachmentStream(docId, doc.Rev, attachmentName);
}
public JObject DeleteAttachment(string id, string rev, string attachmentName)
{
return GetRequest(databaseBaseUri + "/" + id + "/" + attachmentName + "?rev=" + rev).Json().Delete().GetResponse().GetJObject();
}
public JObject DeleteAttachment(string id, string attachmentName)
{
var doc = GetDocument(id);
return DeleteAttachment(doc.Id, doc.Rev, attachmentName);
}
public Document SaveDocument(Document document)
{
if (document.Rev == null)
return CreateDocument(document);
var resp = GetRequest(databaseBaseUri + "/" + document.Id + "?rev=" + document.Rev).Put().Form().Data(document).GetResponse();
var jobj = resp.GetJObject();
//TODO: Change this so it simply alters the revision on the document past in so that there isn't an additional request.
return GetDocument(document.Id);
}
/// <summary>
/// Gets the results of a view with no view parameters. Use the overload to pass parameters
/// </summary>
/// <param name="viewName">The name of the view</param>
/// <param name="designDoc">The design doc on which the view resides</param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName, string designDoc)
{
return View<T>(viewName, null, designDoc);
}
/// <summary>
/// Gets the results of the view using the defaultDesignDoc and no view parameters. Use the overloads to specify options.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewName"></param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName)
{
ThrowDesignDocException();
return View<T>(viewName, defaultDesignDoc);
}
public ViewResult View(string viewName)
{
ThrowDesignDocException();
return View(viewName);
}
public string Show (string showName, string docId)
{
ThrowDesignDocException();
return Show(showName, docId, defaultDesignDoc);
}
private void ThrowDesignDocException()
{
if (string.IsNullOrEmpty(defaultDesignDoc))
throw new Exception("You must use SetDefaultDesignDoc prior to using this signature. Otherwise explicitly specify the design doc in the other overloads.");
}
public string Show(string showName, string docId, string designDoc)
{
//TODO: add in Etag support for Shows
var uri = databaseBaseUri + "/_design/" + designDoc + "/_show/" + showName + "/" + docId;
var req = GetRequest(uri);
return req.GetResponse().GetResponseString();
}
public IListResult List(string listName, string viewName, ViewOptions options, string designDoc)
{
var uri = databaseBaseUri + "/_design/" + designDoc + "/_list/" + listName + "/" + viewName + options.ToString();
var req = GetRequest(uri);
return new ListResult(req.GetRequest(), req.GetResponse());
}
public IListResult List(string listName, string viewName, ViewOptions options)
{
ThrowDesignDocException();
return List(listName, viewName, options, defaultDesignDoc);
}
public void SetDefaultDesignDoc(string designDoc)
{
this.defaultDesignDoc = designDoc;
}
/// <summary>
/// Gets the results of the view using any and all parameters
/// </summary>
/// <param name="viewName">The name of the view</param>
/// <param name="options">Options such as startkey etc.</param>
/// <param name="designDoc">The design doc on which the view resides</param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName, ViewOptions options, string designDoc)
{
var uri = databaseBaseUri + "/_design/" + designDoc + "/_view/" + viewName;
return ProcessGenericResults<T>(uri, options, new ObjectSerializer<T>());
}
/// <summary>
/// Allows you to specify options and uses the defaultDesignDoc Specified.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewName"></param>
/// <param name="options"></param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName, ViewOptions options)
{
ThrowDesignDocException();
return View<T>(viewName, options, defaultDesignDoc);
}
/// <summary>
/// Allows you to override the objectSerializer and use the Default Design Doc settings.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewName"></param>
/// <param name="options"></param>
/// <param name="objectSerializer"></param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName, ViewOptions options, IObjectSerializer<T> objectSerializer)
{
ThrowDesignDocException();
return View<T>(viewName, options, defaultDesignDoc, objectSerializer);
}
public ViewResult View(string viewName, ViewOptions options, string designDoc)
{
var uri = databaseBaseUri + "/_design/" + designDoc + "/_view/" + viewName;
return ProcessResults(uri, options);
}
public ViewResult View(string viewName, ViewOptions options)
{
ThrowDesignDocException();
var uri = databaseBaseUri + "/_design/" + this.defaultDesignDoc + "/_view/" + viewName;
return ProcessResults(uri, options);
}
/// <summary>
/// Don't use this overload unless you intend to override the default ObjectSerialization behavior.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewName"></param>
/// <param name="options"></param>
/// <param name="designDoc"></param>
/// <param name="objectSerializer">Only needed unless you'd like to override the default behavior of the serializer</param>
/// <returns></returns>
public ViewResult<T> View<T>(string viewName, ViewOptions options, string designDoc, IObjectSerializer<T> objectSerializer)
{
var uri = databaseBaseUri + "/_design/" + designDoc + "/_view/" + viewName;
return ProcessGenericResults<T>(uri, options, objectSerializer);
}
private ViewResult<T> ProcessGenericResults<T>(string uri, ViewOptions options, IObjectSerializer<T> objectSerializer)
{
CouchRequest req = GetRequest(options, uri);
var resp = req.GetResponse();
if (resp.StatusCode == HttpStatusCode.BadRequest)
{
throw new CouchException(req.GetRequest(), resp, resp.GetResponseString() + "\n" + req.GetRequest().RequestUri );
}
return new ViewResult<T>(resp, req.GetRequest(), objectSerializer);
}
private ViewResult ProcessResults(string uri, ViewOptions options)
{
CouchRequest req = GetRequest(options, uri);
var resp = req.GetResponse();
return new ViewResult(resp, req.GetRequest());
}
private CouchRequest GetRequest(ViewOptions options, string uri)
{
if (options != null)
uri += options.ToString();
return GetRequest(uri, options == null ? null : options.Etag).Get().Json();
}
/// <summary>
/// Gets all the documents in the database using the _all_docs uri
/// </summary>
/// <returns></returns>
public ViewResult GetAllDocuments()
{
var uri = databaseBaseUri + "/_all_docs";
return ProcessResults(uri, null);
}
public ViewResult GetAllDocuments(ViewOptions options)
{
var uri = databaseBaseUri + "/_all_docs";
return ProcessResults(uri, options);
}
}
}