Skip to content

Commit

Permalink
NCBC-165: Provide support for view does not exist scenarios
Browse files Browse the repository at this point in the history
Currently, when a view doesn't exist an InvalidOperationException
bubbles up to the caller.  New approach provides CheckExists method
of view, which may be called prior to iterating over view or new
typed exceptions which provide details of the error.

Change-Id: Iedec5543e3eb096815fcbd77a764675d8ed5fded
Reviewed-on: http://review.couchbase.org/24068
Reviewed-by: Matt Ingenthron <matt@couchbase.com>
Reviewed-by: Saakshi Manocha <saakshi.manocha@globallogic.com>
Tested-by: Saakshi Manocha <saakshi.manocha@globallogic.com>
  • Loading branch information
jzablocki authored and saakshimanocha committed Feb 1, 2013
1 parent 6eeab0d commit 66df655
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 3 deletions.
135 changes: 135 additions & 0 deletions src/Couchbase.Tests/CouchbaseClientViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using NUnit.Framework;
using Couchbase.Exceptions;

namespace Couchbase.Tests
{
Expand Down Expand Up @@ -54,5 +55,139 @@ public void When_Querying_View_With_Debug_False_Debug_Info_Dictionary_Is_Null()

Assert.That(view.DebugInfo, Is.Null);
}

/// <summary>
/// @test: Check design document for non-existent view, expect false from IView.ViewExists
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if false is returned
/// </summary>
[Test]
public void When_Checking_For_View_That_Does_Not_Exist_Check_Exists_Returns_False()
{
var view = _Client.GetView("cities", "by_postal_code");
var exists = view.CheckExists();
Assert.That(exists, Is.False);
}

/// <summary>
/// @test: Check design document for non-existent view, expect false from IView.ViewExists
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if false is returned
/// </summary>
[Test]
public void When_Checking_For_View_That_Exists_Check_Exists_Returns_True()
{
var view = _Client.GetView("cities", "by_name");
var exists = view.CheckExists();
Assert.That(exists, Is.True);
}

/// <summary>
/// @test: Check that ViewNotFoundException is returned when querying a bad view name
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if exception is thrown
/// </summary>
[Test]
[ExpectedException(typeof(ViewNotFoundException))]
public void When_Querying_A_View_That_Does_Not_Exist_In_A_Design_Doc_That_Does_Exist_View_Not_Found_Exception_Is_Thrown()
{
var view = _Client.GetView("cities", "by_postal_code");
view.Count();
}

/// <summary>
/// @test: Check that ViewNotFoundException is returned when querying a bad view name
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if exception is thrown and string contains not_found
/// </summary>
[Test]
public void When_Querying_A_View_That_Does_Not_Exist_In_A_Design_Doc_That_Does_Exist_Exception_Contains_Not_Found_Error()
{
try
{
var view = _Client.GetView("cities", "by_postal_code");
view.Count();
}
catch (ViewNotFoundException e)
{
Assert.That(e.Reason, Is.StringContaining("not_found"));
return;
}

Assert.Fail();
}

/// <summary>
/// @test: Check that ViewNotFoundException is returned when querying a bad view name
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if exception is thrown with proper messages
/// </summary>
[Test]
[ExpectedException(typeof(ViewNotFoundException))]
public void When_Querying_A_View_In_A_Design_Doc_That_Does_Not_Exist_View_Not_Found_Exception_Is_Thrown()
{
var view = _Client.GetView("states", "by_name");
view.Count();
}

/// <summary>
/// @test: Check that ViewNotFoundException is returned when querying a bad view name
/// @pre: Default configuration to initialize client in app.config and have missing view with design document cities and view name by_postal_code
/// @post: Test passes if exception is thrown
/// </summary>
[Test]
public void When_Querying_A_View_That_In_A_Design_Doc_That_Does_Not_Exist_Exception_Contains_Not_Found_Error()
{
try
{
var view = _Client.GetView("states", "by_postal_code");
view.Count();
}
catch (ViewNotFoundException e)
{
Assert.That(e.Error, Is.StringContaining("not_found"));
return;
}

Assert.Fail();
}

/// <summary>
/// @test: Check that ViewException is returned when querying a view with bad params
/// @pre: Default configuration to initialize client in app.config and have design document cities
/// and view name by_name without a reduce function
/// @post: ViewException is thrown
/// </summary>
[Test]
[ExpectedException(typeof(ViewException))]
public void When_Providing_Invalid_Parameters_To_An_Existing_View_A_View_Exception_Is_Thrown()
{
var view = _Client.GetView("cities", "by_name").Group(true);
view.Count();
}

/// <summary>
/// @test: Check that ViewException is returned with error and reason when querying a view with bad params
/// @pre: Default configuration to initialize client in app.config and have design document cities
/// and view name by_name without a reduce function
/// @post: ViewException is thrown with error and reason
/// </summary>
[Test]
public void When_Providing_Invalid_Parameters_To_An_Existing_View_A_View_Exception_Is_Thrown_And_Contains_Error_And_Reason()
{
try
{
var view = _Client.GetView("cities", "by_name").Group(true);
view.Count();
}
catch (ViewException e)
{
Assert.That(e.Reason, Is.Not.Null.Or.Empty);
Assert.That(e.Message, Is.Not.Null.Or.Empty);
return;
}

Assert.Fail();
}
}
}
2 changes: 2 additions & 0 deletions src/Couchbase/Couchbase.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@
<Compile Include="CouchbaseSpatialView.cs" />
<Compile Include="CouchbaseSpatialViewBase.cs" />
<Compile Include="CouchbaseViewHandler.cs" />
<Compile Include="Exceptions\ViewExceptions.cs" />
<Compile Include="Exceptions\ViewNotFoundException.cs" />
<Compile Include="Extensions\CouchbaseClientExtensions.cs" />
<Compile Include="GenericViewRowTransformer.cs" />
<Compile Include="Helpers\DocHelper.cs" />
Expand Down
5 changes: 5 additions & 0 deletions src/Couchbase/CouchbaseViewBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ public IPagedView<T> GetPagedView(int pageSize, string pagedViewIdProperty = nul
return new PagedView<T>(this, pageSize, pagedViewIdProperty, pagedViewKeyProperty);
}

