C#
Getting Started
Visual Studio Project
Create or open an existing Visual Studio project and install Couchbase Lite using the following method.
Nuget
-
Install either of the following packages from Nuget.
Couchbase Lite Community EditionInstall the
Couchbase.Lite
package.Couchbase Lite Enterprise EditionInstall the
Couchbase.Lite.Enterprise
package.Nuget packages can be installed via PackageReference
orpackages.config
. It is recommended to use thePackageReference
style of dependency management because there is a strict version requirement between Couchbase Lite and its dependent Support library (Couchbase.Lite.Support.<Platform>
andCouchbase.Lite.Enterprise.Support.<Platform>
for Community and Enterprise respectively). If you are usingpackages.config
, you must take extra care when upgrading the package to make sure that the support library is also updated to the exact same version. Versions that are not the same are incompatible with each other. -
Your app must call the relevant
Activate()
function inside of the class that is included in the support assembly. There is only one public class in each support assembly, and the support assembly itself is a nuget dependency.For example, UWP looks like
Couchbase.Lite.Support.UWP.Activate()
. Currently the support assemblies provide dependency injected mechanisms for default directory logic, and platform specific logging (i.e., Android will log to logcat with correct log levels and tags. No more "mono-stdout" always at info level.)
Starter code
Open Main.cs in Visual Studio and copy the following code in the main
method.
This snippet demonstrates how to run basic CRUD operations, a simple Query and running bi-directional replications with Sync Gateway.
// Get the database (and create it if it doesn't exist)
var database = new Database("mydb");
// Create a new document (i.e. a record) in the database
string id = null;
using (var mutableDoc = new MutableDocument()) {
mutableDoc.SetFloat("version", 2.0f)
.SetString("type", "SDK");
// Save it to the database
database.Save(mutableDoc);
id = mutableDoc.Id;
}
// Update a document
using (var doc = database.GetDocument(id))
using (var mutableDoc = doc.ToMutable()) {
mutableDoc.SetString("language", "C#");
database.Save(mutableDoc);
using (var docAgain = database.GetDocument(id)) {
Console.WriteLine($"Document ID :: {docAgain.Id}");
Console.WriteLine($"Learning {docAgain.GetString("language")}");
}
}
// Create a query to fetch documents of type SDK
// i.e. SELECT * FROM database WHERE type = "SDK"
using (var query = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(database))
.Where(Expression.Property("type").EqualTo(Expression.String("SDK")))) {
// Run the query
var result = query.Execute();
Console.WriteLine($"Number of rows :: {result.Count()}");
}
// Create replicator to push and pull changes to and from the cloud
var targetEndpoint = new URLEndpoint(new Uri("ws://localhost:4984/getting-started-db"));
var replConfig = new ReplicatorConfiguration(database, targetEndpoint);
// Add authentication
replConfig.Authenticator = new BasicAuthenticator("john", "pass");
// Create replicator (make sure to add an instance or static variable
// named _Replicator)
_Replicator = new Replicator(replConfig);
_Replicator.AddChangeListener((sender, args) =>
{
if (args.Status.Error != null) {
Console.WriteLine($"Error :: {args.Status.Error}");
}
});
_Replicator.Start();
// Later, stop and dispose the replicator *before* closing/disposing the database
Build and run. You should see the document ID and property printed to the console. The document was successfully persisted to the database.
Supported Versions
Couchbase Lite .NET is a .NET Standard 2.0 library. The following tables list out the supported platforms.
Officially Supported
Runtimes which have received more testing and are officially supported are:
.NET Runtime | Minimum Runtime Version | Minimum OS version |
---|---|---|
.NET Core Win |
2.0 |
10 (any Microsoft supported) |
.NET Framework |
4.6.1 |
10 (any Microsoft supported) |
UWP |
6.0.1 |
10.0.16299 |
Xamarin iOS |
10.14 |
10.3.1 |
Xamarin Android |
8 |
5.1/API 22 API 19,20,21 [DEPRECATED] |
Support for API 19, API 20 and API 21 is deprecated in this release. Support will be removed within two (non-maintenance) releases following the deprecation announcement. |
Not Officially Supported
The following runtimes are also compatible but are not QE tested. So they are not officially supported.
.NET Runtime | Minimum Runtime Version | Minimum OS version |
---|---|---|
.NET Core Mac |
2.0 |
10.12 |
.NET Core Linux |
2.0 |
n/a* |
* There are many different variants of Linux, and we don’t have the resources to test all of them. They are tested on Ubuntu 16.04, but have been shown to work on CentOS, and in theory work on any distro supported by .NET Core.
Comparing this to the supported versions in 1.x you can see we’ve traded some lower obsolete versions for new platform support.
Upgrading
Visual Studio
The public facing API has completely changed in Couchbase Lite 2.0 and will require a re-write to upgrade an application that is using Couchbase Lite 1.x. To update an Xcode project built with Couchbase Lite 1.x:
-
Remove the existing Couchbase Lite nuget package from the Visual Studio project.
-
Remove all the Couchbase Lite 1.x dependencies (see the 1.x installation guide).
-
Install the Couchbase Lite 2.0 framework in your project (see the Getting Started section). At this point, there will be many compiler warnings. Refer to the examples on this page to learn about the new API.
-
Build & run your application.
Database Upgrade
Databases that were created with Couchbase Lite 1.2 or later can be used with Couchbase Lite 2.0. Upon detecting it is a 1.x database file, Couchbase Lite will automatically upgrade it to 2.0. This feature is only available for the default storage type (i.e., not a ForestDB database). Additionally, the automatic migration feature does not support encrypted databases, so if the 1.x database is encrypted you will first need to disable encryption using the Couchbase Lite 1.x API (see the 1.x Database Guide).
Handling of Existing Conflicts
For conflicts in the 1.x database, the automatic upgrade process copies the default winning revision to the new 2.0 database and does NOT copy any conflicting revisions. This functionality is related to the way conflicts are being handled in Couchbase Lite 2.0 (see Handling Conflicts). Optionally, existing conflicts in the 1.x database can be resolved with the 1.x API prior to the database being upgraded to 2.0.
Handling of Existing Attachments
Attachments that were persisted in the 1.x database will be copied to the 2.0 database.
In Couchbase Lite 2.0, the Attachment
API has been renamed to Blob
API.
The functionally is identical but the internal schema for attachments has changed.
In 1.x they were stored under the _attachments
field and in Couchbase Lite 2.0 they are stored anywhere in the document like other value types.
The automatic upgrade functionality will not update the internal schema for attachments, so they will still be accessible under the _attachments
field.
The following example shows how to retrieve an attachment that was created in a 1.x database with the 2.0 API.
var attachments = document.GetDictionary("_attachments");
var avatar = attachments.GetBlob("avatar");
var content = avatar?.Content;
Replication Compatibility
The replication protocol used in Couchbase Lite 2.0 has been re-designed from the ground up and it is not backwards compatible with the 1.x replication protocol. Therefore, to use replication with Couchbase Lite 2.x, the target Sync Gateway instance must also be upgraded to 2.x.
Sync Gateway 2.x will continue to accept clients that connect through the 1.x protocol. It will automatically use the 1.x replication protocol when a Couchbase Lite 1.x client connects through http://localhost:4984/db and the 2.0 replication protocol when a Couchbase Lite 2.0 client connects through ws://localhost:4984/db. This allows for a smoother transition to get all your user base onto a version of your application built with Couchbase Lite 2.x.
Database
New Database
As the top-level entity in the API, new databases can be created using the Database
class by passing in a name, configuration, or both.
The following example creates a database using the Database(string name)
method.
var db = new Database("my-database");
Just as before, the database will be created in a default location.
Alternatively, the Database(string name, DatabaseConfiguration config)
initializer can be used to provide specific options in the DatabaseConfiguration
object such as the database directory.
Database Encryption
Enterprise Edition only
Database encryption is an Enterprise Edition feature.
|
The Couchbase Lite 2.1 release includes the ability to encrypt Couchbase Lite databases. This allows mobile applications to secure the data at rest, when it is being stored on the device. The algorithm used to encrypt the database is 256-bit AES.
To enable encryption, you must set the DatabaseConfiguration.encryptionKey
property with the encryption key of your choice.
The encryption key is then required every time the database is opened.
// Create a new, or open an existing database with encryption enabled
var config = new DatabaseConfiguration
{
// Or, derive a key yourself and pass a byte array of the proper size
EncryptionKey = new EncryptionKey("password")
};
using (var db = new Database("seekrit", config)) {
// Change the encryption key (or add encryption if the DB is unencrypted)
db.ChangeEncryptionKey(new EncryptionKey("betterpassw0rd"));
// Remove encryption
db.ChangeEncryptionKey(null);
}
Couchbase Lite does not persist the key. It is the application’s responsibility to manage the key and store it in a platform specific secure store such as Apple’s Keychain or Android’s Keystore.
An encrypted database can only be opened with the same language SDK that was used to encrypt it in the first place (Swift, C#, Java or Objective-C). For example, if a database is encrypted with the Swift SDK and then exported, it will only be readable with the Swift SDK.
Upgrading from 1.x when Encryption is Enabled
If you’re migrating an application from Couchbase Lite 1.x to 2.x, note that the automatic database upgrade functionality is not supported for encrypted databases. Thus, to upgrade an encrypted 1.x database, you should do the following:
-
Disable encryption using the Couchbase Lite 1.x framework (see 1.x encryption guide)
-
Open the database file with encryption enabled using the Couchbase Lite 2.x framework (see database encryption).
Since it is not possible to package Couchbase Lite 1.x and Couchbase Lite 2.x in the same application this upgrade path would require two successive upgrades. If you are using Sync Gateway to synchronize the database content, it may be preferable to run a pull replication from a new 2.x database with encryption enabled and delete the 1.x local database.
Finding a Database File
Where a database goes by default depends on the platform it is running on. Here are the defaults for each platform:
-
.NET Core:
Path.Combine(AppContext.BaseDirectory, "CouchbaseLite")
(unless the app context is altered [e.g. by XUnit], this will be the same directory as the output binary) -
UWP:
Windows.Storage.ApplicationData.Current.LocalFolder.Path
(Inside the installed app sandbox. Note that this sandbox gets deleted sometimes when debugging from inside Visual Studio when the app is shutdown) -
Xamarin iOS: In a folder named CouchbaseLite inside of
ApplicationSupportDirectory
(this can be retrieved more easily from the simulator using the SimPholders utility) -
Xamarin Android: Using the
Context
passed in theActivate()
method,Context.FilesDir.AbsolutePath
(database can be retrieved using adb)
CLI tool
cblite
is a command-line tool for inspecting and querying Couchbase Lite 2.x databases.
You can download and build it from the couchbaselabs GitHub repository.
Note that the cblite
tool is not supported by the Couchbase Support Policy.
Logging
If you are using a Couchbase Lite release prior to 2.5 see Deprecated functionality
From version 2.5, Couchbase Lite provides a logging API that unifies the logging behavior across all platforms, making debugging and troubleshooting easier during development and in production.
The retrieval of logs from the device is out of scope of this feature. |
Available logging features include:
-
Console based logging
-
File based logging
-
Custom logging
Console based logging
Default: Enabled.
Console based logging is often used to facilitate troubleshooting during development.
File based logging
Default: Disabled.
Available file based logging formats:
-
Binary — most efficient for storage and performance. It is the default for file based logging.
-
Plaintext
We recommend using the binary log format and a decoder, such as cbl-log, to view them. Download cbl-log from couchbaselabs/couchbase-mobile-tools.
See Decoding binary logs. |
The following example enables file based logging.
var tempFolder = Path.Combine(Service.GetInstance<IDefaultDirectoryResolver>().DefaultDirectory(), "cbllog");
Database.Log.File.Config = new LogFileConfiguration(tempFolder);
Database.Log.File.Level = LogLevel.Info;
Custom logging
Default: Disabled.
Allows registration of a callback function to receive Couchbase Lite log messages, which may be logged using any external logging framework.
Apps must implement the Logger
interface, as shown below:
private class LogTestLogger : ILogger
{
public LogLevel Level { get; set; }
public void Reset()
{
_lines.Clear();
}
public void Log(LogLevel level, LogDomain domain, string message)
{
// handle the message, for example piping it to
// a third party framework
}
}
And set it on the custom
property.
Database.Log.Custom = new LogTestLogger();
Decoding binary logs
The cbl-log tool should be used to decode binary log files as shown in these examples.
Download the cbl-log tool using wget
.
wget https://packages.couchbase.com/releases/couchbase-lite-log/2.7.0/couchbase-lite-log-2.7.0-macos.zip
Navigate to the bin directory and run the cbl-log
executable.
$ ./cbl-log logcat LOGFILE <OUTPUT_PATH>
Download the cbl-log tool using wget
.
wget https://packages.couchbase.com/releases/couchbase-lite-log/2.7.0/couchbase-lite-log-2.7.0-centos.zip
Navigate to the bin directory and run the cbl-log
executable.
cbl-log logcat LOGFILE <OUTPUT_PATH>
Download the cbl-log tool using PowerShell.
Invoke-WebRequest https://packages.couchbase.com/releases/couchbase-lite-log/2.7.0/couchbase-lite-log-2.7.0-windows.zip -OutFile couchbase-lite-log-2.7.0-windows.zip
Run the cbl-log
executable.
$ .\cbl-log.exe logcat LOGFILE <OUTPUT_PATH>
Logging functionality prior to Release 2.5
This Logging functionality is deprecated. It was replaced by the current Logging API in release 2.5 and so this information is included for completeness only. |
The log messages are split into different domains (LogDomains
) which can be tuned to different log levels.
The following example enables verbose
logging for the replicator
and query
domains.
Database.SetLogLevel(LogDomain.Replicator, LogLevel.Verbose);
Database.SetLogLevel(LogDomain.Query, LogLevel.Verbose);
Loading a pre-built database
If your app needs to sync a lot of data initially, but that data is fairly static and won’t change much, it can be a lot more efficient to bundle a database in your application and install it on the first launch. Even if some of the content changes on the server after you create the app, the app’s first pull replication will bring the database up to date.
To use a prebuilt database, you need to set up the database, build the database into your app bundle as a resource, and install the database during the initial launch.
After your app launches, it needs to check whether the database exists.
If the database does not exist, the app should copy it from the app bundle using the Database.Copy(string, DatabaseConfiguration)
method as shown below.
// Note: Getting the path to a database is platform-specific. For .NET Core / .NET Framework this
// can be a simple filesystem path. For UWP, you will need to get the path from your assets. For
// iOS you need to get the path from the main bundle. For Android you need to extract it from your
// assets to a temporary directory and then pass that path.
var path = Path.Combine(Environment.CurrentDirectory, "travel-sample.cblite2" + Path.DirectorySeparatorChar);
if (!Database.Exists("travel-sample", null)) {
_NeedsExtraDocs = true;
Database.Copy(path, "travel-sample", null);
}
Document
In Couchbase Lite, a document’s body takes the form of a JSON object—a collection of key/value pairs where the values can be different types of data such as numbers, strings, arrays or even nested objects. Every document is identified by a document ID, which can be automatically generated (as a UUID) or specified programmatically; the only constraints are that it must be unique within the database, and it can’t be changed.
Initializers
The following methods/initializers can be used:
-
The
MutableDocument()
constructor can be used to create a new document where the document ID is randomly generated by the database. -
The
MutableDocument(string documentID)
constructor can be used to create a new document with a specific ID. -
The
database.GetDocument(string documentID)
method can be used to get a document. If it doesn’t exist in the database, it will returnnull
. This method can be used to check if a document with a given ID already exists in the database.
The following code example creates a document and persists it to the database.
using (var newTask = new MutableDocument("xyz")) {
newTask.SetString("type", "task")
.SetString("owner", "todo")
.SetDate("createdAt", DateTimeOffset.UtcNow);
db.Save(newTask);
}
Mutability
By default, when a document is read from the database it is immutable.
The document.toMutable()
method should be used to create an instance of the document which can be updated.
using(var document = db.GetDocument("xyz"))
using (var mutableDocument = document.ToMutable()) {
mutableDocument.SetString("name", "apples");
db.Save(mutableDocument);
}
Changes to the document are persisted to the database when the saveDocument
method is called.
Typed Accessors
The Document
class now offers a set of property accessors
for various scalar types, including boolean, integers, floating-point and strings.
These accessors take care of converting to/from JSON encoding, and make sure you get the type you’re expecting.
In addition, as a convenience we offer DateTimeOffset
accessors.
Dates are a common data type, but JSON doesn’t natively support them, so the convention is to store them as strings in ISO-8601 format.
The following example sets the date on the createdAt
property and reads it back using the document.GetDate(string key)
accessor method.
newTask.SetValue("createdAt", DateTimeOffset.UtcNow);
var date = newTask.GetDate("createdAt");
If the property doesn’t exist in the document it will return the default value for that getter method (0 for getInt
, 0.0 for getFloat
etc.).
To check whether a given property exists in the document, you should use the Document.Contains(string key)
method.
Batch operations
If you’re making multiple changes to a database at once, it’s faster to group them together. The following example persists a few documents in batch.
db.InBatch(() =>
{
for (var i = 0; i < 10; i++) {
using (var doc = new MutableDocument()) {
doc.SetString("type", "user");
doc.SetString("name", $"user {i}");
doc.SetBoolean("admin", false);
db.Save(doc);
Console.WriteLine($"Saved user document {doc.GetString("name")}");
}
}
});
At the local level this operation is still transactional: no other Database
instances, including ones managed by the replicator can make changes during the execution of the block, and other instances will not see partial changes.
But Couchbase Mobile is a distributed system, and due to the way replication works, there’s no guarantee that Sync Gateway or other devices will receive your changes all at once.
Document Constraints
Couchbase Lite APIs do not explicitly disallow the use of attributes with the underscore prefix at the top level of document. This is to facilitate the creation of documents for use either in local only mode where documents are not synced, or when used exclusively in peer-to-peer sync.
"_id", :"_rev" and "_sequence" are reserved keywords and must not be used as top-level attributes — see Example 1. |
Users are cautioned that any attempt to sync such documents to Sync Gateway will result in an error. To be future proof, you are advised to avoid creating such documents. Use of these attributes for user-level data may result in undefined system behavior.
For more guidance — see: Sync Gateway - data modeling guidelines
-
_attachments
-
_id
-
_deleted
-
_removed
-
_rev
Document Expiration
Document expiration allows users to set the expiration date to a document. When the document is expired, the document will be purged from the database. The purge will not be replicated to Sync Gateway.
The following example sets the TTL for a document to 5 minutes from the current time.
// Purge the document one day from now
var ttl = DateTimeOffset.UtcNow.AddDays(1);
db.SetDocumentExpiration("doc123", ttl);
// Reset expiration
db.SetDocumentExpiration("doc1", null);
// Query documents that will be expired in less than five minutes
var fiveMinutesFromNow = DateTimeOffset.UtcNow.AddMinutes(5).ToUnixTimeMilliseconds();
var query = QueryBuilder
.Select(SelectResult.Expression(Meta.ID))
.From(DataSource.Database(db))
.Where(Meta.Expiration.LessThan(Expression.Double(fiveMinutesFromNow)));
Blobs
We’ve renamed "attachments" to "blobs".
The new behavior should be clearer too: a Blob
is now a normal object that can appear in a document as a property value.
In other words, you just instantiate a Blob
and set it as the value of a property, and then later you can get the property value, which will be a Blob
object.
The following code example adds a blob to the document under the avatar
property.
// Note: Reading the data is implementation dependent, as with prebuilt databases
var image = File.ReadAllBytes("avatar.jpg");
var blob = new Blob("image/jpeg", image);
newTask.SetBlob("avatar", blob);
db.Save(newTask);
The Blob
API lets you access the contents as in-memory data (a Data
object) or as a InputStream
.
It also supports an optional type
property that by convention stores the MIME type of the contents.
In the example above, "image/jpeg" is the MIME type and "avatar" is the key which references that Blob
.
That key can be used to retrieve the Blob
object at a later time.
On Couchbase Lite, blobs can be arbitrarily large, and are only read on demand, not when you load a Document
object.
On Sync Gateway, the maximum content size is 20 MB per blob.
If a document’s blob is over 20 MB, the document will be replicated but not the blob.
When a document is synchronized, the Couchbase Lite replicator will add an _attachments
dictionary to the document’s properties if it contains a blob.
A random access name will be generated for each Blob
which is different to the "avatar" key that was used in the example above.
On the image below, the document now contains the _attachments
dictionary when viewed in the Couchbase Server Admin Console.

A blob also has properties such as "digest"
(a SHA-1 digest of the data), "length"
(the length in bytes), and optionally "content_type"
(the MIME type).
The data is not stored in the document, but in a separate content-addressable store, indexed by the digest.
This Blob
can be retrieved on the Sync Gateway REST API at http://localhost:4984/justdoit/user.david/blob_1.
Notice that the blob identifier in the URL path is "blob_1" (not "avatar").
Query
Database queries have changed significantly. Instead of the map/reduce views used in 1.x, they’re now based on expressions, of the form "return ____ from documents where ____, ordered by ____", with semantics based on Couchbase’s N1QL query language.
There are several parts to specifying a query:
- SELECT
-
Specifies the projection, which is the part of the document that is to be returned.
- FROM
-
Specifies the database to query the documents from.
- JOIN
-
Specifies the matching criteria in which to join multiple documents.
- WHERE
-
Specifies the query criteria that the result must satisfy.
- GROUP BY
-
Specifies the query criteria to group rows by.
- ORDER BY
-
Specifies the query criteria to sort the rows in the result.
Indexing
Before we begin querying documents, let’s briefly mention the importance of having a query index. A query can only be fast if there’s a pre-existing database index it can search to narrow down the set of documents to examine.
The following example creates a new index for the type
and name
properties.
{
"_id": "hotel123",
"type": "hotel",
"name": "Apple Droid"
}
// For value types, this is optional but provides performance enhancements
var index = IndexBuilder.ValueIndex(
ValueIndexItem.Expression(Expression.Property("type")),
ValueIndexItem.Expression(Expression.Property("name")));
db.CreateIndex("TypeNameIndex", index);
If there are multiple expressions, the first one will be the primary key, the second the secondary key, etc.
Every index has to be updated whenever a document is updated, so too many indexes can hurt performance. Thus, good performance depends on designing and creating the right indexes to go along with your queries. |
SELECT statement
With the SELECT statement, you can query and manipulate JSON data. With projections, you retrieve just the fields that you need and not the entire document.
A SelectResult represents a single return value of the query statement.
You can specify a comma separated list of SelectResult
expressions in the select statement of your query.
For instance the following select statement queries for the document _id
as well as the type
and name
properties of all documents in the database.
In the query result, we print the _id
and name
properties of each row using the property name getter method.
{
"_id": "hotel123",
"type": "hotel",
"name": "Apple Droid"
}
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("type"),
SelectResult.Property("name"))
.From(DataSource.Database(db))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Document ID :: {result.GetString("id")}");
Console.WriteLine($"Document Name :: {result.GetString("name")}");
}
}
The SelectResult.all()
method can be used to query all the properties of a document.
In this case, the document in the result is embedded in a dictionary where the key is the database name.
The following snippet shows the same query using SelectResult.all()
and the result in JSON.
using (var query = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(db))) {
// All user properties will be available here
}
[
{
"travel-sample": {
"callsign": "MILE-AIR",
"country": "United States",
"iata": "Q5",
"icao": "MLA",
"id": 10,
"name": "40-Mile Air",
"type": "airline"
}
},
{
"travel-sample": {
"callsign": "TXW",
"country": "United States",
"iata": "TQ",
"icao": "TXW",
"id": 10123,
"name": "Texas Wings",
"type": "airline"
}
}
]
WHERE statement
Similar to SQL, you can use the where clause to filter the documents to be returned as part of the query.
The select statement takes in an Expression
.
You can chain any number of Expressions in order to implement sophisticated filtering capabilities.
Comparison
The comparison operators can be used in the WHERE statement to specify on which property to match documents.
In the example below, we use the equalTo
operator to query documents where the type
property equals "hotel".
{
"_id": "hotel123",
"type": "hotel",
"name": "Apple Droid"
}
using (var query = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("hotel")))
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
var dict = result.GetDictionary(db.Name);
Console.WriteLine($"Document Name :: {dict?.GetString("name")}");
}
}
Collection Operators
Collection operators are useful to check if a given value is present in an array.
CONTAINS Operator
The following example uses the Function.arrayContains
to find documents whose public_likes
array property contain a value equal to "Armani Langworth".
{
"_id": "hotel123",
"name": "Apple Droid",
"public_likes": ["Armani Langworth", "Elfrieda Gutkowski", "Maureen Ruecker"]
}
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("name"),
SelectResult.Property("public_likes"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("hotel"))
.And(ArrayFunction.Contains(Expression.Property("public_likes"),
Expression.String("Armani Langworth"))))) {
foreach (var result in query.Execute()) {
var publicLikes = result.GetArray("public_likes");
var jsonString = JsonConvert.SerializeObject(publicLikes);
Console.WriteLine($"Public Likes :: {jsonString}");
}
}
IN Operator
The IN
operator is useful when you need to explicitly list out the values to test against.
The following example looks for documents whose first
, last
or username
property value equals "Armani".
var values = new IExpression[]
{ Expression.Property("first"), Expression.Property("last"), Expression.Property("username") };
using (var query = QueryBuilder.Select(
SelectResult.All())
.From(DataSource.Database(db))
.Where(Expression.String("Armani").In(values))) {
foreach (var result in query.Execute()) {
var body = result.GetDictionary(0);
var jsonString = JsonConvert.SerializeObject(body);
Console.WriteLine($"In results :: {jsonString}");
}
}
Like Operator
The like
operator can be used for string matching.
The like
operator performs case sensitive matches.
So if you want to make the string matching case insensitive, you would have to use Function.lower
or Function.upper
to transform the matched string to lowercase or uppercase equivalents.
In the example below, we are looking for documents of type landmark
where the name property exactly matches the string "Royal engineers museum".
Note that since like
does a case sensitive match, we use Function.lower
to transform the matched string to the lowercase equivalent.
So the following query will return "landmark" type documents with the name matching "Royal Engineers Museum", "royal engineers museum", "ROYAL ENGINEERS MUSEUM" and so on.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("name"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("landmark"))
.And(Function.Lower(Expression.Property("name")).Like(Expression.String("Royal Engineers Museum"))))
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Name Property :: {result.GetString("name")}");
}
}
Wildcard Match
We can use %
sign within a like
expression to do a wildcard match against zero or more characters.
Using wildcards allows you to have some fuzziness in your search string.
In the example below, we are looking for documents of type
"landmark" where the name property matches any string that begins with "eng" followed by zero or more characters, the letter "e", followed by zero or more characters.
Once again, we are using Function.lower
to make the search case insensitive.
The following query will return "landmark" type
documents with name matching "Engineers", "engine", "english egg" , "England Eagle" and so on.
Notice that the matches may span word boundaries.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("name"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("landmark"))
.And(Function.Lower(Expression.Property("name")).Like(Expression.String("Eng%e%"))))
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Name Property :: {result.GetString("name")}");
}
}
Wildcard Character Match
We can use an _
sign within a like expression to do a wildcard match against a single character.
In the example below, we are looking for documents of type "landmark" where the name
property matches any string that begins with "eng" followed by exactly 4 wildcard characters and ending in the letter "r".
The following query will return "landmark" type
documents with the name
matching "Engineer", "engineer" and so on.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("name"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("landmark"))
.And(Expression.Property("name").Like(Expression.String("Royal Eng____rs Museum"))))
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Name Property :: {result.GetString("name")}");
}
}
Regex Operator
Similar to wildcard like
expressions, regex
expressions based pattern matching allow you to have some fuzziness in your search string.
The regex
operator is case sensitive.
In the example below, we are looking for documents of type
"landmark" where the name property matches any string (on word boundaries) that begins with "eng" followed by exactly 4 wildcard characters and ending in the letter "r".
The following query will return "landmark" type documents with name matching "Engine", "engine" and so on.
Note that the \b
specifies that the match must occur on word boundaries.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("name"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("landmark"))
.And(Expression.Property("name").Regex(Expression.String("\\bEng.*e\\b"))))
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Name Property :: {result.GetString("name")}");
}
}
Deleted Document
Starting in Couchbase Lite 2.5, you can query documents that have been deleted (tombstones). The following example shows how to query deleted documents in the database.
// Query documents that have been deleted
var query = QueryBuilder
.Select(SelectResult.Expression(Meta.ID))
.From(DataSource.Database(db))
.Where(Meta.IsDeleted);
JOIN statement
The JOIN clause enables you to create new input objects by combining two or more source objects.
The following example uses a JOIN clause to find the airline details which have routes that start from RIX.
This example JOINS the document of type "route" with documents of type "airline" using the document ID (_id
) on the "airline" document and airlineid
on the "route" document.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Expression.Property("name").From("airline")),
SelectResult.Expression(Expression.Property("callsign").From("airline")),
SelectResult.Expression(Expression.Property("destinationairport").From("route")),
SelectResult.Expression(Expression.Property("stops").From("route")),
SelectResult.Expression(Expression.Property("airline").From("route")))
.From(DataSource.Database(db).As("airline"))
.Join(Join.InnerJoin(DataSource.Database(db).As("route"))
.On(Meta.ID.From("airline").EqualTo(Expression.Property("airlineid").From("route"))))
.Where(Expression.Property("type").From("route").EqualTo(Expression.String("route"))
.And(Expression.Property("type").From("airline").EqualTo(Expression.String("airline")))
.And(Expression.Property("sourceairport").From("route").EqualTo(Expression.String("RIX"))))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Name Property :: {result.GetString("name")}");
}
}
GROUP BY statement
You can perform further processing on the data in your result set before the final projection is generated. The following example looks for the number of airports at an altitude of 300 ft or higher and groups the results by country and timezone.
{
"_id": "airport123",
"type": "airport",
"country": "United States",
"geo": { "alt": 456 },
"tz": "America/Anchorage"
}
using (var query = QueryBuilder.Select(
SelectResult.Expression(Function.Count(Expression.All())),
SelectResult.Property("country"),
SelectResult.Property("tz"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("airport"))
.And(Expression.Property("geo.alt").GreaterThanOrEqualTo(Expression.Int(300))))
.GroupBy(Expression.Property("country"), Expression.Property("tz"))) {
foreach (var result in query.Execute()) {
Console.WriteLine(
$"There are {result.GetInt("$1")} airports in the {result.GetString("tz")} timezone located in {result.GetString("country")} and above 300 ft");
}
}
There are 138 airports on the Europe/Paris timezone located in France and above 300 ft
There are 29 airports on the Europe/London timezone located in United Kingdom and above 300 ft
There are 50 airports on the America/Anchorage timezone located in United States and above 300 ft
There are 279 airports on the America/Chicago timezone located in United States and above 300 ft
There are 123 airports on the America/Denver timezone located in United States and above 300 ft
ORDER BY statement
It is possible to sort the results of a query based on a given expression result. The example below returns documents of type equal to "hotel" sorted in ascending order by the value of the title property.
using (var query = QueryBuilder.Select(
SelectResult.Expression(Meta.ID),
SelectResult.Property("title"))
.From(DataSource.Database(db))
.Where(Expression.Property("type").EqualTo(Expression.String("hotel")))
.OrderBy(Ordering.Property("title").Ascending())
.Limit(Expression.Int(10))) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Title :: {result.GetString("title")}");
}
}
Aberdyfi
Achiltibuie
Altrincham
Ambleside
Annan
Ardèche
Armagh
Avignon
Date/Time Functions
Couchbase Lite documents support a date type that internally stores dates in ISO 8601 with the GMT/UTC timezone.
Couchbase Lite 2.5 adds the ability to run date comparisons in your Couchbase Lite queries. To do so, four functions have been added to the Query Builder API:
Function.StringToMillis(Expression.Property("date_time"))
-
The input to this will be a validly formatted ISO 8601
date_time
string. The end result will be an expression (with a numeric content) that can be further input into the query builder. Function.StringToUTC(Expression.Property("date_time"))
-
The input to this will be a validly formatted ISO 8601
date_time
string. The end result will be an expression (with string content) that can be further input into the query builder. Function.MillisToString(Expression.Property("date_time"))
-
The input for this is a numeric value representing milliseconds since the Unix epoch. The end result will be an expression (with string content representing the date and time as an ISO 8601 string in the device’s timezone) that can be further input into the query builder.
Function.MillisToUTC(Expression.Property("date_time"))
-
The input for this is a numeric value representing milliseconds since the Unix epoch. The end result will be an expression (with string content representing the date and time as a UTC ISO 8601 string) that can be further input into the query builder.
Live Query
A live query stays active and monitors the database for changes. A live query is a great way to build reactive user interfaces, especially table/list views, that keep themselves up to date. For example, as the replicator runs and pulls new data from the server, a live query-driven UI will automatically update to show the data without the user having to manually refresh. This helps your app feel quick and responsive.
var query = QueryBuilder
.Select(SelectResult.All())
.From(DataSource.Database(db));
// Adds a query change listener.
// Changes will be posted on the main queue.
var token = query.AddChangeListener((sender, args) =>
{
var allResult = args.Results.AllResults();
foreach (var result in allResult) {
Console.WriteLine(result.Keys);
/* Update UI */
}
});
// Start live query.
query.Execute(); (1)
1 | To start a live query, you must call query.execute() .
This will immediately execute the query and post the result to the change listener.
When there’s a change it re-runs itself automatically, and posts the new query result to any observers (change listeners). |
The following example stops the live query with the token from the previous example.
query.RemoveChangeListener(token);
query.Dispose();
Indexing
Creating indexes can speed up the performance of queries. While indexes make queries faster, they also make writes slightly slower, and the Couchbase Lite database file slightly larger. As such, it is best to only create indexes when you need to optimize a specific case for better query performance.
The following example creates a new index for the type
and name
properties.
{
"_id": "hotel123",
"type": "hotel",
"name": "Apple Droid"
}
// For value types, this is optional but provides performance enhancements
var index = IndexBuilder.ValueIndex(
ValueIndexItem.Expression(Expression.Property("type")),
ValueIndexItem.Expression(Expression.Property("name")));
db.CreateIndex("TypeNameIndex", index);
If there are multiple expressions, the first one will be the primary key, the second the secondary key, etc.
Every index has to be updated whenever a document is updated, so too many indexes can hurt performance. Thus, good performance depends on designing and creating the right indexes to go along with your queries. |
Predictive Query
Enterprise Edition only
Predictive Query is an Enterprise Edition feature.
|
Predictive Query enables Couchbase Lite queries to use machine learning, by providing query functions that can process document data (properties or blobs) via trained ML models.
Let’s consider an image classifier model that takes a picture as input and outputs a label and probability.

