Skip to main content

Overview

This guide covers the essential changes needed to migrate your Ditto C++ app from v4 to v5. The main architectural shift is removing the deprecated identity-based constructors and requiring the four-phase configuration model (DittoConfig + Ditto::open) that was introduced in late v4. v5 also removes all legacy collection-based APIs in favor of DQL.
If you already use Ditto::open(DittoConfig) in v4, the initialization code is largely the same β€” focus on the removed APIs, renames, and DQL migration sections below.
Also required: v5 uses DQL (Ditto Query Language) for all data operations. See the DQL Migration Guide for query migration steps.

AI Agent Prompt

Use this prompt when working with an AI coding assistant to migrate your Ditto C++ app from v4 to v5.

Ditto Instance Initialization

v5 removes the deprecated Ditto() constructors and the Identity class hierarchy. Ditto::open(DittoConfig) β€” which was already available in v4 β€” is now the only way to create a Ditto instance. v5 separates initialization into four distinct phases for better clarity and control:
These changes provide:
  • Clear separation of connection, initialization, and authentication concerns
  • Builder pattern for flexible configuration
  • No workarounds needed (DQL strict mode, v3 sync disable)
  • Type-safe configuration with compile-time validation
Initialization Migration Steps
1

Replace Initialization

Replace the deprecated Ditto() constructor with Ditto::open(config).
Key changes:
  • Use DittoConfig::default_config() builder pattern instead of Identity
  • app_id β†’ database_id
  • Identity::OnlinePlayground β†’ DittoConfig::Connect::server(...)
  • Identity::OfflinePlayground β†’ DittoConfig::Connect::small_peers_only() (empty string = no encryption)
  • Identity::SharedKey β†’ DittoConfig::Connect::small_peers_only("your-shared-key")
  • Remove disable_sync_with_v3() calls
  • Convenience shortcuts available: set_server_connect(url) and set_small_peers_only_connect(key) can replace set_connect(DittoConfig::Connect::server(url))
Identity Type Mapping:
2

Update Authentication

Set authentication handler separately after initialization.
Key changes:
  • Authentication now uses set_expiration_handler lambda
  • Handler signature: std::function<void(Ditto &ditto, uint32_t sec_remaining)>
  • First param is Ditto & reference β€” access auth via ditto.get_auth()->
  • Handler called when auth required (sec_remaining == 0) or token expiring
  • Use Authenticator::get_development_provider() for playground tokens
  • Custom auth: pass provider name string directly to login()
  • get_auth() returns nullptr if not configured for authentication (e.g., small_peers_only without a server) β€” always null-check before accessing
Additional Authenticator APIs in v5:
  • Authenticator::get_status() β€” returns current AuthenticationStatus
  • Authenticator::observe_status(callback) β€” observe status changes
  • Authenticator::logout(cleanup) β€” log out and optionally run cleanup

Additional Changes

DQL Strict Mode Behavior Change

Breaking Change: v5 defaults to DQL_STRICT_MODE=false, which fundamentally changes how DQL queries behave.
  • v4 default: Objects treated as REGISTER (whole-object replacement)
  • v5 default: Objects treated as MAP (field-level merging)
This affects the behavior of all DQL SELECT, INSERT, and UPDATE operations.
Choose the appropriate migration path based on your current v4 configuration:
If you’re currently using the v4 default (DQL_STRICT_MODE=true), you must explicitly set strict mode to true in v5 before starting sync or executing any queries.
Failing to set strict mode will cause objects to merge at the field level instead of replacing entirely, which can result in unexpected data behavior and perceived data loss.
To migrate to DQL_STRICT_MODE=false (the new v5 default), contact Ditto Customer Support for guidance.
If you explicitly set DQL_STRICT_MODE=false in v4, no changes are required.v5 uses DQL_STRICT_MODE=false as the default, so your existing DQL queries will behave identically. You can upgrade freely.Optional: You can remove the explicit ALTER SYSTEM SET DQL_STRICT_MODE = false statement in v5 since this is now the default behavior.
The Legacy Query Builder has been removed in v5. All queries must be converted to DQL before upgrading.Good news: Legacy Query Builder functionality has 1:1 support with DQL_STRICT_MODE=false (the v5 default), making migrations straightforward.Migration Steps:
  1. In v4: Set DQL_STRICT_MODE=false after initialization:
  2. Convert all queries from Legacy Query Builder to DQL See the C++ Legacy→DQL Migration Guide for detailed conversion examples.
  3. Upgrade to v5 No DQL configuration changes requiredβ€”v5 defaults to DQL_STRICT_MODE=false.
