Documents
Overview
Document Structure
In Couchbase Lite the term document refers to an entry in the database; a record, or row in a table if you like.
Each document has an ID (primary key in other databases) by which it can be located. This ID 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. The document also has a value which contains the actual application data. This value is stored as a dictionary collection of key-value (k-v) pairs where the values themselves may comprise different types of data such as numbers, strings, arrays or even nested objects.
Data Encoding
The document body is stored in an internal, efficient, binary form (Fleece). This internal form is easily converted into a manageable native dictionary format for manipulation in applications.
Fleece data is stored in the smallest format that will hold the value, whilst maintaining the integrity of the value.
Fleece Data Encoding
When working with Dart, the Fleece encoding cycle can result in the Dart type
information being lost. Specifically, DateTime
s are stored as
strings. So, care should be taken when storing and recovering data in a
document - or converting that document to JSON and back - using non-explicit
functions such as DictionaryInterface.toPlainMap
.
Always use explicit creation of the expected type, whenever the type of result is not itself explicit. For example:
final doc = (await collection.document(documentId))!;
final map = doc.toPlainMap();
final createdAtFromTypedGetter = doc.date('createdAt');
final createdAtFromMap = DateTime.parse(map['createdAt']! as String);
Data Types
The Document
class 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.
So your document content may well comprise one or more supporting data types such as:
- Boolean
- Date
- Double
- Float
- Integer
- String
In addition to these basic data types Couchbase Lite provides for the following:
Dictionary
— Represents a read-only key-value pair collection.MutableDictionary
— Represents a writeable key-value pair collection.Array
— Represents a readonly ordered collection of objects.MutableArray
— Represents a writeable collection of objects.Blob
— Represents an arbitrary piece of binary data.
JSON
Couchbase Lite also provides for the direct handling of JSON data implemented in
most cases by the provision of a toJson()
method on appropriate API classes
(for example, on MutableDocument
, Dictionary
, Array
and
Blob
) — see Working with JSON Data.
Constructing a Document
An individual document often represents a single instance of an object in application code. A document might be considered equivalent to a row in a relational table; with each of the document's attributes being equivalent to a column.
Documents can contain nested structures. This allows developers to express many-to-many relationships without requiring a reference or junction table; and is naturally expressive of hierarchical data.
Most apps will work with one or more documents, persisting them to a local database and optionally syncing them, either centrally or to the cloud.
In this section we provide an example of how you might create a hotel document, which provides basic contact details and price data.
Data Model
hotel: {
type: string (value = `hotel`)
name: string
address: dictionary {
street: string
city: string
state: string
country: string
code: string
}
phones: array
rate: float
}
Open a Database
First we open your database. If the database does not already exist, Couchbase Lite will create it for us.
Couchbase documents are assigned to a Collection. All the CRUD examples in this document operate on a collection object (here, the Default Collection).
// Get the database (and create it if it doesn't exist).
final database = await Database.openAsync('hoteldb', dbConfig);
final collection = await database.defaultCollection;
See: Databases for more information.
Create a Document
Now we create a new document to hold our application's data.
Because we will be adding data to the document we must use its mutable form.
// Create your new document.
final mutableDoc = MutableDocument.withId('hotel::1');
For more on using Documents, see: Document Initializers and Mutability.
Create a Dictionary
Here we create a dictionary (address
). Because we want to add values into the
dictionary, we must create it in mutable form.
When the dictionary is retrieved, each element's value is directly accessible via its own key.
// Create and populate mutable dictionary.
// Create a new mutable dictionary and populate some keys/values.
final address = MutableDictionary()
..setString('1 Main Street', key:'street')
..setString('San Francisco', key:'city')
..setString('CA.', key:'state')
..setString('USA', key:'country')
..setString('90210', key:'code');
For more on using Dictionaries see: Using Dictionaries.
Create an Array
Since our hotel may have multiple lines we provide an array (phones
) to hold
contact numbers. Again, because we want to add values into the array, we create
it in mutable form.
// Create and populate mutable array.
final phones = MutableArray()
..addString('555-555-0000')
..addString('555-555-0001');
For more on using Arrays see: Using Arrays.
Populate a Document
Here we add our data to the mutable document we created earlier. Each data item is stored as a key-value pair.
// Initialize and populate the document
mutableDoc
// <1> Add document type to document properties.
..setString('hotel', key: 'type')
// <2> Add hotel name string to document properties.
..setString('Hotel Dart Mo', key: 'name')
// <3> Add float to document properties.
..setFloat(121.75, key: 'room_rate')
// <4> Add dictionary to document's properties.
..setDictionary(address, key: 'address')
// <5> Add array to document's properties.
..setString(phones, key: 'phones');
- Add hotel name (string).
- Add average room rate (float).
- Add document type (string).
Couchbase recommend using atype
attribute to define each logical document type. - Add address (dictionary).
Theaddress
dictionary is added to the document and stored under the keyaddress
. We will use this to retrieve it when needed. - Add phone numbers (array).
The phones arrary is added to the document and stored under the keyphones
. We will use this to retrieve it when needed.
Save a Document
With the document now populated, we can persist to our Couchbase Lite database.
// Save the document to the database.
await collection.saveDocument(mutableDoc);
Close the Database
With our document saved, we can now close our Couchbase Lite database.
await database.close();
Working with Data
Checking a Document's Properties
To check whether a given property exists in the document, you should use the
DictionaryInterface.contains
method (Document
implements
DictionaryInterface
).
If the property doesn't exist, the call will return the default for that that
method (0
for DictionaryInterface.integer
, 0.0
for
DictionaryInterface.float
, etc.).
Care should be taken when storing and recovering data in a document or converting that document to JSON and back.
Data encoding (Fleece) can result in Long values being converted to Float instead of Double. Interpreting data as boolean can also give inconsistent results.
Date accessors
As a convenience Couchbase Lite offers Date 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.
Using Dictionaries
API References
Using Arrays
API References
Using Blobs
For more on working with blobs — see Blobs.
Document 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.withId
constructor can be used to create a new
document with a specific document ID.
The Database.document
method can be used to get a document. If it doesn't
exist in the database, it will return null
. This method can be used to check
if a document with a given ID already exists in the database.
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.
Any user change to the value of reserved keys (_id
, _rev
or _deleted
) will
be detected when a document is saved and will result in an exception
(DatabaseErrorCode.corruptRevisionData
) — see also
Document Constraints.
Document Conversion
A Document can be converted to a plain dictionary type and-or to a JSON string. This can often be useful to pass the document contents as a plain object to another 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.
Example 8. Batch operations
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. Couchbase Mobile is a distributed system, and due to the way
replication works, there's no guarantee that Capella App Services or Sync
Gateway will receive your changes all at once.
Document change events
It is possible to register for document changes. The following example registers for changes to the document with ID user.john and prints the verified_account property when a change is detected.
Change Streams
Streams are a convenient alternative to listen for changes.
When multiple databases are involed, making sure that a stream is able to observe all changes requires waiting for the stream to be ready. See General Concepts - Change Streams for more information.
To stop listening to changes just cancel the subscription, like with any other stream.
Document Expiration
Document expiration allows users to set the expiration date for a document. When the document expires, it is purged from the database. The purge is not replicated to Capella App Services or Sync Gateway.
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.
_id
, _rev
and _sequence
are reserved keywords and must not be used as
top-level attributes — see Figure 13.
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
Working with JSON Data
The toJson
typed-accessor means you can easily work with JSON data, native and
Couchbase Lite objects.
Documents
Convert a Document
to a JSON string using the toJson
method — see
Example 14.
Dictionaries
Convert a Dictionary
to a JSON string using the toJson
method — see
Example 15.
Arrays
Convert an Array
to a JSON string using the toJson
method — see
Example 16.
Blobs
Convert a Blob
to JSON using the toJson
method — see Example 17.
You can also check whether a given plain Map
is a blob, or not, using
Blob.isBlob
— again, see Example 17.
The blob object must first be saved to the database (generating required
metadata) before you can use the toJson
method.
Query Results
Convert a Result
to JSON using its toJson
method — see Example 18.
JSON String format
If your query selects ALL then the JSON format will be:
{
<collection-name>: {
"key1": "value1",
"keyx": "valuex"
}
}
If your query selects a sub-set of available properties then the JSON format will be:
{
"key1": "value1",
"keyx": "valuex"
}