To run a predictive query with a model as the one shown above, you must implement the following steps.
Integrate the Model
To integrate a model with Couchbase Lite, you must implement the PredictiveModel
interface which has only one function called predict()
.
// `TensorFlowModel` is a fake implementation
// this would be the implementation of the ml model you have chosen
class TensorFlowModel
{
public static IDictionary<string, object> PredictImage(byte[] data)
{
// Do calculations, etc
return null;
}
}
class ImageClassifierModel : IPredictiveModel
{
public DictionaryObject Predict(DictionaryObject input)
{
var blob = input.GetBlob("photo");
if (blob == null) {
return null;
}
var imageData = blob.Content;
// `TensorFlowModel` is a fake implementation
// this would be the implementation of the ml model you have chosen
var modelOutput = TensorFlowModel.PredictImage(imageData);
return new MutableDictionaryObject(modelOutput); (1)
}
}
1 | The predict(input) -> output method provides the input and expects the result of using the machine learning model.
The input and output of the predictive model is a DictionaryObject .
Therefore, the supported data type will be constrained by the data type that the DictionaryObject supports. |
Register the Model
To register the model you must create a new instance and pass it to the Database.prediction.registerModel
static method.
var model = new ImageClassifierModel();
Database.Prediction.RegisterModel("ImageClassifier", model);
Create an Index
Creating an index for a predictive query is highly recommended. By computing the predictions during writes and building a prediction index, you can significantly improve the speed of prediction queries (which would otherwise have to be computed during reads).
There are two types of indexes for predictive queries:
Value Index
The code below creates a value index from the "label" value of the prediction result. When documents are added or updated, the index will call the prediction function to update the label value in the index.
var index = IndexBuilder.ValueIndex(ValueIndexItem.Property("label"));
db.CreateIndex("value-index-image-classifier", index);
Predictive Index
Predictive Index is a new index type used for predictive query. The Predictive Index is different from the value index in that the Predictive Index caches the predictive result and creates the value index from the cached predictive result when the predictive results values are specified.
The code below creates a predictive index from the "label" value of the prediction result.
var input = Expression.Dictionary(new Dictionary<string, object>
{
["photo"] = Expression.Property("photo")
});
var index = IndexBuilder.PredictiveIndex("ImageClassifier", input);
db.CreateIndex("predictive-index-image-classifier", index);
Run a Prediction Query
The code below creates a query that calls the prediction function to return the "label" value for the first 10 results in the database.
var input = Expression.Dictionary(new Dictionary<string, object>
{
["photo"] = Expression.Property("photo")
});
var prediction = PredictiveModel.predict("ImageClassifier", input); (1)
using (var q = QueryBuilder.Select(SelectResult.All())
.From(DataSource.Database(db))
.Where(prediction.Property("label").EqualTo(Expression.String("car"))
.And(prediction.Property("probability").GreaterThanOrEqualTo(Expression.Double(0.8))))) {
var result = q.Execute();
Console.WriteLine($"Number of rows: {result.Count()}");
}
1 | The PredictiveModel.predict() method returns a constructed Prediction Function object which can be used further to specify a property value extracted from the output dictionary of the PredictiveModel.predict() function.
|
Full-Text Search
To run a full-text search (FTS) query, you must have created a full-text index on the expression being matched.
Unlike queries, the index is not optional.
The following example inserts documents and creates an FTS index on the name
property.
var index = IndexBuilder.FullTextIndex(FullTextIndexItem.Property("name")).IgnoreAccents(false);
db.CreateIndex("nameFTSIndex", index);
Multiple properties to index can be specified in the IndexBuilder.FullTextIndex(params FullTextIndexItem[] items)
method.
With the index created, an FTS query on the property that is being indexed can be constructed and ran.
The full-text search criteria is defined as a FullTextExpression
.
The left-hand side is the full-text index to use and the right-hand side is the pattern to match.
var whereClause = FullTextExpression.Index("nameFTSIndex").Match("'buy'");
using (var query = QueryBuilder.Select(SelectResult.Expression(Meta.ID))
.From(DataSource.Database(db))
.Where(whereClause)) {
foreach (var result in query.Execute()) {
Console.WriteLine($"Document id {result.GetString(0)}");
}
}
In the example above, the pattern to match is a word, the full-text search query matches all documents that contain the word "buy" in the value of the doc.name
property.
Search is supported for all languages that use whitespace to separate words.
Stemming, which is the process of fuzzy matching parts of speech, like "fast" and "faster", is supported in the following languages: danish, dutch, english, finnish, french, german, hungarian, italian, norwegian, portuguese, romanian, russian, spanish, swedish and turkish.
The pattern to match can also be in the following forms:
- prefix queries
-
The query expression used to search for a term prefix is the prefix itself with a "*" character appended to it. For example:
"'lin*'" -- Query for all documents containing a term with the prefix "lin". This will match -- all documents that contain "linux", but also those that contain terms "linear", --"linker", "linguistic" and so on.
- overriding the property name that is being indexed
-
Normally, a token or token prefix query is matched against the document property specified as the left-hand side of the
match
operator. This may be overridden by specifying a property name followed by a ":" character before a basic term query. There may be space between the ":" and the term to query for, but not between the property name and the ":" character. For example:'title:linux problems' -- Query the database for documents for which the term "linux" appears in -- the document title, and the term "problems" appears in either the title -- or body of the document.
- phrase queries
-
A phrase query is a query that retrieves all documents that contain a nominated set of terms or term prefixes in a specified order with no intervening tokens. Phrase queries are specified by enclosing a space separated sequence of terms or term prefixes in double quotes ("). For example:
"'"linux applications"'" -- Query for all documents that contain the phrase "linux applications".
- NEAR queries
-
A NEAR query is a query that returns documents that contain a two or more nominated terms or phrases within a specified proximity of each other (by default with 10 or less intervening terms). A NEAR query is specified by putting the keyword "NEAR" between two phrase, token or token prefix queries. To specify a proximity other than the default, an operator of the form "NEAR/" may be used, where is the maximum number of intervening terms allowed. For example:
"'database NEAR/2 "replication"'" -- Search for a document that contains the phrase "replication" and the term -- "database" with not more than 2 terms separating the two.
- AND, OR & NOT query operators
-
The enhanced query syntax supports the AND, OR and NOT binary set operators. Each of the two operands to an operator may be a basic FTS query, or the result of another AND, OR or NOT set operation. Operators must be entered using capital letters. Otherwise, they are interpreted as basic term queries instead of set operators. For example:
'couchbase AND database' -- Return the set of documents that contain the term "couchbase", and the -- term "database". This query will return the document with docid 3 only.
When using the enhanced query syntax, parenthesis may be used to specify the precedence of the various operators. For example:
'("couchbase database" OR "sqlite library") AND linux' -- Query for the set of documents that contains the term "linux", and at least -- one of the phrases "couchbase database" and "sqlite library".
Ordering results
It’s very common to sort full-text results in descending order of relevance.
This can be a very difficult heuristic to define, but Couchbase Lite comes with a fairly simple ranking function you can use.
In the OrderBy
array, use a string of the form Rank(X)
, where X
is the property or expression being searched, to represent the ranking of the result.
Replication
Couchbase Mobile’s replication protocol id based on WebSockets [1].
The replicator is designed to send documents from a source to a target database. The target can be one of the following:
- URLEndpoint
-
To replicate data between a local Couchbase Lite database and remote Sync Gateway database.
- DatabaseEndpoint
-
To replicate data between two local Couchbase Lite databases to store data on secondary storage.
- MessageEndpoint
-
To replicate with another Couchbase Lite database via a custom transportation protocol such iOS Multipeer Connectivity, Android WiFi Direct, Android NearByConnection, socket based transportation etc.
Replications can be run in one of two modes:
-
one shot - single exchange of data
-
continuous - a continual replication of data changes as they occur
Compatibility
The new protocol is incompatible with CouchDB-based databases. And since Couchbase Lite 2 only supports the new protocol, you will need to run a version of Sync Gateway that supports it. |
To use this protocol with Couchbase Lite 2.0, the replication URL should specify WebSockets as the URL scheme (see the "Starting a Replication" section below). Mobile clients using Couchbase Lite 1.x can continue to use http as the URL scheme. Sync Gateway 2.0 will automatically use the 1.x replication protocol when a Couchbase Lite 1.x client connects through "http://localhost:4984/db" and the 2.0 replication protocol when a Couchbase Lite 2.0 client connects through "ws://localhost:4984/db".
Starting Sync Gateway
Download Sync Gateway and start it from the command line with the configuration file created above.
powershell & 'C:\Program Files (x86)\Couchbase\sync_gateway.exe' sync-gateway-config.json
bash ~/Downloads/couchbase-sync-gateway/bin/sync_gateway
For platform specific installation instructions, refer to the Sync Gateway installation guide.
Starting a Replication
Replication objects are now bidirectional, this means you can start a push
/pull
replication with a single instance.
The replication’s parameters can be specified through the ReplicatorConfiguration
object;
for example, if you wish to start a push
only or pull
only replication.
The following example creates a pull
replication with Sync Gateway.
public class MyClass
{
public Database Database { get; set; }
public Replicator Replicator { get; set; } (1)
public void StartReplication()
{
var url = new Uri("ws://localhost:4984/db"); (2)
var target = new URLEndpoint(url);
var config = new ReplicatorConfiguration(Database, target)
{
ReplicatorType = ReplicatorType.Pull
};
Replicator = new Replicator(config);
Replicator.Start();
}
}
1 | A replication is an asynchronous operation.
To keep a reference to the replicator object, you can set it as an instance property. |
2 | The URL scheme for remote database URLs has changed in Couchbase Lite 2.0.
You should now use ws: , or wss: for SSL/TLS connections. |
To verify that documents have been replicated, you can:
-
Monitor the Sync Gateway sequence number returned by the database endpoint (
GET /{db}/
). The sequence number increments for every change that happens on the Sync Gateway database. -
Query a document by ID on the Sync Gateway REST API (
GET /{db}/{id}
). -
Query a document from the Query Workbench on the Couchbase Server Console.
Couchbase Lite 2.0 uses WebSockets as the communication protocol to transmit data. Some load balancers are not configured for WebSocket connections by default (NGINX for example); so it might be necessary to explicitly enable them in the load balancer’s configuration (see Load Balancers).
By default, the WebSocket protocol uses compression to optimize for speed and bandwidth utilization.
The level of compression is set on Sync Gateway and can be tuned in the configuration file (replicator_compression
).
Troubleshooting
As always, when there is a problem with replication, logging is your friend. The following example increases the log output for activity related to replication with Sync Gateway.
Database.SetLogLevel(LogDomain.Replicator, LogLevel.Verbose);
Database.SetLogLevel(LogDomain.Network, LogLevel.Verbose);
Authentication
By default, Sync Gateway does not enable authentication. This is to make it easier to get up and running with synchronization. You can enable authentication with the following properties in the configuration file:
{
"databases": {
"mydatabase": {
"users": {
"GUEST": {"disabled": true}
}
}
}
}
To authenticate with Sync Gateway, an associated user must first be created.
Sync Gateway users can be created through the POST /{db}/_user
endpoint on the Admin REST API.
Provided that the user exists on Sync Gateway, there are two ways to authenticate from a Couchbase Lite client: Basic Authentication or Session Authentication.
Basic Authentication
You can provide a user name and password to the basic authenticator class method.
Under the hood, the replicator will send the credentials in the first request to retrieve a SyncGatewaySession
cookie and use it for all subsequent requests during the replication.
This is the recommended way of using basic authentication.
The following example initiates a one-shot replication as the user john with the password pass.
var url = new Uri("ws://localhost:4984/mydatabase");
var target = new URLEndpoint(url);
var config = new ReplicatorConfiguration(database, target);
config.Authenticator = new BasicAuthenticator("john", "pass");
var replicator = new Replicator(config);
replicator.Start();
Session Authentication
Session authentication is another way to authenticate with Sync Gateway.
A user session must first be created through the POST /{db}/_session
endpoint on the Public REST API.
The HTTP response contains a session ID which can then be used to authenticate as the user it was created for.
The following example initiates a one-shot replication with the session ID that is returned from the POST /{db}/_session
endpoint.
var url = new Uri("ws://localhost:4984/mydatabase");
var target = new URLEndpoint(url);
var config = new ReplicatorConfiguration(database, target);
config.Authenticator = new SessionAuthenticator("904ac010862f37c8dd99015a33ab5a3565fd8447");
var replicator = new Replicator(config);
replicator.Start();
Replication Status
The replication.Status.Activity
property can be used to check the status of a replication.
For example, when the replication is actively transferring data and when it has stopped.
replicator.AddChangeListener((sender, args) =>
{
if (args.Status.Activity == ReplicatorActivityLevel.Stopped) {
Console.WriteLine("Replication stopped");
}
});
The following table lists the different activity levels in the API and the meaning of each one.
State | Meaning |
---|---|
|
The replication is finished or hit a fatal error. |
|
The replicator is offline as the remote host is unreachable. |
|
The replicator is connecting to the remote host. |
|
The replication caught up with all the changes available from the server.
The |
|
The replication is actively transferring data. |
The replication change object also has properties to track the progress (change.status.completed and change.status.total ).
But since the replication occurs in batches and the total count can vary through the course of a replication, those progress indicators are not very useful from the standpoint of an app user.
Hence, these should not be used for tracking the actual progress of the replication.
|
Replication Status and App Life Cycle
Couchbase Lite doesn’t react to OS backgrounding or foregrounding events and replication(s) will continue running as long as the remote system does not terminate the connection and the app does not terminate. It is generally recommended to stop replications before going into the background otherwise socket connections may be closed by the OS and this may interfere with the replication process.
Handling Network Errors
If an error occurs, the replication status will be updated with an Error
which follows the standard HTTP error codes.
The following example monitors the replication for errors and logs the error code to the console.
replicator.AddChangeListener((sender, args) =>
{
if (args.Status.Error != null) {
Console.WriteLine($"Error :: {args.Status.Error}");
}
});
When a permanent error occurs (i.e., 404
: not found, 401
: unauthorized), the replicator (continuous or one-shot) will stop permanently.
If the error is temporary (i.e., waiting for the network to recover), a continuous replication will retry to connect indefinitely and if the replication is one-shot it will retry for a limited number of times.
The following error codes are considered temporary by the Couchbase Lite replicator and thus will trigger a connection retry.
-
408
: Request Timeout -
429
: Too Many Requests -
500
: Internal Server Error -
502
: Bad Gateway -
503
: Service Unavailable -
504
: Gateway Timeout -
1001
: DNS resolution error
Replication Events
You can choose to register for document updates during a replication.
For example, the code snippet below registers a listener to monitor document replication performed by the replicator referenced by the variable replicator
. It prints the document ID of each document received and sent.
var token = replicator.AddDocumentReplicationListener((sender, args) =>
{
var direction = args.IsPush ? "Push" : "Pull";
Console.WriteLine($"Replication type :: {direction}");
foreach (var document in args.Documents) {
if (document.Error == null) {
Console.WriteLine($"Doc ID :: {document.Id}");
if (document.Flags.HasFlag(DocumentFlags.Deleted)) {
Console.WriteLine("Successfully replicated a deleted document");
}
} else {
// There was an error
}
}
});
replicator.Start();
The following example stops the change listener with the token from the previous example.
replicator.RemoveChangeListener(token);
Document Access Removal Behavior
When access to a document is removed on Sync Gateway, the document replication listener sends a notification with the AccessRemoved
flag set to true
and subsequently purges the document from the database.
Custom Headers
Custom headers can be set on the configuration object. And the replicator will send those header(s) in every request. As an example, this feature can be useful to pass additional credentials when there is an authentication or authorization step being done by a proxy server (between Couchbase Lite and Sync Gateway).
var config = new ReplicatorConfiguration(database, target)
{
Headers = new Dictionary<string, string>
{
["CustomHeaderName"] = "Value"
}
};
Replication Checkpoint Reset
Replicators use checkpoints to keep track of documents sent to the target database. Without checkpoints, Couchbase Lite would replicate the entire database content to the target database on each connection, even though previous replications may already have replicated some or all of that content.
This functionality is generally not a concern to application developers.
However, if you do want to force the replication to start again from zero, use the checkpoint reset method replicator.resetCheckpoint()
before starting the replicator.
// replicator is a Replicator instance
replicator.ResetCheckpoint();
replicator.Start();
Replication Filters
Replication Filters allow you to have quick control over which documents are stored as the result of a push and/or pull replication.
Push Filter
A push filter allows an app to push a subset of a database to the server, which can be very useful in some circumstances. For instance, high-priority documents could be pushed first, or documents in a "draft" state could be skipped.
The following example filters out documents whose type
property is equal to draft
.
var url = new Uri("ws://localhost:4984/mydatabase");
var target = new URLEndpoint(url);
var config = new ReplicatorConfiguration(database, target);
config.PushFilter = (document, flags) => (1)
{
if (flags.HasFlag(DocumentFlags.Deleted)) {
return false;
}
return true;
};
// Dispose() later
var replicator = new Replicator(config);
replicator.Start();
1 | The callback should follow the semantics of a pure function. Otherwise, long running functions would slow down the replicator considerably. Furthermore, your callback should not make assumptions about what thread it is being called on. |
Pull Filter
A pull filter gives an app the ability to validate documents being pulled, and skip ones that fail. This is an important security mechanism in a peer-to-peer topology with peers that are not fully trusted.
Pull replication filters are not a substitute for channels. Sync Gateway channels are designed to be scalable (documents are filtered on the server) whereas a pull replication filter is applied to a document once it has been downloaded. |
var url = new Uri("ws://localhost:4984/mydatabase");
var target = new URLEndpoint(url);
var config = new ReplicatorConfiguration(database, target);
config.PullFilter = (document, flags) => (1)
{
if (document.GetString("type") == "draft") {
return false;
}
return true;
};
// Dispose() later
var replicator = new Replicator(config);
replicator.Start();
1 | The callback should follow the semantics of a pure function. Otherwise, long running functions would slow down the replicator considerably. Furthermore, your callback should not make assumptions about what thread it is being called on. |
Losing access to a document (via the Sync Function) also triggers the pull replication filter. Filtering out such an event would retain the document locally. As a result, there would be a local copy of the document disjointed from the one that resides on Couchbase Server. Further updates to the document stored on Couchbase Server would not be received in pull replications and further local edits could be potentially pushed, which would result in 409 errors since access has been revoked. |
Handling Conflicts
Document conflicts can occur if multiple changes are made to the same version of a document by multiple peers in a distributed system. For Couchbase Mobile, this can be a Couchbase Lite or Sync Gateway database instance.
Such conflicts can occur after either of the following events:
-
A replication saves a document change — in which case the change with the most-revisions wins (unless one change is a delete). See the example Case 1: Conflicts when a replication is in progress
-
An application saves a document change directly to a database instance — in which case, last write wins, unless one change is a delete — see Case 2: Conflicts when saving a document
Deletes always win. So, in either of the above cases, if one of the changes was a Delete then that change wins. |
The following sections discuss each scenario in more detail.
Dive deeper …
Read more about Document Conflicts and Automatic Conflict Resolution in Couchbase Mobile on The Couchbase Blog.
|
Case 1: Conflicts when a replication is in progress
There’s no practical way to prevent a conflict when incompatible changes to a document are be made in multiple instances of an app. The conflict is realized only when replication propagates the incompatible changes to each other.
-
Molly uses her device to create DocumentA.
-
Replication syncs DocumentA to Naomi’s device.
-
Molly uses her device to apply ChangeX to DocumentA.
-
Naomi uses her device to make a different change, ChangeY, to DocumentA.
-
Replication syncs ChangeY to Molly’s device.
This device already has ChangeX putting the local document in conflict.
-
Replication syncs ChangeX to Naomi’s device.
This device already has ChangeY and now Naomi’s local document is in conflict.
Automatic Conflict Resolution
These rules apply only to conflicts arising from replication. |
Couchbase Lite uses the following rules to handle conflicts such as those described in A typical replication conflict scenario:
-
If one of the changes is a deletion:
A deleted document (that is, a tombstone) always wins over a document update.
-
If both changes are document changes:
The change with the most revisions will win.
Since each change creates a revision with an ID prefixed by an incremented version number, the winner is the change with the highest version number.
The result is saved internally by the Couchbase Lite replicator. Those rules describe the internal behavior of the replicator. For additional control over the handling of conflicts, including when a replication is in progress, see Custom Conflict Resolution.
Custom Conflict Resolution
Starting in Couchbase Lite 2.6, application developers who want more control over how document conflicts are handled can use custom logic to select the winner between conflicting revisions of a document.
If a custom conflict resolver is not provided, the system will automatically resolve conflicts as discussed in Automatic Conflict Resolution, and as a consequence there will be no conflicting revisions in the database.
While this is true of any user defined functions, app developers must be strongly cautioned against writing sub-optimal custom conflict handlers that are time consuming and could slow down the client’s save operations. |
To implement custom conflict resolution during replication, you must implement the following steps.
Conflict Resolver
Apps have the following strategies for resolving conflicts:
-
Local Wins: The current revision in the database wins.
-
Remote Wins: The revision pulled from the remote endpoint through replication wins.
-
Merge: Merge the content bodies of the conflicting revisions.
class LocalWinConflictResolver : IConflictResolver
{
Document Resolve(Conflict conflict)
{
return conflict.LocalDocument;
}
}
class RemoteWinConflictResolver : IConflictResolver
{
public Document Resolve(Conflict conflict)
{
return conflict.RemoteDocument;
}
}
class MergeConflictResolver : IConflictResolver
{
public Document Resolve(Conflict conflict)
{
var localDict = conflict.LocalDocument.ToDictionary();
var remoteDict = conflict.RemoteDocument.ToDictionary();
var result = localDict.Concat(remoteDict)
.GroupBy(kv => kv.Key)
.ToDictionary(g => g.Key, g => g.First().Value);
return new MutableDocument(conflict.DocumentID, result);
}
}
When a null document is returned by the resolver, the conflict will be resolved as a document deletion.
Important Guidelines and Best Practices
There are some important points to be noted:
-
If you have multiple replicators, it is recommended that instead of distinct resolvers, you should use a unified conflict resolver across all replicators. Failure to do so could potentially lead to data loss under exception cases or if the app is terminated (by the user or an app crash) while there are pending conflicts.
-
If the document ID of the document returned by the resolver does not correspond to the document that is in conflict then the replicator will log a warning message.
Developers are encouraged to review the warnings and fix the resolver to return a valid document ID. -
If a document from a different database is returned, the replicator will treat it as an error. A document replication event will be posted with an error and an error message will be logged.
Apps are encouraged to observe such errors and take appropriate measures to fix the resolver function. -
When the replicator is stopped, the system will attempt to resolve outstanding and pending conflicts before stopping. Hence apps should expect to see some delay when attempting to stop the replicator depending on the number of outstanding documents in the replication queue and the complexity of the resolver function.
-
If there is an exception thrown in the
resolve()
method, the exception will be caught and handled:-
The conflict to resolve will be skipped. The pending conflicted documents will be resolved when the replicator is restarted.
-
The exception will be reported in the warning logs.
-
The exception will be reported in the document replication event.
While the system will handle exceptions in the manner specified above, it is strongly encouraged for the resolver function to catch exceptions and handle them in a way appropriate to their needs.
-
Configure the Replicator
The implemented custom conflict resolver can be registered on the replicator configuration object.
The default value of the conflictResolver is null
.
When the value is null
, the default conflict resolution will be applied.
var target = new URLEndpoint(new Uri("ws://localhost:4984/mydatabase"));
var replConfig = new ReplicatorConfiguration(database, target);
replConfig.ConflictResolver = new LocalWinConflictResolver();
var replicator = new Replicator(replConfig);
replicator.Start();
Case 2: Conflicts when saving a document
When updating a document, you need to consider the possibility of update conflicts. Update conflicts can occur when you try to update a document that’s been updated since you read it. Here’s a typical sequence of events that would create an update conflict:
-
Your code reads the document’s current properties, and constructs a modified copy to save.
-
Another thread (perhaps the replicator) updates the document, creating a new revision with different properties.
-
Your code updates the document with its modified properties, for example using
db.save( mutableDocumentName)
.
Automatic Conflict Resolution
In Couchbase Lite 2.0, by default, the conflict is automatically resolved and only one document update is stored in the database. The Last-Write-Win (LWW) algorithm is used to pick the winning update. So in effect, the changes from step 2 would be overwritten and lost.
If the probability of update conflicts is high in your app and you wish to avoid the possibility of overwritten data, the save
and delete
APIs provide additional method signatures with concurrency control:
-
save(document: MutableDocument, concurrencyControl: ConcurrencyControl)
: attempts to save the document with a concurrency control. The concurrency control parameter has two possible values:-
lastWriteWins
(default): The last operation wins if there is a conflict. -
failOnConflict
: The operation will fail if there is a conflict. In this case, the app can detect the error that is being thrown, and handle it by re-reading the document, making the necessary conflict resolution, then trying again.
-
Similarly to the save operation, the delete operation also has two method signatures to specify how to handle a possible conflict:
-
delete(document: Document)
: The last write will win if there is a conflict. -
delete(document: Document, concurrencyControl: ConcurrencyControl)
: attempts to delete the document with a concurrency control. The concurrency control parameter has two possible values:-
lastWriteWins
(default): The last operation wins if there is a conflict. -
failOnConflict
: The operation will fail if there is a conflict. In this case, the app can detect the error that is being thrown, and handle it by re-reading the document, making the necessary conflict resolution, then trying again.
-
Custom Conflict Resolution
Starting in Couchbase Lite 2.6, we allow developers to hook a conflict handler when saving a document so that developers can easily handle the conflict in a single save method call.
To implement custom conflict resolution when saving a document, apps must call the save()
method with a conflict handler.
The following code snippet shows an example of merging properties from the existing document (current
) into the one being saved (new
).
In the event of conflicting keys, it will pick the key value from new
.
using (var document = database.GetDocument("xyz"))
using (var mutableDocument = document.ToMutable()) {
mutableDocument.SetString("name", "apples");
database.Save(mutableDocument, (updated, current) =>
{
var currentDict = current.ToDictionary();
var newDict = updated.ToDictionary();
var result = newDict.Concat(currentDict)
.GroupBy(kv => kv.Key)
.ToDictionary(g => g.Key, g => g.First().Value);
updated.SetData(result);
return true;
});
}
Important points to be noted:
-
Within the conflict handler, you can modify the
document
parameter which is the same instance ofDocument
that is passed to thesave()
method. So in effect, you will be directly modifying the document that is being saved. -
When handling is done, the method must return
true
. -
If the handler could not resolve the conflict, it can return
false
. In this case, the save method will cancel the save operation and returnfalse
the same way as using thesave()
method with thefailOnConflict
concurrency control. -
If there is an exception thrown in the
handle()
method, the exception will be caught and rethrown in thesave()
method.
Database Replicas
Database replicas is available in the Enterprise Edition only (https://www.couchbase.com/downloads). Starting in Couchbase Lite 2.0, replication between two local databases is now supported. It allows a Couchbase Lite replicator to store data on secondary storage. It would be especially useful in scenarios where a user’s device is damaged and the data needs to be moved to a different device. Note that the code below won’t compile if you’re running the Community Edition of Couchbase Lite.
var targetDatabase = new DatabaseEndpoint(database2);
var config = new ReplicatorConfiguration(db, targetDatabase)
{
ReplicatorType = ReplicatorType.Push
};
var replicator = new Replicator(config);
replicator.Start();
Certificate Pinning
Couchbase Lite supports certificate pinning. Certificate pinning is a technique that can be used by applications to "pin" a host to its certificate. The certificate is typically delivered to the client by an out-of-band channel and bundled with the client. In this case, Couchbase Lite uses this embedded certificate to verify the trustworthiness of the server and no longer needs to rely on a trusted third party for that (commonly referred to as the Certificate Authority).
The following steps describe how to configure certificate pinning between Couchbase Lite and Sync Gateway.
-
Create your own self-signed certificate with the
openssl
command. After completing this step, you should have 3 files:cert.pem
,cert.cer
andprivkey.pem
. -
Configure Sync Gateway with the
cert.pem
andprivkey.pem
files. After completing this step, Sync Gateway is reachable overhttps
/wss
. -
On the Couchbase Lite side, the replication must point to a URL with the
wss
scheme and configured with thecert.cer
file created in step 1.// Note: `GetCertificate` is a fake method. This would be the platform-specific method // to find and load the certificate as an instance of `X509Certificate2`. // For .NET Core / .NET Framework this can be loaded from the filesystem path. // For UWP, from the assets directory. // For iOS, from the main bundle. // For Android, from the assets directory. var certificate = GetCertificate("cert.cer"); var config = new ReplicatorConfiguration(db, target) { PinnedServerCertificate = certificate };
This example loads the certificate from the application sandbox, then converts it to the appropriate type to configure the replication object.
-
Build and run your app. The replication should now run successfully over https with certificate pinning.
Troubleshooting
If Sync Gateway is configured with a self signed certificate but your app points to a ws
scheme instead of wss
you will encounter an error with status code 11006
.
CouchbaseLite Replicator ERROR: {Repl#2} Got LiteCore error: WebSocket error 1006 "connection closed abnormally"
If Sync Gateway is configured with a self signed certificate, and your app points to a wss
scheme but the replicator configuration isn’t using the certificate you will encounter an error with status code 5011
.
CouchbaseLite Replicator ERROR: {Repl#2} Got LiteCore error: Network error 11 "server TLS certificate is self-signed or has unknown root cert"
Peer-to-Peer Sync
Enterprise Edition only
Peer-to-peer sync is an Enterprise Edition feature.
You must purchase the Enterprise License which includes official Couchbase Support to use it in production (also see the FAQ).
|
Peer-to-peer sync allows devices running Couchbase Lite to directly sync data with each other. As part of this, Couchbase Lite is responsible for storing the data and keeping track of the data exchange, but isn’t responsible for the data transfer itself. Sending and receiving data must be handled by the platform APIs or a third party framework. In this section, we will refer to these third party frameworks as communication frameworks.
On .NET, the DeviceWatcher, DnssdServiceInstance and StreamSocketListener APIs would be a good choice for the Communication Framework. Those APIs handle sending and receiving messages over WiFi or Bluetooth.
Thus, to enable peer-to-peer sync with Couchbase Lite, the application must use the Communication Framework with Couchbase Lite. The following sections describe a typical peer-to-peer workflow. Where applicable, we discuss how to integrate Couchbase Lite into the workflow.
In Couchbase Lite, a peer can take on one of these two roles:
- Active Peer
-
The peer that initializes the connection and replication (i.e the "client" side).
- Passive Peer
-
The passive side reacts to things that it receives but does not initiate any communication on its own (i.e. the "server" side).
Peer Discovery
Peer discovery is the first step. The communication framework will generally include a peer discovery API for devices to advertise themselves on the network and to browse for other peers.

Passive Peer
In addition to initializing the database, the passive peer must initialize the MessageEndpointListener
.
The MessageEndpointListener
acts as as a listener for incoming connections.
var database = new Database("mydb");
var config = new MessageEndpointListenerConfiguration(database, ProtocolType.MessageStream);
_messageEndpointListener = new MessageEndpointListener(config);
Peer Selection and Connection Setup
Once a peer device is found, it is the application code’s responsibility to decide whether it should establish a connection with that peer. This step includes inviting a peer to a session and peer authentication.
This is handled by the Communication Framework.

Once the remote peer has been authenticated, the next step is to connect with that peer and initialize the Message Endpoint API.
Replication Setup

Active Peer
When the connection is established, the active peer must instantiate a MessageEndpoint
object corresponding to the remote peer.
var database = new Database("dbname");
// The delegate must implement the `IMessageEndpointDelegate` protocol.
var messageEndpointTarget = new MessageEndpoint(uid: "UID:123", target: "",
protocolType: ProtocolType.MessageStream, delegateObject: this);
The MessageEndpoint
initializer takes the following arguments.
-
uid
: a unique ID that represents the remote active peer. -
target
: This represents the remote passive peer and could be any suitable representation of the remote peer. It could be an Id, URL etc. If using the MultiPeerConnectivity Framework, this could be the MCPeerID. -
protocolType
: specifies the kind of transport you intend to implement. There are two options.-
The default (
MessageStream
) means that you want to "send a series of messages", or in other words the Communication Framework will control the formatting of messages so that there are clear boundaries between messages. -
The alternative (
ByteStream
) means that you just want to send raw bytes over the stream and Couchbase should format for you to ensure that messages get delivered in full.Typically, the Communication Framework will handle message assembly and disassembly so you would use the
MessageType
option in most cases.
-
-
delegate
: the delegate that will implement theMessageEndpointDelegate
protocol, which is a factory forMessageEndpointConnection
.
Then, a Replicator
is instantiated with the initialized MessageEndpoint
as the target.
var config = new ReplicatorConfiguration(database, messageEndpointTarget);
// Create the replicator object
var replicator = new Replicator(config);
// Start the replicator
replicator.Start();
Next, Couchbase Lite will call back the application code through the MessageEndpointDelegate.createConnection
interface method.
When the application receives the callback, it must create an instance of MessageEndpointConnection
and return it.
/* implementation of MessageEndpointDelegate */
public IMessageEndpointConnection CreateConnection(MessageEndpoint endpoint)
{
var connection = new ActivePeerConnection(); /* implements IMessageEndpointConnection */
return connection;
}
Next, Couchbase Lite will call back the application code through the MessageEndpointConnection.open
method.
/* implementation of IMessageEndpointConnection */
public async Task Open(IReplicatorConnection connection)
{
_replicatorConnection = connection;
// await socket.Open(), etc
// throw MessagingException if something goes wrong
}
The connection argument is then set on an instance variable.
The application code must keep track of every ReplicatorConnection
associated with every MessageEndpointConnection
.
The MessageError
argument in the completion block is used to specify if the error is recoverable or not.
If it is a recoverable error, the replicator will kick off a retry process which will result to creating a new MessageEndpointConnection
instance.
Passive Peer
The first step after connection establishment on the passive peer is to initialize a new MessageEndpointConnection
and pass it to the listener.
This tells the listener to accept incoming data from that peer.
var connection = new PassivePeerConnection(); /* implements IMessageEndpointConnection */
_messageEndpointListener?.Accept(connection);
messageEndpointListener
is the instance of the MessageEndpointListener
that was created in the first step (Peer Discovery)
Couchbase Lite will then call back the application code through the MessageEndpointConnection.open
method.
/* implementation of IMessageEndpointConnection */
public Task Open(IReplicatorConnection connection)
{
_replicatorConnection = connection;
// socket should already be open on the passive side
return Task.FromResult(true);
}
The connection
argument is then set on an instance variable.
The application code must keep track of every ReplicatorConnection
associated with every MessageEndpointConnection
.
At this point, the connection is established and both peers are ready to exchange data.
Push/Pull Replication
Typically, an application needs to send data and receive data. Directionality of the replication could be any of the following.
-
Push only: The data is pushed from the local database to the remote database.
-
Pull only: The data is pulled from the remote database to the local database.
-
Push and Pull: The data is exchanged both ways.
Usually, the remote is a Sync Gateway database which is identified through a URL. In the context of peer-to-peer syncing, the remote is another Couchbase Lite database.

The replication lifecycle is handled through the MessageEndpointConnection
.
Active Peer
When Couchbase Lite calls back the application code through the MessageEndpointConnection.send
method, you should send that data to the other peer using the communication framework.
/* implementation of IMessageEndpointConnection */
public async Task Send(Message message)
{
var data = message.ToByteArray();
// await Socket.Send(), etc
// throw MessagingException if something goes wrong
}
Once the data is sent, call the completion block to acknowledge the completion.
You can use the MessageError
in the completion block to specify if the error is recoverable or not.
If it is a recoverable error, the replicator will kick off a retry process which will result to creating a new MessageEndpointConnection
.
When data is received from the passive peer via the Communication Framework, you call the ReplicatorConnection.receive
method.
var message = Message.FromBytes(data);
_replicatorConnection?.Receive(message);
The replication connection’s receive
method is called which then processes the data in order to persist it to the local database.
Passive Peer
As in the case of the active peer, the passive peer must implement the MessageEndpointConnection.send
method to send data to the other peer.
/* implementation of IMessageEndpointConnection */
public async Task Send(Message message)
{
var data = message.ToByteArray();
// await Socket.Send(), etc
// throw MessagingException if something goes wrong
}
Once the data is sent, call the completion block to acknowledge the completion.
You can use the MessageError
in the completion block to specify if the error is recoverable or not.
If it is a recoverable error, the replicator will kick off a retry process which will result to creating a new MessageEndpointConnection
.
When data is received from the active peer via the Communication Framework, you call the ReplicatorConnection.receive
method.
var message = Message.FromBytes(data);
_replicatorConnection?.Receive(message);
Connection Teardown
When a peer disconnects from a peer-to-peer network, all connected peers are notified. The disconnect notification is a good opportunity to close and remove a replication connection. The steps to teardown the connection are slightly different depending on whether it is the active or passive peer that disconnects first. We will cover each case below.
Initiated by Active Peer

Active Peer
When an active peer disconnects, it must call the ReplicatorConnection.close
method.
_replicatorConnection?.Close(null);
Then, Couchbase Lite will call back your code through the MessageEndpointConnection.close
to give the application a chance to disconnect with the Communication Framework.
/* implementation of IMessageEndpointConnection */
public async Task Close(Exception error)
{
// await socket.Close, etc (or do nothing if already closed)
// throw MessagingException if something goes wrong (though
// since it is "close" nothing special will happen)
}
Passive Peer
When the passive peer receives the corresponding disconnect notification from the Communication Framework, it must call the ReplicatorConnection.close
method.
_replicatorConnection?.Close(null);
Then, Couchbase Lite will call back your code through the MessageEndpointConnection.close
to give the application a chance to disconnect with the Communication Framework.
/* implementation of IMessageEndpointConnection */
public async Task Close(Exception error)
{
// await socket.Close, etc (or do nothing if already closed)
// throw MessagingException if something goes wrong (though
// since it is "close" nothing special will happen)
}
Initiated by Passive Peer

Passive Peer
When the passive disconnects, it must class the MessageEndpointListener.closeAll
method.
_messageEndpointListener?.CloseAll();
Then, Couchbase Lite will call back your code through the MessageEndpointConnection.close
to give the application a chance to disconnect with the Communication Framework.
/* implementation of IMessageEndpointConnection */
public async Task Close(Exception error)
{
// await socket.Close, etc (or do nothing if already closed)
// throw MessagingException if something goes wrong (though
// since it is "close" nothing special will happen)
}
Active Peer
When the active peer receives the corresponding disconnect notification from the Communication Framework, it must call the ReplicatorConnection.close
method.
_replicatorConnection?.Close(null);
Then, Couchbase Lite will call back your code through the MessageEndpointConnection.close
to give the application a chance to disconnect with the Communication Framework.
/* implementation of IMessageEndpointConnection */
public async Task Close(Exception error)
{
// await socket.Close, etc (or do nothing if already closed)
// throw MessagingException if something goes wrong (though
// since it is "close" nothing special will happen)
}
Thread Safety
The Couchbase Lite API is thread safe except for calls to mutable objects: MutableDocument
, MutableDictionary
and MutableArray
.
Release Notes
2.7.1
Note: Support notices remain in force for this maintenance release — see Support Notices below.
Fixed at this Release
This maintenance release fixes the following issues:
-
CBL-849 Returning WebProxy null when WINHTTP_PROXY_INFO out as invalid value if that ever happens
-
CBL-789 Crash when accessing
connection→name()
-
CBL-701 Pending Document IDs not working correctly
-
CBL-698 Wrong query evaluation using expression values instead of expression properties
-
CBL-657 LIKE and CONTAINS are much slower in 2.7
-
CBL-580 Native memory leak when save document repeatedly
-
CBL-579 SDK fails for opening Database files over 2GB
2.7.0
New Features
New at this release is the Java Platform, which enables development of Java apps on any platform that supports the JVM model
Known issues
Fixed at this Release
2.6.0
New Features
-
Custom Conflict Resolution
Enhancements
-
Expose the Document Revision ID: the
revisionID
property on aDocument
instance now returns the current revision identifier.
OS API Support
Support for Android API 19, API 20 and API 21 on Xamarin is deprecated in this release. Support will be removed within two (non-maintenance) releases following the deprecation announcement.
Fixed at this Release
-
CBL-171 CBL replication never finishes for replication filtering when doc count is 1000
-
CBL-136 Replication is not replicating all updates with delta-sync enabled
-
CBL-110 delta sync on top of a deletion caused data discrepancy between SG and CBL
-
CBL-106 Replicator C++ exception: "properties excessively large"
-
CBL-104 Replicating two documents with identical blob could cause POSIX error
-
CBL-86 Race condition on SharedKeys causes silent error
-
CBL-47 Intermittent SQLITE_MISUSE error during normal testing
-
CBL-45 LiteCore time out issues stray busy callback
-
CBL-38 null pointer dereference
-
CBL-2 [sg-ssl enabled] cbl replicator does not resume after SG restart
Known issues
2.5.0
New Features
-
Delta Sync
-
Replication Filters
-
Continuous Logging
-
Predictive Query
Enhancements
Fixed at this Release
Known issues
-
#1140 [xamarin-ios][sg-ssl enabled] cbl replicator does not resume after SG restart
2.1.1
-
#1058 Xamarin iOS missing PInvokeCallback attributes on listener callbacks
2.1
-
#1000 2.0. Xamarin. Replicator fails to get out of Connecting State when connecting to unreachable host
-
#1010 [Xamarin][db023] continuous synchronization stopping after a while
-
#1011 Xamarin Android cannot recover from loss of connection to LAN address
-
#1012 Reusing the same collation in a query will cause all of them to be affected by the last modification
-
#1020 Closing/Disposing database throws Exception because of unfinished replicators
-
#1048 Couchbase Lite parsing nested value as UInt64 then throwing exception on update.
-
#1051 Heap corruption/Access Violation with unreachable replication endpoint on Windows x86
-
#1040 HTTP Proxy Support
2.0.3
-
Support for Log redaction of sensitive information when logging is enabled.
2.0.2
-
Support for Log redaction of sensitive information when logging is enabled.
2.0 release notes
-
#884 Replication with DNS endpoint failing on (at least) Mac
-
#894 Crash on iOS Simulator 9.x on Query.Run() execution
-
#957 SG doesn’t have any docs in its cache after replication
-
#966 Incorrect callback type(?) leads to AOT compilation failure for Xamarin
-
#994 2.0db023 SetBinaryLogDirectory(string) path ignored, still written to AppContext.BaseDirectory