For additional guidance or questions, contact Ditto Customer Support.

Default Persistence Directory

v5 includes the database ID in the default directory name: ditto-{database_id} instead of ditto. This is for only new databases created in v5. v5 does not migrate existing databases to the new structure. To maintain v4 compatibility:

Observer Changes

v4 and v5 both use callback-based observers. The core register_observer API pattern is the same in both versions, returning a StoreObserver that you hold via shared_ptr and call .cancel() on for cleanup.
Key changes:
  • v5 adds introspection methods to StoreObserver: get_query(), get_query_arguments(), get_query_arguments_cbor_data(), get_query_arguments_json_string()
  • Container type changed: store.get_observers() returns std::unordered_set (v4 returned std::set)
  • The deprecated store.observers public field is removed β€” use store.get_observers()

Back Pressure (SignalNext)

The standard register_observer automatically calls signal_next after your handler returns. Use the StoreObservationHandlerWithSignalNext overload when your handler needs manual control over when the next callback is delivered.
Coalescing behavior: While your handler is processing, Ditto coalesces any intermediate changes. When you call signal_next(), you receive a single callback with the latest state β€” not every intermediate change.
Types:
  • SignalNext = std::function<void()> (renamed from v4’s NextSignal)
  • StoreObservationHandlerWithSignalNext = std::function<void(QueryResult, SignalNext)> (renamed from v4’s StoreObservationHandlerWithNextSignal)

Presence API Changes

Presence is now accessed through ditto->get_presence() rather than directly on the Ditto object.
Peer property renames: New Presence APIs in v5:
  • Presence::graph() β€” get current PresenceGraph immediately
  • Presence::peer_metadata() / set_peer_metadata(json) β€” get/set arbitrary peer metadata
  • Presence::peer_metadata_json_string() / set_peer_metadata_json_string(str) β€” JSON string variants
  • Presence::set_connection_request_handler(handler) β€” control which peers can connect

DiskUsage API Changes

Transport Config Changes

update_transport_config and set_transport_config remain available in v5. The key change is:

Transaction API Changes

v5 removes the deprecated WriteTransaction and related classes. Use the DQL-based transaction API instead:
Key changes:
  • Store::write(fn) with WriteTransaction β€” removed
  • WriteTransaction, ScopedWriteTransaction, WriteTransactionResult, UpdateResult β€” removed
  • DQL-based Store::transaction() and Store::execute_transaction() are the replacement
  • New: Store::transaction_returning<T>() β€” return a value from a transaction
  • TransactionOptions available: set_read_only(bool), set_hint(string)

API Renames

Log β†’ Logger Rename

All Log:: call sites must be updated to Logger:::
Note: Log::Callback changed from a raw function pointer (void (*)(LogLevel, std::string)) to std::function<void(LogLevel, const std::string&)> β€” lambdas with captures are now supported. Logger::export_to_file(path) performs a one-time export of accumulated logs to disk. It returns std::future<uint64_t> (number of bytes written). The output is gzip-compressed JSON lines (.jsonl.gz recommended extension). This is not a replacement for Log::set_log_file() β€” it existed in v4 as Log::export_to_file() and is only renamed in v5. Migrating from Log::set_log_file(): Continuous real-time file logging has been removed in v5. If you need ongoing file logging, use Logger::set_custom_log_cb() to register a callback that writes to a file yourself.

Header Count Reduced

v5.0 ships 50 header files (down from 86 in v4.14). 38 headers were removed (legacy collection, document, live query, write transaction, identity, and log APIs). 2 headers were added (Logger.hpp, fnv_1a_hash.hpp). If your build system includes specific headers by path, audit the include list after upgrading.

