Today we are releasing another bug fix and maintenance release of the Couchbase .NET SDK: 2.0.2. This is a follow up release to 2.0 and 2.0.1 and most notably provides support for N1QL DP4 which is now merged with the master branch.
What’s in this release?
Earlier this month, we released a developer preview of the SDK for supporting N1QL DP4. N1QL DP4 brings a whole new REST API and support for new features such as prepared statements, named and positional parameters and timeouts. In this release we stabilized the interfaces and programming model for working with N1QL in the SDK. In addition, we updated from Common.Logging 2.0 to 3.0 (thus getting rid of the dependency on the deprecated NuGet packages), we added support for configurable connection timeouts, and added improved the ClusterHelper and IBucket interfaces.
N1QL DP4 Support
The SDK now supports the following N1QL REST API features: named and positional parameters, statements and prepared statements, timeout, errors, metrics, readonly, signature and client context id.
Named Parameters and Positional Parameters
When you create a statement and want to pass parameters along with it, for example as part of the WHERE clause, you can now do so by one of two means: named parameters and optional parameters. Named parameters take the form of a variable prefixed with the dollar sign “$”. When you create the query and execute it, they are sent to the server and substituted with the value that you provide for variable. Here is an example of to do this with the SDK:
1 2 3 4 5 6 7 8 9 10 |
<span style="color: blue"> var</span> query = <span style="color: #a31515">"SELECT * FROM `beer-sample` WHERE type=$type LIMIT $limit"</span>;    <span style="color: blue">var</span> queryRequest = <span style="color: blue">new</span> <span style="color: #2b91af">QueryRequest</span>(query)          .AddNamedParameter(<span style="color: #a31515">"$type"</span>, <span style="color: #a31515">"beer"</span>)          .AddNamedParameter(<span style="color: #a31515">"$limit"</span>, 10);    <span style="color: blue">var</span> results = bucket.Query<<span style="color: blue">dynamic</span>>(queryRequest);    <span style="color: blue">foreach</span> (<span style="color: blue">var</span> result <span style="color: blue">in</span> results.Rows)    {         <span style="color: #2b91af">Console</span>.WriteLine(result);    } |
In contrast to Named parameters, Positional parameters associate a value with an ordinal variable in the statement. The order in which add the parameters defines the order of the variable/value substitution by the server when the query is executed. For example:
1 2 3 4 5 6 7 8 9 10 |
<span style="color: blue"> var</span> query = <span style="color: #a31515">"SELECT * FROM `beer-sample` WHERE type=$1 LIMIT $2"</span>;     <span style="color: blue">var</span> queryRequest = <span style="color: blue">new</span> <span style="color: #2b91af">QueryRequest</span>(query)         .AddPositionalParameter(<span style="color: #a31515">"beer"</span>)         .AddPositionalParameter(10);     <span style="color: blue">var</span> results = bucket.Query<<span style="color: blue">dynamic</span>>(queryRequest);     <span style="color: blue">foreach</span> (<span style="color: blue">var</span> result <span style="color: blue">in</span> results.Rows)     {          <span style="color: #2b91af">Console</span>.WriteLine(result);     } |
Note that this is the exact same query as the first one, we have just used Named Parameters as opposed to Positional Parameters.
Statements and Prepared Statements
The SDK splits the N1QL into two distinct things: the language itself and the API for executing N1QL queries. A N1QL “query” is called a statement and is a string of N1QL code that will be sent to the server for execution and the results returned back to the client and mapped into either the dynamic Type or as a POCO. Here is an example of executing a N1QL statement using a RequestQuery object:
1 2 3 4 5 6 7 8 |
<span style="color: blue"> var</span> queryRequest = <span style="color: blue">new</span> <span style="color: #2b91af">QueryRequest</span>()          .Statement(<span style="color: #a31515">"SELECT * FROM `beer-sample`"</span>);      <span style="color: blue">var</span> results = bucket.Query<<span style="color: blue">dynamic</span>>(queryRequest);      <span style="color: blue">foreach</span> (<span style="color: blue">var</span> result <span style="color: blue">in</span> results.Rows)      {          <span style="color: #2b91af">Console</span>.WriteLine(result);      } |
When the server receives a N1QL statement, it will compile a query plan for the statement. This takes time and resources, so it would be better if the results of this step were cached and reused over and over again. That is exactly what prepared statements are for. In order to use prepared statements with the SDK, you simply have to set the prepared property on the QueryRequest object:
1 2 3 4 5 6 7 8 9 |
<span style="color: blue"> var</span> queryRequest = <span style="color: blue">new</span> <span style="color: #2b91af">QueryRequest</span>()          .Statement(<span style="color: #a31515">"SELECT * FROM `beer-sample`"</span>)          .Prepared(<span style="color: blue">true</span>);      <span style="color: blue">var</span> results = bucket.Query<<span style="color: blue">dynamic</span>>(queryRequest);      <span style="color: blue">foreach</span> (<span style="color: blue">var</span> result <span style="color: blue">in</span> results.Rows)      {          <span style="color: #2b91af">Console</span>.WriteLine(result);      } |
Note that the client will execute the statement, get the prepared statement and subsequent calls reuse the cached prepared statement. If you run the code above with a timer, you’ll notice that the first call takes considerable longer than subsequent calls: that is the effect of creating the prepared statement and caching it.
Errors
The new API also has a new “errors” API which is mapped to the QueryResult object. If an error occurs while processing a request, the N1QL engine will return a sub-document called errors which is then mapped to a class in the SDK called conveniently, “Error”:
The fields are defined as follows:
Field | Meaning | Optional |
Code | A unique number for the error or warning. | False |
Message | A detailed description of the error or warning. | False |
Name | A unique name which has a 1:1 matching to the code and identifies the condition that caused error or warning. | True |
Severity | One of the following N1QL severity levels: Severe, Warn, Info or Error | True |
Temp | Indicates that the error or warning is temporary and if false, retrying the request will result in the same outcome. | True |
Metrics
Metrics provide, well metrics, regarding the statement that was executed by the server. Note that there is a server side metrics parameter that is true by default, so if the request doesn’t include a metrics parameter, they will still be sent. To disable metrics, you set the following field on your QueryRequest object before executing it:
1 2 3 4 5 6 7 8 9 |
<span style="color: blue"> var</span> queryRequest = <span style="color: blue">new</span> <span style="color: #2b91af">QueryRequest</span>()                 .Statement(<span style="color: #a31515">"SELECT * FROM `beer-sample`"</span>)                 .Metrics(<span style="color: blue">false</span>);             <span style="color: blue">var</span> results = bucket.Query<<span style="color: blue">dynamic</span>>(queryRequest);             <span style="color: blue">foreach</span> (<span style="color: blue">var</span> result <span style="color: blue">in</span> results.Rows)             {                 <span style="color: #2b91af">Console</span>.WriteLine(result);             } |
In general, for most production code you do not want metrics to be returned, however the reverse is true for development environments.
Readonly, Signatures and Client Context Id
I’ll describe these briefly. Readonly is a server-wide setting that can be also be overridden by the client. In general, this will be true for all GET requests, however there is a server wide setting that will supersede this.  Signature is an optional header for the results schema. Client Context ID is an optional 64 character string that is provided by the client and echoed back by the server. Its main purpose is for tracing and debugging requests.
What has changed?
Common.Logging 3.0
The 2.0.0 and 2.0.1 releases depending upon version 2.0 of the Common.Logging Library. In 2.0.2 we updated this dependency to Common.logging 3.0. You can read about the reason for updating here. The good news is that you will no longer see this confusing message when you try to add a dependency to Log4Net or NLog in Nuget:
The bad news is that you will likely need to make changes to your App.Config or Web.Config to support the change in Assembly naming that he Common.Logging project is now using. For example, to add a dependency on Log4Net you will need to use the “new” naming convention in your App.Config or Web.Config from this:
To something like this:
Of course the Assembly name you would supply there would be whichever version you had taken a dependency on.
Changes to ICluster and ClusterHelper
A new method was added to the ICluster interface, thus the Cluster object itself: IsOpen(string bucketName). The purpose of this method is, you guessed it, to determine if a Cluster object is holding a reference to an IBucket implementation. This is useful for testing and perhaps within your application when you want to manage your IBucket references.
The ClusterHelper also got some love: it’s now a “multi-ton” for buckets and well as a singleton for Cluster instances! Why did we do this? To simply make it easier to manage Cluster and Bucket references in multithreaded environments, such as ASP.NET applications. The API now looks like this:
The big additions are GetBucket(bucketName) and RemoveBucket(bucketName) If you open a bucket using GetBucket(bucketName), the reference will be cached and subsequent requests for the bucket by the same name will result in the cached bucket object being returned.
RemoveBucket(bucketName) will remove the bucket instance from the Cluster object. Note that one and only one reference will be stored per bucket name, but that you can open as many different (i.e. default, beer-sample, etc) buckets as you wish. In addition to GetBucket(bucketName) and RemoveBucket(bucketName), another method, Count, was added which gives a count of the open buckets being maintained by the ClusterHelper. Finally, when call Close() on the ClusterHelper, all of the buckets will be closed.
Changes to ViewRow
The ViewRow.Key property has been changed from being a System.object Type to a dynamic Type. This one done so that the property will reflect the JSON structure of the key and not leak the underlying JSON serialization API’s types.
Bug Fixes
The list of Jira tickets that are included in this release can be found in the release notes here.