A data type is different than a standard scalar type by declaring merge behaviors, operations, and the spectrum of scalar types accessible for individual fields:

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:

TypeREGISTERMAPATTACHMENTPN_COUNTER
CRDT typeLast-write-winsAdd-winsLast-write-winsPositive-negative
PayloadAnyObjectBinary fileDouble

Data Type Operations

Data types have different operations available.

REGISTER Operations

A Register supports scalar types, including primitive types, such as string 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
field1 = 1

MAP Operations

The MAP 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.

field1.sub1.s_sub1 = 1 

ATTACHMENT Operations

To set the last-write-wins ATTACHMENT data type, provide an ATTACHMENT object:

DQL
field1 = :attachment

Read more about attachments and large binary files.

Counter Operations

Counters are available in 4.11 and later. Read more

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

You can first create a placeholder for the counter in a document by using the INSERT statement to insert a double value of 0.0:

let product = [
  "_id": "123",
  "in_stock": 0.0,
  "updatedBy": "abc123"
]

await ditto.store.execute(
  query: """
    INSERT INTO COLLECTION products 
    INITIAL DOCUMENTS (:product)
    """,
  arguments: [ "product": product ])

To update a counter, use the APPLY keyword followed by the field name and then the PN_INCREMENT keyword followed by the value. To decrement a counter, use a negative value.

UPDATE products
APPLY in_stock PN_INCREMENT BY 1.0
SET updatedBy = 'def456'
WHERE _id = '123'

You can then retrieve the latest value of a counter using a SELECT statement:

SELECT * FROM products 
WHERE _id = '123'

Default Value Operation

Some data types can be set to a default value type using the default() functional operator.

  • REGISTERNULL
  • AWMAP → Empty Map {}
  • ATTACHMENTNOT SUPPORTED
  • PN_COUNTERNOT SUPPORTED
field1 = default()

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

A REGISTER 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

With 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.

UPDATE COLLECTION orders (updatedAt REGISTER)
SET updatedAt = {
	"datetime": "2025-02-28", 
	"authorId": "67c0faa40054d13a000c614a"
}
WHERE _id = 'my-id'

SELECT * FROM COLLECTION orders (updatedAt REGISTER) WHERE _id = 'my-id'

The results of the SELECT statement above would be:

{
  "_id": "my-id"
  "regionId": "01234",
  "items": {
    "shake": {...},
    "fries": {...},
    "burger": {...}
  },
  "updatedAt": {
	  "datetime": "2025-02-28",
	  "updatedBy": "67c0faa40054d13a000c614a" 
  }
}

If you need to remove a register map, you need to use the 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.

UPDATE COLLECTION orders (updatedAt REGISTER)
UNSET updatedAt 
WHERE _id = 'my-id'

You will receive an error if you attempt to SET or UNSET a nested key of a register using dot notation.

UPDATE COLLECTION orders (updatedAt REGISTER)
SET updatedAt.datetime = "2025-04-28"
WHERE _id = 'my-id'
-- Unsupported DML operation on REGISTER field "updatedAt"

Non-Registers

In 4.11+ and DQL_STRICT_MODE=false, collection definitions for non-registers are no longer required.

Read more

With DQL_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
SELECT *
FROM COLLECTION your_collection_name (field1 MAP, field2 ATTACHMENT)
WHERE field1.rating > 100

In this syntax:

DQL
... COLLECTION your_collection_name (field1 data_type, field2 data_type, ...) ...
  • COLLECTION declares that the collection has a type definition
  • your_collection_name is 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 as REGISTER , MAP, or ATTACHMENT

SELECT with Definition

DQL
SELECT *
FROM COLLECTION your_collection_name (field1 MAP, field2 ATTACHMENT)

UPDATE with Definition

DQL
UPDATE COLLECTION your_collection_name (field1 MAP, field2 ATTACHMENT)
SET ...

INSERT with Definition

DQL
INSERT INTO COLLECTION your_collection_name (field1 MAP, field2 ATTACHMENT)
VALUES (...)

MAP Type Specifics

The 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
... COLLECTION your_collection_name (map_name MAP(sub1 ATTACHMENT, sub2 MAP))

Single MAP

The syntax for a single MAP with all other fields type REGISTER:

DQL
... COLLECTION your_collection_name (field1 MAP)

Single ATTACHMENT

The syntax for a single ATTACHMENT with all other fields type REGISTER:

DQL
... COLLECTION your_collection_name (field1 ATTACHMENT)

MAP and ATTACHMENT

The syntax for a single MAP and a single ATTACHMENT with all other fields type REGISTER:

DQL
... COLLECTION your_collection_name (field1 MAP, field2 ATTACHMENT)

Deeply Embedded MAP

Disable Strict Mode

In 4.11+ and DQL_STRICT_MODE=false, collection definitions are no longer required.

Read more

The syntax for a document hierarchy of depth two — a single MAP nested with another MAP — with all other fields type REGISTER:

DQL
... COLLECTION your_collection_name (field1 MAP(sub1 MAP))

The syntax for a document hierarchy of depth four with all other fields type REGISTER:

DQL
... COLLECTION your_collection_name (field1 MAP(sub1 MAP(s_sub1 MAP(s_s_sub1 MAP))))