Ditto Lifetime Management

Ditto::close() is removed. Manage Ditto lifetime via the shared_ptr returned by Ditto::open(). The instance is destroyed when all shared_ptr copies go out of scope.

APIs Removed Without Replacement

Legacy Collection API β€” Completely Removed

All collection-based query APIs (38 headers removed) are gone in v5. Use DQL queries via get_store().execute().

New APIs in v5

Ditto

  • Ditto::get_version() β€” replaces get_sdk_version()
  • Ditto::get_presence() β€” returns Presence & for presence operations

Logger

  • Logger class β€” replaces Log; callback type is now std::function
  • Logger::export_to_file(path) β€” one-time export of accumulated logs; returns std::future<uint64_t> (bytes written as gzip-compressed JSON lines). Renamed from Log::export_to_file() β€” not a replacement for the removed Log::set_log_file().

Sync

  • ditto->get_sync().start() / .stop() / .is_active() β€” sync lifecycle on Sync object
  • SyncSubscriptionHash / SyncSubscriptionEq β€” hash/eq helpers for subscription unordered_set
  • SyncSubscriptionSet type alias β€” unordered_set of SyncSubscription shared_ptrs
  • SyncSubscription::get_query() / get_query_arguments() / get_query_arguments_cbor_data() / get_query_arguments_json_string() β€” introspect registered subscription

Store & Observers

  • StoreObservationHandlerWithSignalNext / SignalNext β€” renamed from NextSignal variants
  • StoreObserverHash / StoreObserverEq β€” hash/eq helpers for observer unordered_set
  • StoreObserver::get_query() / get_query_arguments() / get_query_arguments_cbor_data() / get_query_arguments_json_string() β€” introspect registered observer
  • Store::execute_transaction() β€” convenience transaction with auto-commit
  • Store::transaction_returning<T>() β€” transaction that returns a value

Authentication

  • Authenticator::get_status() β€” returns current AuthenticationStatus
  • Authenticator::observe_status(callback) β€” observe authentication status changes
  • Authenticator::logout(cleanup) β€” log out with optional cleanup callback

Presence

  • Presence::observe(cb) β€” replaces Ditto::observe_peers()
  • Presence::graph() β€” get current PresenceGraph immediately
  • Presence::peer_metadata() / set_peer_metadata(json) β€” get/set arbitrary peer metadata
  • Presence::peer_metadata_json_string() / set_peer_metadata_json_string(str) β€” JSON string variants
  • Presence::set_connection_request_handler(handler) β€” control incoming peer connections

DiskUsage

  • DiskUsageItem β€” renamed from DiskUsageChild
  • DiskUsageObserver β€” renamed from DiskObserverContext
  • DiskUsage::item() β€” renamed from DiskUsage::exec()

Configuration

  • DittoConfig::set_server_connect(url) β€” convenience shortcut for set_connect(Connect::server(url))
  • DittoConfig::set_small_peers_only_connect(key) β€” convenience shortcut for set_connect(Connect::small_peers_only(key))

Migration Checklist

Initialization

  • Replace deprecated Ditto() constructor with Ditto::open(config)
  • Create DittoConfig with set_database_id and set_connect
  • Update Identity::OnlinePlayground β†’ DittoConfig::Connect::server(...)
  • Update Identity::OfflinePlayground β†’ DittoConfig::Connect::small_peers_only()
  • Update Identity::SharedKey β†’ DittoConfig::Connect::small_peers_only("your-shared-key")
  • Remove disable_sync_with_v3() calls
  • Set DQL_STRICT_MODE=true BEFORE starting sync if maintaining v4 behavior

Authentication

  • Set set_expiration_handler lambda after initialization
  • Handler receives Ditto &ditto, uint32_t sec_remaining
  • Access auth inside handler via ditto.get_auth()->login(...)
  • Use Authenticator::get_development_provider() for playground tokens
  • Remove authentication from identity configuration
  • Null-check get_auth() β€” returns nullptr for non-authenticated configs

Sync

  • Update start_sync() β†’ get_sync().start()
  • Update stop_sync() β†’ get_sync().stop()
  • Update get_is_sync_active() β†’ get_sync().is_active()