public bool CheckExists()
{
return ViewHandler.CheckViewExists();
}

#endregion

#region IEnumerable<IViewRow> Members
Expand Down
53 changes: 52 additions & 1 deletion src/Couchbase/CouchbaseViewHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
using System.IO;
using Newtonsoft.Json.Linq;
using Couchbase.Helpers;
using Couchbase.Exceptions;

namespace Couchbase
{
internal class CouchbaseViewHandler
{
private const string ERROR_VIEW_NOT_FOUND = "not_found";

protected static readonly Enyim.Caching.ILog log = Enyim.Caching.LogManager.GetLogger(typeof(CouchbaseViewHandler));

public ICouchbaseClient Client { get; private set; }
Expand All @@ -36,7 +39,6 @@ internal CouchbaseViewHandler(ICouchbaseClient client, IHttpClientLocator client
public IEnumerator<T> TransformResults<T>(Func<JsonReader, T> rowTransformer, IDictionary<string, string> viewParams)
{
var response = GetResponse(viewParams);
Debug.Assert(response != null);

using (var sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8, true))
using (var jsonReader = new JsonTextReader(sr))
Expand All @@ -57,6 +59,26 @@ public IEnumerator<T> TransformResults<T>(Func<JsonReader, T> rowTransformer, ID
{
TotalRows = Convert.ToInt32(jsonReader.Value);
}
else if (jsonReader.Value as string == "error" && jsonReader.Read())
{
var error = jsonReader.Value as string;
var reason = "";
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName && jsonReader.Value as string == "reason" && jsonReader.Read())
{
reason = jsonReader.Value.ToString();
}
}

//When requesting a bad design document, the response will be a 404 with the error == "not_found"
//When requesting a bad view name and a valid design doc, response will be a 500 with a reason containing "not_found"
if (error == ERROR_VIEW_NOT_FOUND || reason.Contains(ERROR_VIEW_NOT_FOUND))
{
throw new ViewNotFoundException(DesignDocument, IndexName, error, reason);
}
throw new ViewException(DesignDocument, IndexName, error, reason);
}
else if (jsonReader.Value as string == "rows" && jsonReader.Read())
{
// position the reader on the first "rows" field which contains the actual resultset
Expand Down Expand Up @@ -91,6 +113,35 @@ public IEnumerator<T> TransformResults<T>(Func<JsonReader, T> rowTransformer, ID
}
}

public bool CheckViewExists()
{
var client = ClientLocator.Locate(DesignDocument);
var request = client.CreateRequest(this.DesignDocument + "/");
var response = request.GetResponse();

using (var sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8, true))
using (var jsonReader = new JsonTextReader(sr))
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName
&& jsonReader.Depth == 1
&& ((string)jsonReader.Value) == "views")
{
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName && (string)jsonReader.Value == IndexName)
{
return true;
}
}
}
}
}

return false;
}

protected static IEnumerable<string> FormatErrors(object[] list)
{
if (list == null || list.Length == 0)
Expand Down
45 changes: 45 additions & 0 deletions src/Couchbase/Exceptions/ViewExceptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Couchbase.Exceptions
{
/// <summary>
/// Standard exception class thrown on when view errors are encountered
/// </summary>
public class ViewException : Exception
{
public string Error { get; set; }

public string Reason { get; set; }

public ViewException(string designDoc, string viewName, string error, string reason) :
base(string.Format("Query failed for view {0} in design document {1}", viewName, designDoc))
{
Error = error;
Reason = reason;
}
}
}

#region [ License information ]
/* ************************************************************
*
* @author Couchbase <info@couchbase.com>
* @copyright 2013 Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ************************************************************/
#endregion
37 changes: 37 additions & 0 deletions src/Couchbase/Exceptions/ViewNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Couchbase.Exceptions
{
/// <summary>
/// Exception thrown when non-existent view is queried
/// </summary>
public class ViewNotFoundException : ViewException
{
public ViewNotFoundException(string designDoc, string viewName, string error, string reason) :
base(viewName, designDoc, error, reason) {}
}
}

#region [ License information ]
/* ************************************************************
*
* @author Couchbase <info@couchbase.com>
* @copyright 2013 Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ************************************************************/
#endregion
2 changes: 0 additions & 2 deletions src/Couchbase/RestSharpHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ public void ExecuteWith(RestClient client)
this.response = client.Execute(request);

if (response.ErrorException != null) throw response.ErrorException;
if (response.StatusCode != System.Net.HttpStatusCode.OK)
throw new InvalidOperationException(String.Format("Server returned {0}: {1}, {2}", response.StatusCode, response.StatusDescription, response.Content));
}

Stream IHttpResponse.GetResponseStream()
Expand Down

0 comments on commit 66df655

Please sign in to comment.