Data Types
In DQL, you’ll use three data types:REGISTER, MAP, and ATTACHMENT type. By
default, fields in a DQL statement are assigned the REGISTER type unless
otherwise specified by way of type definition.
Following are the key characteristics:
| Type | CRDT Type | Payload |
|---|---|---|
REGISTER | Last-write-wins | Any |
MAP | Add-wins | Object |
ATTACHMENT | Last-write-wins | Binary file |
COUNTER | Positive-negative & LWW on Set | Integer |
PN_COUNTER (legacy) | Positive-negative | Integer |
Data Type Operations
Data types have different operations available.REGISTER Operations
A Register supports scalar types, including primitive types, such asstring
and boolean, as well as a JSON blob, encapsulating multiple field‑value pairs
that function as a single object. The REGISTER can only be set to a specific
field.
For example:
DQL
MAP Operations
TheMAP type supports inserting and tombstoning of fields using the functional
operators. Inserting a field is an implicit operation performed by assigning a
value to a field or a child of the field.
ATTACHMENT Operations
To set the last-write-winsATTACHMENT data type, provide an ATTACHMENT object:
DQL
Counter (Settable Counter)
ACOUNTER is an enhanced version of the PN_COUNTER that combines positive-negative counter semantics with the ability to explicitly set the counter to a specific value. Like PN_COUNTER, it’s a CRDT type that can be incremented or decremented by any peer, but it also supports a RESTART operation that uses last-write-wins semantics.
The counter is an integer value that automatically resolves conflicting increments and decrements from different peers by tracking operations and composing them to provide a final value. When peers perform RESTART operations concurrently, the last write wins.
Counters are useful for tracking counts that multiple peers might update simultaneously, such as:
- Like/vote counts with the ability to reset
- Inventory counts that need periodic recalibration
- Session counts that can be initialized to specific values
- Metrics that require both incremental updates and explicit resets
Counter operations (
INCREMENT BY, RESTART WITH, RESTART) are specified using the APPLY clause, not the SET clause. The APPLY clause is specifically designed for CRDT operations on special field types like counters.Strict Mode and Type Declarations
The requirement to declare counter types in queries depends on your DQL_STRICT_MODE setting: WhenDQL_STRICT_MODE=true (default):
- You must declare counter types in
COLLECTIONdefinitions forINSERT,UPDATE, andSELECTstatements - Counter fields are only visible in queries when the type declaration is included
DQL_STRICT_MODE=false:
- Counter type declarations are not required for
SELECTandUPDATEstatements usingAPPLY - Counter type declarations are still required for
INSERTstatements - Counter fields are automatically visible in queries without type declarations
Creating Counters
There are two ways to create a counter field:- Using INSERT with type declaration: You can declare a field as a
COUNTERin theCOLLECTIONdefinition when inserting a document. This allows you to initialize the counter with a specific value (type declaration is required for INSERT regardless of strict mode):
DQL
- Using APPLY operations: Counter fields are automatically created when you first use
INCREMENTorRESTARToperations on them. This is useful when you want to create documents without counter fields initially:
Incrementing Counters
INCREMENT:
DQL
Setting Counter Values with RESTART
TheRESTART operation allows you to explicitly set a counter to a specific value or reset it to zero. This uses last-write-wins semantics, so if multiple peers restart a counter concurrently, the last write will win.
Set counter to a specific value:
DQL
DQL
Combining Counter Operations with Other Updates
You can combine counter operations with other field updates in a single statement:DQL
Querying Counter Values
You can retrieve the current value of a counter using aSELECT statement:
DQL
Counter vs PN_COUNTER
The key differences betweenCOUNTER and PN_COUNTER:
| Feature | COUNTER | PN_COUNTER |
|---|---|---|
| Increment/Decrement | ✓ | ✓ |
| Set to specific value | ✓ (RESTART WITH) | ✗ |
| Reset to zero | ✓ (RESTART) | ✗ |
| Conflict resolution | PN + LWW on RESTART | PN only |
| Available since | 4.13+ | 4.11+ |
COUNTER when you need the ability to explicitly set or reset counter values. Use PN_COUNTER only for backward compatibility with older Ditto versions.
PN Counter Operations (Legacy)
A counter is a special type of field that can be incremented or decremented. A counter is a double-precision floating-point number. In 4.11 and above, ditto offers PN_COUNTER, or positive-negative counter, which is a CRDT type that can be incremented or decremented by any peer. Counters automatically resolve conflicting increments and decrements from different peers by tracking the operations and composing them to provide a final value. Counters are useful for tracking counts that multiple peers might update simultaneously, such as:- Like/vote counts
- Number of views or interactions
PN_INCREMENT operation directly on the field. If the field doesn’t exist, it will be created as a counter with the increment value. If you need to create a document first, insert it without the counter field or with other fields only:
PN_INCREMENT keyword followed by the value. To decrement a counter, use a
negative value.
SELECT statement:
Default Value Operation
Some data types can be set to a default value type using thedefault()
functional operator.
REGISTER→NULLAWMAP→ Empty Map{}ATTACHMENT→ NOT SUPPORTEDPN_COUNTER→ NOT SUPPORTEDCOUNTER→ NOT SUPPORTED
Declaring Type Definition
With strict mode enabled, all fields are treated as a register by default. When enabled, every field in a document must match the collection definition exactly — including its CRDT type (e.g., map, register, counter). Disabling strict mode enables new functionality: when set to false, collection definitions are no longer required. SELECT queries will return and display all fields by default. Registers
AREGISTER is a data type in Ditto that stores a single scalar value and
uses last-write-wins merge strategy for handling conflicts.
Key characteristics of REGISTER:
- Stores primitive types (string, boolean) or JSON objects
- Last-write-wins conflict resolution ensures consistent values across peers
DQL_STRICT_MODE=false, if you want to force a JSON Object to use a
REGISTER data type instead of a MAP in DQL, it must be specified explicitly.
UNSET statement at the top level. Because a register map is treated the same as a scalar value (such as string, int), you operate on the entire object as a whole, similar to a JSON blob.
SET or UNSET a nested key of a register using dot notation.
Non-Registers
WithDQL_STRICT_MODE=true, REGISTER is the default type in DQL. That means
that you need to specify the type definition when overriding with type MAP, PN_COUNTER, or
ATTACHMENT within your query.
DQL type definitions describe the schema of the documents within a specific
collection — defining the field types within the collection and specifying the
assigned data types for each field.
To explicitly declare the type definition as non-REGISTER type, add a prefix
of COLLECTION and the suffix of (field1 data_type, field2 data_type, ...) to
list the fields within the collection and their associated data types:
DQL
DQL
COLLECTIONdeclares that the collection has a type definitionyour_collection_nameis the name of the collection from which you want to set a definition.(field1 data_type, field2 data_type, ...)specifies the data type of each field such asREGISTER,MAP, orATTACHMENT
DQL
DQL
DQL
MAP (Add-Wins Map) contains fields with their own data type. Data types for these fields are defined using parentheses following the MAP keyword. For example, MAP(sub1 data_type, sub2 data_type, ...):
DQL
MAP with all other fields type REGISTER:
DQL
ATTACHMENT with all other fields type REGISTER:
DQL
MAP and a single ATTACHMENT with all other fields type REGISTER:
DQL
MAP nested with another MAP — with all other fields type REGISTER:
DQL
REGISTER:
DQL