Data Operations

  • Replace all collection-based queries with DQL via get_store().execute()
  • Use parameterized queries with :paramName β€” never string interpolation
  • Replace .upsert() with INSERT INTO ... ON ID CONFLICT DO UPDATE
  • Replace .update {} closures with UPDATE SET DQL
  • Replace .remove() with DELETE FROM
  • Replace .evict() with EVICT FROM
  • Replace .observe_local() with get_store().register_observer()
  • Replace .subscribe() with get_sync().register_subscription()
  • Replace Store::write(fn) with Store::transaction() or Store::execute_transaction()

Presence

  • Update observe_peers(cb) β†’ get_presence().observe(cb)
  • Update peer_key_string β†’ peer_key
  • Update peer_key_string1/2 β†’ peer1/2
  • Update is_connected_to_ditto_cloud β†’ is_connected_to_ditto_server
  • Remove approximate_distance_in_meters usage (no replacement)

Logging

  • Replace Log:: with Logger:: throughout codebase
  • Update Log::Callback stored variables: raw function pointer β†’ std::function
  • Update Log::set_custom_log_cb β†’ Logger::set_custom_log_cb
  • Remove Log::set_log_file(path) calls β€” continuous file logging removed. Use Logger::set_custom_log_cb() if you need ongoing file output.
  • Remove Log::disable_log_file() calls
  • Remove Log::set_emoji_log_level_headings_enabled() calls

DiskUsage

  • Update DiskUsageChild β†’ DiskUsageItem
  • Update DiskUsage::exec() β†’ DiskUsage::item()

Breaking Changes

  • Set set_persistence_directory if maintaining v4 directory structure
  • Replace StoreObservationHandlerWithNextSignal β†’ StoreObservationHandlerWithSignalNext
  • Replace NextSignal β†’ SignalNext type alias
  • Replace store.observers field access with store.get_observers() (now returns unordered_set)
  • Replace Sync::subscriptions field access with Sync::get_subscriptions() (now returns unordered_set)
  • Remove Ditto::close() calls β€” manage lifetime via shared_ptr
  • Replace get_sdk_version() with get_version()
  • Audit header includes β€” header count reduced from 86 to 50 (38 removed, 2 added)
  • If processing is expensive, use StoreObservationHandlerWithSignalNext overload for back-pressure control
  • Remove HttpListenConfig::static_content_path references

Verification

  • Build compiles with zero errors
  • No deprecated API warnings
  • Observers update data immediately
  • Authentication works before sync starts
  • No memory leaks

Common Pitfalls

  1. DQL_STRICT_MODE silent change: Not setting DQL_STRICT_MODE=true when your v4 app used the default. Objects that were replaced whole will now merge at field level β€” causes unexpected data merging.
  2. Wrong auth accessor: Using ditto->auth() instead of ditto->get_auth(). Inside the expiration handler, the Ditto & reference requires ditto.get_auth()->login(...) (note: . for the reference, -> for the shared_ptr).
  3. Null auth pointer: get_auth() returns nullptr for small_peers_only configurations without a server. Always null-check before calling methods on the result.
  4. String interpolation in queries: Never build queries with string concatenation. Always use parameterized queries: execute("SELECT * FROM cars WHERE color = :color", {{"color", "blue"}}).
  5. Counter initialization: Don’t put 0 in insert documents for counter fields. Counters are created on first PN_INCREMENT. Inserting 0 creates a REGISTER, not a COUNTER.
  6. Missing ON ID CONFLICT: INSERT fails if document _id already exists without a conflict clause.
  7. Log::Callback type change: v4 used a raw function pointer (void(*)(LogLevel, std::string)). v5 uses std::function<void(LogLevel, const std::string&)>. Code storing callbacks as function pointers must be updated.
  8. shared_ptr lifetime: Ditto::open() returns shared_ptr<Ditto>. The instance is destroyed when all copies go out of scope. Do not call close() β€” it was removed.
  9. Observer container type: store.get_observers() now returns std::unordered_set instead of std::set. Code that depends on ordered iteration must be updated.